From 88b0fae9cdda06e5e78619d6422d52257f4f6d3a Mon Sep 17 00:00:00 2001 From: Proactive Runtime Bot Date: Wed, 3 Jun 2026 13:55:51 +0200 Subject: [PATCH] feat(relayauth): self-sufficient workspace-path mint --- .../server/src/__tests__/tokens-route.test.ts | 96 +++++++++++++------ packages/server/src/routes/tokens.ts | 11 ++- 2 files changed, 71 insertions(+), 36 deletions(-) diff --git a/packages/server/src/__tests__/tokens-route.test.ts b/packages/server/src/__tests__/tokens-route.test.ts index 7d589ce..25c8db8 100644 --- a/packages/server/src/__tests__/tokens-route.test.ts +++ b/packages/server/src/__tests__/tokens-route.test.ts @@ -673,21 +673,38 @@ test("POST /v1/tokens/path", async (t) => { }); test("POST /v1/tokens/workspace-path", async (t) => { - async function seedWorkspace( - app: ReturnType, - workspaceId = "ws_tokens_route", - orgId = "org_tokens_route", - ): Promise { - await seedWorkspaceContext(app, { - id: workspaceId, - workspaceId, - orgId, - scopes: [], - roles: [], + await t.test("requires workspaceId", async () => { + const { app, authHeaders } = await createHarness({ + authClaims: { + scopes: [ + "relayauth:api-key:manage:*", + "relayfile:fs:read:*", + "relayfile:fs:write:*", + ], + }, }); - } + const orgApiKey = await issueApiKey(app, authHeaders, [ + "relayauth:api-key:manage:*", + "relayfile:fs:read:*", + "relayfile:fs:write:*", + ]); - await t.test("mints a short-lived relay_pa directly from an org api key", async () => { + const response = await requestRoute(app, "POST", "/v1/tokens/workspace-path", { + body: { + paths: ["/github/repos/AgentWorkforce/cloud/issues/123/**"], + scopes: ["relayfile:fs:write:/github/repos/AgentWorkforce/cloud/issues/123/**"], + }, + headers: { + "x-api-key": orgApiKey.key, + }, + }); + + await assertJsonResponse(response, 400, (body) => { + assert.equal(body.code, "workspaceId_required"); + }); + }); + + await t.test("mints a short-lived relay_pa directly from an org api key without a seeded workspace row", async () => { const { app, authHeaders } = await createHarness({ authClaims: { scopes: [ @@ -697,7 +714,6 @@ test("POST /v1/tokens/workspace-path", async (t) => { ], }, }); - await seedWorkspace(app); const orgApiKey = await issueApiKey(app, authHeaders, [ "relayauth:api-key:manage:*", "relayfile:fs:read:*", @@ -706,7 +722,7 @@ test("POST /v1/tokens/workspace-path", async (t) => { const response = await requestRoute(app, "POST", "/v1/tokens/workspace-path", { body: { - workspaceId: "ws_tokens_route", + workspaceId: " ws_tokens_route ", agentName: "cloud-team-member", paths: ["/github/repos/AgentWorkforce/cloud/issues/123/**"], scopes: ["relayfile:fs:write:/github/repos/AgentWorkforce/cloud/issues/123/**"], @@ -732,6 +748,7 @@ test("POST /v1/tokens/workspace-path", async (t) => { const accessClaims = decodeJwtJsonSegment(body.accessToken, 1); assert.equal(accessClaims.sub, "agent_cloud-team-member"); assert.equal(accessClaims.wks, "ws_tokens_route"); + assert.equal(accessClaims.org, "org_tokens_route"); assert.equal(accessClaims.meta?.tokenClass, "path"); assert.equal(accessClaims.meta?.workspaceTokenId, undefined); assert.equal(accessClaims.parentTokenId, undefined); @@ -743,9 +760,10 @@ test("POST /v1/tokens/workspace-path", async (t) => { assert.ok(accessClaims.exp - accessClaims.iat <= 120, "direct path access TTL should honor short ttlSeconds"); }); - await t.test("caps direct path token TTL at the agent-token maximum", async () => { + await t.test("stamps the authenticated org even when the workspaceId is associated with another org", async () => { const { app, authHeaders } = await createHarness({ authClaims: { + org: "org_a", scopes: [ "relayauth:api-key:manage:*", "relayfile:fs:read:*", @@ -753,42 +771,60 @@ test("POST /v1/tokens/workspace-path", async (t) => { ], }, }); - await seedWorkspace(app); + await seedWorkspaceContext(app, { + id: "ws_owned_by_org_b", + workspaceId: "ws_owned_by_org_b", + orgId: "org_b", + scopes: [], + roles: [], + }); + const orgApiKey = await issueApiKey(app, authHeaders, [ + "relayauth:api-key:manage:*", + "relayfile:fs:read:*", + "relayfile:fs:write:*", + ]); const response = await requestRoute(app, "POST", "/v1/tokens/workspace-path", { body: { - workspaceId: "ws_tokens_route", + workspaceId: "ws_owned_by_org_b", paths: ["/github/repos/AgentWorkforce/cloud/issues/123/*"], - ttlSeconds: 7200, + scopes: ["relayfile:fs:write:/github/repos/AgentWorkforce/cloud/issues/123/*"], + }, + headers: { + "x-api-key": orgApiKey.key, }, - headers: authHeaders, }); const body = await assertJsonResponse(response, 201); const accessClaims = decodeJwtJsonSegment(body.accessToken, 1); - assert.ok(accessClaims.exp - accessClaims.iat <= 3600, "direct path access TTL should cap at 1h"); + assert.equal(accessClaims.org, "org_a"); + assert.equal(accessClaims.wks, "ws_owned_by_org_b"); + assert.equal(body.workspaceId, "ws_owned_by_org_b"); }); - await t.test("rejects a workspace outside the caller org", async () => { + await t.test("caps direct path token TTL at the agent-token maximum", async () => { const { app, authHeaders } = await createHarness({ authClaims: { - scopes: ["relayauth:api-key:manage:*", "relayfile:fs:write:*"], + scopes: [ + "relayauth:api-key:manage:*", + "relayfile:fs:read:*", + "relayfile:fs:write:*", + ], }, }); - await seedWorkspace(app, "ws_other_org", "org_other"); const response = await requestRoute(app, "POST", "/v1/tokens/workspace-path", { body: { - workspaceId: "ws_other_org", + workspaceId: "ws_tokens_route", paths: ["/github/repos/AgentWorkforce/cloud/issues/123/*"], - scopes: ["relayfile:fs:write:/github/repos/AgentWorkforce/cloud/issues/123/*"], + ttlSeconds: 7200, }, headers: authHeaders, }); - await assertJsonResponse(response, 404, (body) => { - assert.equal(body.code, "workspace_not_found"); - }); + const body = await assertJsonResponse(response, 201); + const accessClaims = decodeJwtJsonSegment(body.accessToken, 1); + assert.ok(accessClaims.exp - accessClaims.iat <= 3600, "direct path access TTL should cap at 1h"); }); await t.test("rejects requested scopes outside the org api-key grant", async () => { @@ -797,7 +833,6 @@ test("POST /v1/tokens/workspace-path", async (t) => { scopes: ["relayauth:api-key:manage:*", "relayfile:fs:read:*"], }, }); - await seedWorkspace(app); const response = await requestRoute(app, "POST", "/v1/tokens/workspace-path", { body: { @@ -819,7 +854,6 @@ test("POST /v1/tokens/workspace-path", async (t) => { scopes: ["relayauth:api-key:manage:*", "relayfile:fs:write:*"], }, }); - await seedWorkspace(app); const degenerate = await requestRoute(app, "POST", "/v1/tokens/workspace-path", { body: { diff --git a/packages/server/src/routes/tokens.ts b/packages/server/src/routes/tokens.ts index 180e8c8..04541ea 100644 --- a/packages/server/src/routes/tokens.ts +++ b/packages/server/src/routes/tokens.ts @@ -442,10 +442,11 @@ tokens.post("/workspace-path", async (c) => { } const storage = getSqlStorage(c.get("storage")); - const workspace = await storage.contexts.getWorkspace(workspaceId); - if (!workspace || workspace.orgId !== auth.claims.org) { - return c.json({ error: "workspace_not_found", code: "workspace_not_found" }, 404); - } + // Direct workspace-path minting is intentionally equivalent to: + // POST /v1/tokens/workspace (org API key + caller-supplied workspaceId) + // followed by /v1/tokens/path. + // The org API key grant is the authorization boundary here; the workspace + // row is not an auth source and must not be required on this hot path. const paths = normalizePathTokenPaths(body.paths); if (!paths.ok) { @@ -469,7 +470,7 @@ tokens.post("/workspace-path", async (c) => { agentId, agentName, orgId: auth.claims.org, - workspaceId: workspace.workspaceId, + workspaceId, sponsorId: auth.claims.sponsorId, sponsorChain: auth.claims.sponsorChain, scopes: auth.claims.scopes,