diff --git a/apps/backend/src/app/api/latest/auth/cli/poll/route.tsx b/apps/backend/src/app/api/latest/auth/cli/poll/route.tsx index 90c5b41aff..d0275ae889 100644 --- a/apps/backend/src/app/api/latest/auth/cli/poll/route.tsx +++ b/apps/backend/src/app/api/latest/auth/cli/poll/route.tsx @@ -1,8 +1,16 @@ -import { getPrismaClientForTenancy } from "@/prisma-client"; +import { Prisma } from "@/generated/prisma/client"; +import { getPrismaClientForTenancy, getPrismaSchemaForTenancy, sqlQuoteIdent } from "@/prisma-client"; import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { KnownErrors } from "@stackframe/stack-shared"; import { adaptSchema, clientOrHigherAuthTypeSchema, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; +type CliAuthAttemptRow = { + id: string, + refreshToken: string | null, + expiresAt: Date, + usedAt: Date | null, +}; + // Helper function to create response const createResponse = (status: 'waiting' | 'success' | 'expired' | 'used', refreshToken?: string) => ({ statusCode: status === 'success' ? 201 : 200, @@ -38,18 +46,24 @@ export const POST = createSmartRouteHandler({ }), async handler({ auth: { tenancy }, body: { polling_code } }) { const prisma = await getPrismaClientForTenancy(tenancy); + const schema = await getPrismaSchemaForTenancy(tenancy); - // Find the CLI auth attempt - const cliAuth = await prisma.cliAuthAttempt.findFirst({ - where: { - tenancyId: tenancy.id, - pollingCode: polling_code, - }, - }); + const cliAuthRows = await prisma.$queryRaw(Prisma.sql` + SELECT + "id", + "refreshToken", + "expiresAt", + "usedAt" + FROM ${sqlQuoteIdent(schema)}."CliAuthAttempt" + WHERE "tenancyId" = ${tenancy.id}::UUID + AND "pollingCode" = ${polling_code} + LIMIT 1 + `); - if (!cliAuth) { + if (cliAuthRows.length === 0) { throw new KnownErrors.InvalidPollingCodeError(); } + const cliAuth = cliAuthRows[0]; if (cliAuth.expiresAt < new Date()) { return createResponse('expired'); @@ -64,17 +78,14 @@ export const POST = createSmartRouteHandler({ } // Mark as used - await prisma.cliAuthAttempt.update({ - where: { - tenancyId_id: { - tenancyId: tenancy.id, - id: cliAuth.id, - }, - }, - data: { - usedAt: new Date(), - }, - }); + await prisma.$executeRaw(Prisma.sql` + UPDATE ${sqlQuoteIdent(schema)}."CliAuthAttempt" + SET + "usedAt" = NOW(), + "updatedAt" = NOW() + WHERE "tenancyId" = ${tenancy.id}::UUID + AND "id" = ${cliAuth.id}::UUID + `); return createResponse('success', cliAuth.refreshToken); }, diff --git a/apps/backend/src/app/api/latest/auth/cli/route.tsx b/apps/backend/src/app/api/latest/auth/cli/route.tsx index fb869535f5..6ddc28ecd6 100644 --- a/apps/backend/src/app/api/latest/auth/cli/route.tsx +++ b/apps/backend/src/app/api/latest/auth/cli/route.tsx @@ -25,7 +25,7 @@ export const POST = createSmartRouteHandler({ tenancy: adaptSchema.defined(), }).defined(), body: yupObject({ - expires_in_millis: yupNumber().max(1000 * 60 * 60 * 24).default(1000 * 60 * 120), // Default: 2 hours, max: 24 hours + expires_in_millis: yupNumber().max(1000 * 60 * 60 * 24).default(1000 * 60 * 2), // Default: 2 minutes, max: 24 hours anon_refresh_token: yupString().optional(), }).default({}), }), @@ -42,7 +42,6 @@ export const POST = createSmartRouteHandler({ let anonRefreshToken: string | null = null; if (anon_refresh_token) { - // ProjectUserRefreshToken lives in the global DB (see tokens.tsx and oauth/model.tsx). const refreshTokenRows = await globalPrismaClient.$queryRaw(Prisma.sql` SELECT "tenancyId", "projectUserId", "expiresAt" FROM "ProjectUserRefreshToken" diff --git a/docker/server/.env.example b/docker/server/.env.example index cbba7e67ce..bc8f1e8061 100644 --- a/docker/server/.env.example +++ b/docker/server/.env.example @@ -2,6 +2,9 @@ NEXT_PUBLIC_STACK_API_URL=http://localhost:8102 NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:8101 +STACK_API_URL=http://localhost:8102 +STACK_DASHBOARD_URL=http://localhost:8101 +STACK_CLI_PUBLISHABLE_CLIENT_KEY= STACK_DATABASE_CONNECTION_STRING=postgres://postgres:password@host.docker.internal:8128/stackframe diff --git a/packages/stack-cli/src/lib/auth.ts b/packages/stack-cli/src/lib/auth.ts index a2d2cc081b..dd5de6bd95 100644 --- a/packages/stack-cli/src/lib/auth.ts +++ b/packages/stack-cli/src/lib/auth.ts @@ -1,8 +1,8 @@ import { readConfigValue } from "./config.js"; import { AuthError } from "./errors.js"; -export const DEFAULT_API_URL = "https://api.stack-auth.com"; -export const DEFAULT_DASHBOARD_URL = "https://app.stack-auth.com"; +export const DEFAULT_API_URL = process.env.STACK_API_URL ?? "https://api.stack-auth.com"; +export const DEFAULT_DASHBOARD_URL = process.env.STACK_DASHBOARD_URL ?? "https://app.stack-auth.com"; export const DEFAULT_PUBLISHABLE_CLIENT_KEY = process.env.STACK_CLI_PUBLISHABLE_CLIENT_KEY ?? "pck_9bbqvqsbh0gdb6smk11d71qg4ktc4rz8ya7cc69yndm7g"; type Flags = {