diff --git a/services/cowork-service/src/modules/marketplace/routes.test.ts b/services/cowork-service/src/modules/marketplace/routes.test.ts index ef2609fa..a1578af4 100644 --- a/services/cowork-service/src/modules/marketplace/routes.test.ts +++ b/services/cowork-service/src/modules/marketplace/routes.test.ts @@ -217,6 +217,43 @@ describe('marketplace proxy routes', () => { expect(mockFetch.mock.calls[0][0]).toContain('/marketplace/listings/mine'); }); + // ── Auth Header Forwarding ── + + it('forwards Authorization header to platform-service for authenticated routes', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 201, + json: async () => ({ install: { id: 'inst_1' } }), + }); + + await app.inject({ + method: 'POST', + url: '/api/marketplace/lst_123/install', + headers: { authorization: 'Bearer test-jwt-token' }, + }); + + expect(mockFetch).toHaveBeenCalledOnce(); + const fetchHeaders = mockFetch.mock.calls[0][1].headers as Record; + expect(fetchHeaders['authorization']).toBe('Bearer test-jwt-token'); + expect(fetchHeaders['x-product-id']).toBe('clawcowork'); + }); + + it('omits Authorization header when no token present', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => ({ items: [], total: 0 }), + }); + + await app.inject({ + method: 'GET', + url: '/api/marketplace', + }); + + const fetchHeaders = mockFetch.mock.calls[0][1].headers as Record; + expect(fetchHeaders['authorization']).toBeUndefined(); + }); + // ── Error Handling ── it('returns 502 when platform-service is unreachable', async () => { diff --git a/services/cowork-service/src/modules/marketplace/routes.ts b/services/cowork-service/src/modules/marketplace/routes.ts index c9f3d9e9..49d1f268 100644 --- a/services/cowork-service/src/modules/marketplace/routes.ts +++ b/services/cowork-service/src/modules/marketplace/routes.ts @@ -24,15 +24,15 @@ import { ListListingsQuerySchema, ListInstallsQuerySchema, ListReviewsQuerySchem const platformUrl = config.PLATFORM_SERVICE_URL; /** Build standard proxy headers for platform-service. */ -function proxyHeaders(req: { id: string; jwtPayload?: { sub: string } }): Record { +function proxyHeaders(req: { id: string; headers: Record; jwtPayload?: { sub: string } }): Record { const headers: Record = { 'x-product-id': PRODUCT_ID, 'x-request-id': req.id, 'content-type': 'application/json', }; - if (req.jwtPayload?.sub) { - headers['x-user-id'] = req.jwtPayload.sub; - } + // Forward JWT for authenticated routes (consumer + author endpoints) + const auth = req.headers.authorization; + if (typeof auth === 'string') headers['authorization'] = auth; return headers; } diff --git a/services/cowork-service/src/server.ts b/services/cowork-service/src/server.ts index b409102a..1bc236cd 100644 --- a/services/cowork-service/src/server.ts +++ b/services/cowork-service/src/server.ts @@ -111,8 +111,8 @@ bridge.onIncomingRequest(async (method, params) => { const result = await getLlmRouter().chat({ messages: messages.map(m => ({ role: m.role as 'system' | 'user' | 'assistant', content: m.content })), model: (params.model as string) || undefined, - temperature: (params.temperature as number) || undefined, - max_tokens: (params.max_tokens as number) || undefined, + temperature: (params.temperature as number) ?? undefined, + max_tokens: (params.max_tokens as number) ?? undefined, }); // Record spend for budget tracking (best-effort)