From 98d8f296064d26de6554e7a8508fe97bf2fd839e Mon Sep 17 00:00:00 2001 From: stefanbaxter Date: Sat, 21 Mar 2026 22:17:33 +0000 Subject: [PATCH] 010: Filter discover endpoint by JWT partition claim, remap dev ports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Discover endpoint now only returns datasources from the team matching the WorkOS JWT partition claim instead of all teams the user belongs to - Remap Actions host port 3000→3001 and Redis 6379→6381 in dev compose to avoid conflicts with other local services Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.dev.yml | 4 +- .../src/routes/__tests__/discover.test.js | 73 ++++++++++++++++++- services/cubejs/src/routes/discover.js | 32 +++++++- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 61ee5b1d..29cc4572 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -23,7 +23,7 @@ services: image: redis:7.0.0 restart: always ports: - - 6379:6379 + - 6381:6379 networks: - synmetrix_default @@ -36,7 +36,7 @@ services: - ./services/actions/src:/app/src - ./services/actions/index.js:/app/index.js ports: - - 3000:3000 + - 3001:3000 env_file: - .env - .dev.env diff --git a/services/cubejs/src/routes/__tests__/discover.test.js b/services/cubejs/src/routes/__tests__/discover.test.js index 629a32f0..b422ae41 100644 --- a/services/cubejs/src/routes/__tests__/discover.test.js +++ b/services/cubejs/src/routes/__tests__/discover.test.js @@ -1,7 +1,11 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; -import { extractCubes, buildDiscoverResponse } from "../discover.js"; +import { + extractCubes, + buildDiscoverResponse, + resolvePartitionTeamIds, +} from "../discover.js"; // --- Helpers --- @@ -236,4 +240,71 @@ describe("buildDiscoverResponse", () => { assert.equal(result[0].version_id, null); assert.deepEqual(result[0].cubes, []); }); + + it("filters datasources by partition team IDs", () => { + const dataSources = [ + makeDatasource("ds-a", "match", [yamlSchema("a.yml", [{ name: "A" }])], { + team_id: "team-1", + }), + makeDatasource("ds-b", "other", [yamlSchema("b.yml", [{ name: "B" }])], { + team_id: "team-2", + }), + ]; + const partitionTeamIds = new Set(["team-1"]); + const result = buildDiscoverResponse(dataSources, partitionTeamIds); + assert.equal(result.length, 1); + assert.equal(result[0].id, "ds-a"); + }); + + it("returns all datasources when no partitionTeamIds provided", () => { + const dataSources = [ + makeDatasource("ds-a", "a", [], { team_id: "team-1" }), + makeDatasource("ds-b", "b", [], { team_id: "team-2" }), + ]; + const result = buildDiscoverResponse(dataSources, null); + assert.equal(result.length, 2); + }); +}); + +// --- resolvePartitionTeamIds --- + +describe("resolvePartitionTeamIds", () => { + it("returns team IDs matching the partition", () => { + const members = [ + { team_id: "t1", team: { settings: { partition: "blue.is" } } }, + { team_id: "t2", team: { settings: { partition: "other.co" } } }, + { team_id: "t3", team: { settings: { partition: "blue.is" } } }, + ]; + const ids = resolvePartitionTeamIds(members, "blue.is"); + assert.equal(ids.size, 2); + assert.ok(ids.has("t1")); + assert.ok(ids.has("t3")); + assert.ok(!ids.has("t2")); + }); + + it("returns null when no partition in token", () => { + const members = [ + { team_id: "t1", team: { settings: { partition: "blue.is" } } }, + ]; + assert.equal(resolvePartitionTeamIds(members, undefined), null); + assert.equal(resolvePartitionTeamIds(members, null), null); + }); + + it("returns empty set when no teams match", () => { + const members = [ + { team_id: "t1", team: { settings: { partition: "other.co" } } }, + ]; + const ids = resolvePartitionTeamIds(members, "blue.is"); + assert.equal(ids.size, 0); + }); + + it("handles teams with no settings", () => { + const members = [ + { team_id: "t1", team: { settings: null } }, + { team_id: "t2", team: {} }, + { team_id: "t3" }, + ]; + const ids = resolvePartitionTeamIds(members, "blue.is"); + assert.equal(ids.size, 0); + }); }); diff --git a/services/cubejs/src/routes/discover.js b/services/cubejs/src/routes/discover.js index 0f863aca..bc8fdb21 100644 --- a/services/cubejs/src/routes/discover.js +++ b/services/cubejs/src/routes/discover.js @@ -36,11 +36,29 @@ export function extractCubes(schema) { })); } +/** + * Build a set of team IDs whose partition matches the JWT partition claim. + */ +export function resolvePartitionTeamIds(members, partition) { + if (!partition) return null; // no filtering if no partition in token + const ids = new Set(); + for (const member of members) { + if (member.team?.settings?.partition === partition) { + ids.add(member.team_id); + } + } + return ids; +} + /** * Build the datasources response from findUser result. + * When partitionTeamIds is provided, only include datasources from those teams. */ -export function buildDiscoverResponse(dataSources) { - return dataSources.map((ds) => { +export function buildDiscoverResponse(dataSources, partitionTeamIds) { + const filtered = partitionTeamIds + ? dataSources.filter((ds) => partitionTeamIds.has(ds.team_id)) + : dataSources; + return filtered.map((ds) => { const activeBranch = ds.branches?.find((b) => b.status === "active") || ds.branches?.[0]; const latestVersion = activeBranch?.versions?.[0] || null; @@ -103,7 +121,15 @@ export default async function discover(req, res) { return res.json({ datasources: [] }); } - res.json({ datasources: buildDiscoverResponse(user.dataSources) }); + // Only return datasources from the team matching the JWT partition claim + const partitionTeamIds = resolvePartitionTeamIds( + user.members, + payload.partition + ); + + res.json({ + datasources: buildDiscoverResponse(user.dataSources, partitionTeamIds), + }); } catch (err) { const status = err.status || 500; if (status >= 500) {