Skip to content

Commit 94637a2

Browse files
committed
Update default expiration time for CLI auth from 2 minutes to 10 minutes and improve null checks in route handlers
1 parent 38a64ff commit 94637a2

4 files changed

Lines changed: 18 additions & 13 deletions

File tree

apps/backend/src/app/api/latest/auth/cli/poll/route.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,24 +69,32 @@ export const POST = createSmartRouteHandler({
6969
return createResponse('expired');
7070
}
7171

72-
if (cliAuth.usedAt) {
72+
if (cliAuth.usedAt !== null) {
7373
return createResponse('used');
7474
}
7575

76-
if (!cliAuth.refreshToken) {
76+
if (cliAuth.refreshToken === null) {
7777
return createResponse('waiting');
7878
}
7979

80-
// Mark as used
81-
await prisma.$executeRaw(Prisma.sql`
80+
// Atomically mark as used, claiming the row only if no one else has.
81+
// This prevents a TOCTOU race where two concurrent polls could both
82+
// read usedAt = null and both receive the same refresh token.
83+
const claimed = await prisma.$queryRaw<{ refreshToken: string }[]>(Prisma.sql`
8284
UPDATE ${sqlQuoteIdent(schema)}."CliAuthAttempt"
8385
SET
8486
"usedAt" = NOW(),
8587
"updatedAt" = NOW()
8688
WHERE "tenancyId" = ${tenancy.id}::UUID
8789
AND "id" = ${cliAuth.id}::UUID
90+
AND "usedAt" IS NULL
91+
RETURNING "refreshToken"
8892
`);
8993

90-
return createResponse('success', cliAuth.refreshToken);
94+
if (claimed.length === 0) {
95+
return createResponse('used');
96+
}
97+
98+
return createResponse('success', claimed[0].refreshToken);
9199
},
92100
});

apps/backend/src/app/api/latest/auth/cli/route.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const POST = createSmartRouteHandler({
2525
tenancy: adaptSchema.defined(),
2626
}).defined(),
2727
body: yupObject({
28-
expires_in_millis: yupNumber().max(1000 * 60 * 60 * 24).default(1000 * 60 * 2), // Default: 2 minutes, max: 24 hours
28+
expires_in_millis: yupNumber().max(1000 * 60 * 60 * 24).default(1000 * 60 * 10), // Default: 10 minutes, max: 24 hours
2929
anon_refresh_token: yupString().optional(),
3030
}).default({}),
3131
}),
@@ -41,7 +41,7 @@ export const POST = createSmartRouteHandler({
4141
async handler({ auth: { tenancy }, body: { expires_in_millis, anon_refresh_token } }) {
4242
let anonRefreshToken: string | null = null;
4343

44-
if (anon_refresh_token) {
44+
if (anon_refresh_token != null) {
4545
const refreshTokenRows = await globalPrismaClient.$queryRaw<RefreshTokenRow[]>(Prisma.sql`
4646
SELECT "tenancyId", "projectUserId", "expiresAt"
4747
FROM "ProjectUserRefreshToken"
@@ -57,7 +57,7 @@ export const POST = createSmartRouteHandler({
5757
throw new StatusError(400, "Anon refresh token does not belong to this project");
5858
}
5959

60-
if (refreshTokenObj.expiresAt && refreshTokenObj.expiresAt < new Date()) {
60+
if (refreshTokenObj.expiresAt != null && refreshTokenObj.expiresAt < new Date()) {
6161
throw new StatusError(400, "The provided anon refresh token has expired");
6262
}
6363

docker/server/.env.example

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
NEXT_PUBLIC_STACK_API_URL=http://localhost:8102
44
NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:8101
5-
STACK_API_URL=http://localhost:8102
6-
STACK_DASHBOARD_URL=http://localhost:8101
7-
STACK_CLI_PUBLISHABLE_CLIENT_KEY=
85

96
STACK_DATABASE_CONNECTION_STRING=postgres://postgres:password@host.docker.internal:8128/stackframe
107

packages/stack-cli/src/lib/auth.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { readConfigValue } from "./config.js";
22
import { AuthError } from "./errors.js";
33

4-
export const DEFAULT_API_URL = process.env.STACK_API_URL ?? "https://api.stack-auth.com";
5-
export const DEFAULT_DASHBOARD_URL = process.env.STACK_DASHBOARD_URL ?? "https://app.stack-auth.com";
4+
export const DEFAULT_API_URL = "https://api.stack-auth.com";
5+
export const DEFAULT_DASHBOARD_URL = "https://app.stack-auth.com";
66
export const DEFAULT_PUBLISHABLE_CLIENT_KEY = process.env.STACK_CLI_PUBLISHABLE_CLIENT_KEY ?? "pck_9bbqvqsbh0gdb6smk11d71qg4ktc4rz8ya7cc69yndm7g";
77

88
type Flags = {

0 commit comments

Comments
 (0)