diff --git a/apps/backend/src/lib/tokens.tsx b/apps/backend/src/lib/tokens.tsx index 9322f746e..0092e6504 100644 --- a/apps/backend/src/lib/tokens.tsx +++ b/apps/backend/src/lib/tokens.tsx @@ -443,7 +443,9 @@ export async function createRefreshTokenObj(options: CreateRefreshTokenOptions) const refreshToken = generateSecureRandomString(); - const scopes = options.scopes ? [...new Set(options.scopes)] : []; + // Dedupe so the persisted scope string never carries duplicates (the registry intersection at + // grant time already constrains the set, this just normalizes it). + const scopes = options.scopes != null ? [...new Set(options.scopes)] : []; const refreshTokenObj = await globalPrismaClient.projectUserRefreshToken.create({ data: { diff --git a/apps/e2e/tests/backend/endpoints/api/v1/auth/sessions/scopes.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/auth/sessions/scopes.test.ts index 55357a31d..f8309f96f 100644 --- a/apps/e2e/tests/backend/endpoints/api/v1/auth/sessions/scopes.test.ts +++ b/apps/e2e/tests/backend/endpoints/api/v1/auth/sessions/scopes.test.ts @@ -79,7 +79,19 @@ it("treats unrestricted (no-scope) sessions as unrestricted for scoped endpoints body: { display_name: "Unrestricted Test Team" }, userAuth: { accessToken }, }); - expect(createRes.body.code).not.toBe("INSUFFICIENT_SCOPE"); + expect(createRes).toMatchInlineSnapshot(` + NiceResponse { + "status": 201, + "body": { + "client_metadata": null, + "client_read_only_metadata": null, + "display_name": "Unrestricted Test Team", + "id": "", + "profile_image_url": null, + }, + "headers": Headers {