Address CodeRabbit: harden InsufficientScope deserialization, type-only Scope import, explicit null check, snapshot test assertions

Co-Authored-By: mantra <mantra@stack-auth.com>
This commit is contained in:
Devin AI 2026-06-01 20:47:42 +00:00
parent f18255b6fa
commit 797a5f4dd5
4 changed files with 36 additions and 6 deletions

View File

@ -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: {

View File

@ -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": "<stripped UUID>",
"profile_image_url": null,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("persists scopes across a token refresh", async ({ expect }) => {
@ -106,8 +118,20 @@ it("persists scopes across a token refresh", async ({ expect }) => {
body: { display_name: "Refreshed Scoped Team" },
userAuth: { accessToken: refreshedAccessToken },
});
expect(createRes.status).toBe(403);
expect(createRes.body.code).toBe("INSUFFICIENT_SCOPE");
expect(createRes).toMatchInlineSnapshot(`
NiceResponse {
"status": 403,
"body": {
"code": "INSUFFICIENT_SCOPE",
"details": { "missing_scopes": ["teams:write"] },
"error": "The access token is missing the following required scope(s): 'teams:write'. Mint a token that includes these scopes and try again.",
},
"headers": Headers {
"x-stack-known-error": "INSUFFICIENT_SCOPE",
<some fields may have been hidden>,
},
}
`);
});
it("rejects creating a session with an unknown scope", async ({ expect }) => {

View File

@ -1,6 +1,6 @@
import * as yup from 'yup';
import { yupObject, yupString } from './schema-fields';
import { Scope } from './scopes';
import type { Scope } from './scopes';
import { filterUndefined } from './utils/objects';
import { NullishCoalesce } from './utils/types';

View File

@ -1389,7 +1389,11 @@ const InsufficientScope = createKnownErrorConstructor(
missing_scopes: missingScopes,
},
] as const,
(json: any) => [json.missing_scopes] as const,
(json: any) => [
Array.isArray(json?.missing_scopes)
? json.missing_scopes
: throwErr("missing_scopes not found (or not an array) in InsufficientScope details"),
] as const,
);
const InvalidSharedOAuthProviderId = createKnownErrorConstructor(