diff --git a/apps/backend/src/app/api/latest/projects/[project_id]/.well-known/jwks.json/route.ts b/apps/backend/src/app/api/latest/projects/[project_id]/.well-known/jwks.json/route.ts index ce3060f84..c2880e41a 100644 --- a/apps/backend/src/app/api/latest/projects/[project_id]/.well-known/jwks.json/route.ts +++ b/apps/backend/src/app/api/latest/projects/[project_id]/.well-known/jwks.json/route.ts @@ -1,4 +1,4 @@ -import { yupArray, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; +import { yupArray, yupNumber, yupObject, yupString, yupTuple } from "@stackframe/stack-shared/dist/schema-fields"; import { StatusError } from "@stackframe/stack-shared/dist/utils/errors"; import { deindent } from "@stackframe/stack-shared/dist/utils/strings"; import { getProject } from "../../../../../../../lib/projects"; @@ -27,7 +27,9 @@ export const GET = createSmartRouteHandler({ body: yupObject({ keys: yupArray().defined(), }).defined(), - + headers: yupObject({ + "Cache-Control": yupTuple([yupString().defined()]).defined(), + }).defined(), }), async handler({ params, query }) { const project = await getProject(params.project_id); @@ -42,7 +44,7 @@ export const GET = createSmartRouteHandler({ body: await getPublicProjectJwkSet(params.project_id, query.include_anonymous === "true"), headers: { // Cache for 1 hour - "Cache-Control": "public, max-age=3600", + "Cache-Control": ["public, max-age=3600"] as const, }, }; }, diff --git a/apps/backend/src/route-handlers/smart-response.tsx b/apps/backend/src/route-handlers/smart-response.tsx index e64206a00..e1116fb6b 100644 --- a/apps/backend/src/route-handlers/smart-response.tsx +++ b/apps/backend/src/route-handlers/smart-response.tsx @@ -122,16 +122,16 @@ export async function createResponse(req: NextRequest | headers.set("x-stack-actual-status", [obj.statusCode.toString()]); } + // set all headers from the smart response (considering case insensitivity) + for (const [key, values] of Object.entries(obj.headers ?? {})) { + headers.set(key.toLowerCase(), values); + } + return new Response( arrayBufferBody, { status, - headers: [ - ...Object.entries({ - ...Object.fromEntries(headers), - ...obj.headers ?? {} - }).flatMap(([key, values]) => values.map(v => [key.toLowerCase(), v!] as [string, string])), - ], + headers: [...headers].flatMap(([key, values]) => values.map(v => [key, v] satisfies [string, string])), }, ); }); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts index 57ba1dc85..a182b2652 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts @@ -1477,6 +1477,7 @@ it("has a correctly formatted JWKS endpoint", async ({ expect }) => { const response = await niceBackendFetch("/api/v1/projects/internal/.well-known/jwks.json"); expect(response.status).toBe(200); expect(response.headers.get("content-type")).includes("application/json"); + expect(response.headers.get("cache-control")).toBe("public, max-age=3600"); expect(response.body).toEqual({ keys: [ {