From 770f01a0572d90a9eb931ef1f29d30973e3ee83b Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Mon, 13 Jul 2026 13:40:56 -0700 Subject: [PATCH] Various improvements --- .../migration.sql | 5 + .../tests/index-created.ts | 28 ++++ apps/backend/prisma/schema.prisma | 1 + .../api/latest/internal/cli-auth/route.tsx | 122 +++++++++--------- .../internal/flush-background-tasks/route.tsx | 8 +- .../[projectId]/cli-auth/page-client.tsx | 55 ++++---- apps/e2e/tests/backend/backend-helpers.ts | 10 +- .../api/v1/internal/cli-auth.test.ts | 41 ++++++ .../internal/flush-background-tasks.test.ts | 33 +++++ .../ai-setup-prompt.ts | 2 +- .../apps/implementations/common.ts | 19 ++- .../src/pushed-config-error-overlay/index.ts | 2 +- scripts/reseed-bulldozer.sh | 103 +++++++++++++-- 13 files changed, 316 insertions(+), 113 deletions(-) create mode 100644 apps/backend/prisma/migrations/20260713000000_add_cli_auth_attempt_recent_index/migration.sql create mode 100644 apps/backend/prisma/migrations/20260713000000_add_cli_auth_attempt_recent_index/tests/index-created.ts create mode 100644 apps/e2e/tests/backend/endpoints/api/v1/internal/cli-auth.test.ts create mode 100644 apps/e2e/tests/backend/endpoints/api/v1/internal/flush-background-tasks.test.ts diff --git a/apps/backend/prisma/migrations/20260713000000_add_cli_auth_attempt_recent_index/migration.sql b/apps/backend/prisma/migrations/20260713000000_add_cli_auth_attempt_recent_index/migration.sql new file mode 100644 index 000000000..736715a44 --- /dev/null +++ b/apps/backend/prisma/migrations/20260713000000_add_cli_auth_attempt_recent_index/migration.sql @@ -0,0 +1,5 @@ +-- SPLIT_STATEMENT_SENTINEL +-- SINGLE_STATEMENT_SENTINEL +-- RUN_OUTSIDE_TRANSACTION_SENTINEL +CREATE INDEX CONCURRENTLY IF NOT EXISTS "CliAuthAttempt_tenancyId_createdAt_id_idx" + ON /* SCHEMA_NAME_SENTINEL */."CliAuthAttempt"("tenancyId", "createdAt" DESC, "id" DESC); diff --git a/apps/backend/prisma/migrations/20260713000000_add_cli_auth_attempt_recent_index/tests/index-created.ts b/apps/backend/prisma/migrations/20260713000000_add_cli_auth_attempt_recent_index/tests/index-created.ts new file mode 100644 index 000000000..b78d16ae0 --- /dev/null +++ b/apps/backend/prisma/migrations/20260713000000_add_cli_auth_attempt_recent_index/tests/index-created.ts @@ -0,0 +1,28 @@ +import type { Sql } from "postgres"; +import { expect } from "vitest"; + +export const postMigration = async (sql: Sql) => { + const indexes = await sql` + SELECT + pg_get_indexdef(index_relation.oid) AS indexdef, + index_metadata.indisvalid, + index_metadata.indisready + FROM pg_index index_metadata + JOIN pg_class index_relation + ON index_relation.oid = index_metadata.indexrelid + JOIN pg_class table_relation + ON table_relation.oid = index_metadata.indrelid + JOIN pg_namespace table_namespace + ON table_namespace.oid = table_relation.relnamespace + WHERE table_namespace.nspname = current_schema() + AND table_relation.relname = 'CliAuthAttempt' + AND index_relation.relname = 'CliAuthAttempt_tenancyId_createdAt_id_idx' + `; + + expect(indexes).toHaveLength(1); + expect(indexes[0]).toMatchObject({ + indisvalid: true, + indisready: true, + }); + expect(indexes[0].indexdef).toContain('("tenancyId", "createdAt" DESC, id DESC)'); +}; diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index 7d7f55b62..ae7fa75bf 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -1140,6 +1140,7 @@ model CliAuthAttempt { updatedAt DateTime @updatedAt @@id([tenancyId, id]) + @@index([tenancyId, createdAt(sort: Desc), id(sort: Desc)], name: "CliAuthAttempt_tenancyId_createdAt_id_idx") } model UserNotificationPreference { diff --git a/apps/backend/src/app/api/latest/internal/cli-auth/route.tsx b/apps/backend/src/app/api/latest/internal/cli-auth/route.tsx index 6cab88c15..8203df5ca 100644 --- a/apps/backend/src/app/api/latest/internal/cli-auth/route.tsx +++ b/apps/backend/src/app/api/latest/internal/cli-auth/route.tsx @@ -3,11 +3,12 @@ import { getPrismaClientForTenancy, getPrismaSchemaForTenancy, globalPrismaClien import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { adaptSchema, adminAuthTypeSchema, yupArray, yupBoolean, yupNumber, yupObject, yupString } from "@hexclave/shared/dist/schema-fields"; +const recentAttemptLimit = 50; +const activeTokenAttemptLimit = 200; + type CliAuthAttemptRow = { id: string, - pollingCode: string, - loginCode: string, - refreshToken: string | null, + status: "pending" | "completed" | "expired" | "used", expiresAt: Date, usedAt: Date | null, createdAt: Date, @@ -42,12 +43,14 @@ export const GET = createSmartRouteHandler({ bodyType: yupString().oneOf(["json"]).defined(), body: yupObject({ summary: yupObject({ - total_attempts: yupNumber().integer().defined(), - completed_attempts: yupNumber().integer().defined(), - used_attempts: yupNumber().integer().defined(), - expired_attempts: yupNumber().integer().defined(), - pending_attempts: yupNumber().integer().defined(), - active_tokens: yupNumber().integer().defined(), + attempts_in_window: yupNumber().integer().defined(), + completed_attempts_in_window: yupNumber().integer().defined(), + used_attempts_in_window: yupNumber().integer().defined(), + expired_attempts_in_window: yupNumber().integer().defined(), + pending_attempts_in_window: yupNumber().integer().defined(), + active_tokens_in_lookup_window: yupNumber().integer().defined(), + attempt_window_limit: yupNumber().integer().defined(), + active_token_lookup_window_limit: yupNumber().integer().defined(), }).defined(), recent_attempts: yupArray(yupObject({ id: yupString().defined(), @@ -73,72 +76,72 @@ export const GET = createSmartRouteHandler({ const schema = await getPrismaSchemaForTenancy(tenancy); const now = new Date(); - // Fetch recent CLI auth attempts (last 50) + // These dashboard metrics intentionally describe a bounded recent window. + // Exact all-time aggregates would scan unbounded history on every page load. const recentAttempts = await prisma.$replica().$queryRaw(Prisma.sql` SELECT "id", - "pollingCode", - "loginCode", - "refreshToken", + CASE + WHEN "usedAt" IS NOT NULL THEN 'used' + WHEN "expiresAt" < ${now} THEN 'expired' + WHEN "refreshToken" IS NOT NULL THEN 'completed' + ELSE 'pending' + END AS "status", "expiresAt", "usedAt", "createdAt" FROM ${sqlQuoteIdent(schema)}."CliAuthAttempt" WHERE "tenancyId" = ${tenancy.id}::UUID - ORDER BY "createdAt" DESC - LIMIT 50 + ORDER BY "createdAt" DESC, "id" DESC + LIMIT ${recentAttemptLimit} `); - // Compute summary stats let used = 0; let completed = 0; let expired = 0; let pending = 0; for (const attempt of recentAttempts) { - if (attempt.usedAt != null) { - used++; - } else if (attempt.expiresAt < now) { - expired++; - } else if (attempt.refreshToken != null) { - completed++; - } else { - pending++; + switch (attempt.status) { + case "used": { + used++; + break; + } + case "completed": { + completed++; + break; + } + case "expired": { + expired++; + break; + } + case "pending": { + pending++; + break; + } } } - // Format recent attempts with status const formattedAttempts = recentAttempts.map((attempt) => { - let status: "pending" | "completed" | "expired" | "used"; - if (attempt.usedAt != null) { - status = "used"; - } else if (attempt.refreshToken != null && attempt.expiresAt >= now) { - status = "completed"; - } else if (attempt.expiresAt < now) { - status = "expired"; - } else { - status = "pending"; - } return { id: attempt.id, - status, + status: attempt.status, created_at: attempt.createdAt.toISOString(), expires_at: attempt.expiresAt.toISOString(), used_at: attempt.usedAt?.toISOString() ?? null, }; }); - // Find active CLI tokens: tokens that were created via CLI auth (by looking - // at CliAuthAttempt rows that have a non-null refreshToken and usedAt). - // We join with the global ProjectUserRefreshToken table to find active sessions. - // Bounded to most recent 200 to avoid scanning entire history for high-usage tenants. + // Active-session discovery is also bounded: it considers refresh tokens + // attached to the most recently used CLI attempts, then checks which of + // those sessions still exist in the canonical refresh-token table. const cliRefreshTokens = await prisma.$replica().$queryRaw<{ refreshToken: string }[]>(Prisma.sql` SELECT "refreshToken" FROM ${sqlQuoteIdent(schema)}."CliAuthAttempt" WHERE "tenancyId" = ${tenancy.id}::UUID AND "refreshToken" IS NOT NULL AND "usedAt" IS NOT NULL - ORDER BY "createdAt" DESC - LIMIT 200 + ORDER BY "createdAt" DESC, "id" DESC + LIMIT ${activeTokenAttemptLimit} `); let activeCliUsers: Array<{ @@ -150,15 +153,12 @@ export const GET = createSmartRouteHandler({ expires_at: string | null, is_expired: boolean, }> = []; - let activeTokenCount = 0; if (cliRefreshTokens.length > 0) { - const tokenValues = cliRefreshTokens.map((r) => r.refreshToken); - - // Get accurate total count of active (non-expired) tokens separately from the display list + const tokenValues = cliRefreshTokens.map((row) => row.refreshToken); const countResult = await globalPrismaClient.$replica().$queryRaw<{ count: bigint }[]>(Prisma.sql` - SELECT COUNT(*) as count + SELECT COUNT(*) AS count FROM "ProjectUserRefreshToken" WHERE "tenancyId" = ${tenancy.id}::UUID AND "refreshToken" = ANY(${tokenValues}) @@ -166,7 +166,6 @@ export const GET = createSmartRouteHandler({ `); activeTokenCount = Number(countResult[0]?.count ?? 0n); - // Look up which tokens are still active in the global refresh token table (limited for display) const activeTokens = await globalPrismaClient.$replica().$queryRaw(Prisma.sql` SELECT "id", @@ -182,11 +181,7 @@ export const GET = createSmartRouteHandler({ `); if (activeTokens.length > 0) { - // Fetch user info for the active tokens - const userIds = [...new Set(activeTokens.map((t) => t.projectUserId))]; - // ProjectUser has no email column; the primary email lives in ContactChannel - // (type = EMAIL, isPrimary = TRUE). isPrimary/type are Postgres enums, so we - // cast to text for a schema-agnostic comparison. + const userIds = [...new Set(activeTokens.map((token) => token.projectUserId))]; const userRows = await prisma.$replica().$queryRaw(Prisma.sql` SELECT pu."projectUserId", @@ -201,11 +196,10 @@ export const GET = createSmartRouteHandler({ WHERE pu."tenancyId" = ${tenancy.id}::UUID AND pu."projectUserId" = ANY(${userIds}::UUID[]) `); - const userMap = new Map(userRows.map((u) => [u.projectUserId, u])); + const usersById = new Map(userRows.map((user) => [user.projectUserId, user])); activeCliUsers = activeTokens.map((token) => { - const user = userMap.get(token.projectUserId); - const isExpired = token.expiresAt != null && token.expiresAt < now; + const user = usersById.get(token.projectUserId); return { user_id: token.projectUserId, display_name: user?.displayName ?? null, @@ -213,7 +207,7 @@ export const GET = createSmartRouteHandler({ token_created_at: token.createdAt.toISOString(), last_active_at: token.lastActiveAt.toISOString(), expires_at: token.expiresAt?.toISOString() ?? null, - is_expired: isExpired, + is_expired: token.expiresAt != null && token.expiresAt < now, }; }); } @@ -224,12 +218,14 @@ export const GET = createSmartRouteHandler({ bodyType: "json" as const, body: { summary: { - total_attempts: recentAttempts.length, - completed_attempts: completed, - used_attempts: used, - expired_attempts: expired, - pending_attempts: pending, - active_tokens: activeTokenCount, + attempts_in_window: recentAttempts.length, + completed_attempts_in_window: completed, + used_attempts_in_window: used, + expired_attempts_in_window: expired, + pending_attempts_in_window: pending, + active_tokens_in_lookup_window: activeTokenCount, + attempt_window_limit: recentAttemptLimit, + active_token_lookup_window_limit: activeTokenAttemptLimit, }, recent_attempts: formattedAttempts, active_cli_users: activeCliUsers, diff --git a/apps/backend/src/app/api/latest/internal/flush-background-tasks/route.tsx b/apps/backend/src/app/api/latest/internal/flush-background-tasks/route.tsx index 6b9f83d48..a7331bc5c 100644 --- a/apps/backend/src/app/api/latest/internal/flush-background-tasks/route.tsx +++ b/apps/backend/src/app/api/latest/internal/flush-background-tasks/route.tsx @@ -1,6 +1,6 @@ import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { drainInFlightPromises } from "@/utils/background-tasks"; -import { adaptSchema, adminAuthTypeSchema, yupBoolean, yupNumber, yupObject, yupString } from "@hexclave/shared/dist/schema-fields"; +import { adminAuthTypeSchema, yupBoolean, yupNumber, yupObject, yupString } from "@hexclave/shared/dist/schema-fields"; // Test/dev-only hook that awaits any in-flight background tasks spawned via // `runAsynchronouslyAndWaitUntil` (e.g. Stripe webhook processing, which is @@ -18,7 +18,11 @@ export const POST = createSmartRouteHandler({ request: yupObject({ auth: yupObject({ type: adminAuthTypeSchema, - tenancy: adaptSchema.defined(), + tenancy: yupObject({ + project: yupObject({ + id: yupString().oneOf(["internal"]).defined(), + }).defined(), + }).defined(), }).defined(), method: yupString().oneOf(["POST"]).defined(), }), diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/cli-auth/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/cli-auth/page-client.tsx index 4357a0488..c103eb1c7 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/cli-auth/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/cli-auth/page-client.tsx @@ -24,12 +24,14 @@ import { PageLayout } from "../page-layout"; import { useAdminApp } from "../use-admin-app"; type CliAuthSummary = { - total_attempts: number, - completed_attempts: number, - used_attempts: number, - expired_attempts: number, - pending_attempts: number, - active_tokens: number, + attempts_in_window: number, + completed_attempts_in_window: number, + used_attempts_in_window: number, + expired_attempts_in_window: number, + pending_attempts_in_window: number, + active_tokens_in_lookup_window: number, + attempt_window_limit: number, + active_token_lookup_window_limit: number, }; type CliAuthAttempt = { @@ -96,7 +98,7 @@ function formatRelativeTime(dateStr: string): string { export default function PageClient() { return ( - + @@ -138,7 +140,7 @@ function CliAuthContent() { return (
- {Array.from({ length: 5 }).map((_, i) => )} + {Array.from({ length: 5 }).map((_, index) => )}
@@ -161,8 +163,8 @@ function CliAuthContent() { function CliAuthDashboard({ data }: { data: CliAuthData }) { const { summary, recent_attempts, active_cli_users } = data; - const activeUsers = active_cli_users.filter((u) => !u.is_expired); - const expiredUsers = active_cli_users.filter((u) => u.is_expired); + const activeUsers = active_cli_users.filter((user) => !user.is_expired); + const expiredUsers = active_cli_users.filter((user) => user.is_expired); return (
@@ -172,28 +174,28 @@ function CliAuthDashboard({ data }: { data: CliAuthData }) { description={<> CLI Auth allows users to authenticate from command-line tools using a browser-based login flow. The CLI initiates a session, the user confirms in a browser, and a refresh token is issued to the CLI. + Metrics below are bounded snapshots: attempt counts cover the newest {summary.attempt_window_limit} attempts, + and active sessions are discovered from the newest {summary.active_token_lookup_window_limit} used attempts. } /> - {/* Summary KPIs */}
- } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } />
- {/* Active CLI Users */} {activeUsers.length === 0 ? (
- No active CLI sessions. + No active CLI sessions in the lookup window.
) : (
@@ -203,7 +205,7 @@ function CliAuthDashboard({ data }: { data: CliAuthData }) { {user.display_name ?? user.primary_email ?? user.user_id} - {user.primary_email && user.display_name && ( + {user.primary_email != null && user.display_name != null && ( {user.primary_email} )}
@@ -224,17 +226,15 @@ function CliAuthDashboard({ data }: { data: CliAuthData }) { )} {expiredUsers.length > 0 && (
- - {expiredUsers.length} expired session{expiredUsers.length === 1 ? "" : "s"} + + {expiredUsers.length} expired session{expiredUsers.length === 1 ? "" : "s"} in the lookup window
{expiredUsers.map((user) => (
-
- - {user.display_name ?? user.primary_email ?? user.user_id} - -
+ + {user.display_name ?? user.primary_email ?? user.user_id} +
Expired {user.expires_at != null ? formatRelativeTime(user.expires_at) : ""} @@ -248,7 +248,6 @@ function CliAuthDashboard({ data }: { data: CliAuthData }) { )} - {/* Recent Attempts */} { + return await niceBackendFetch("/api/latest/internal/flush-background-tasks", { + method: "POST", + accessType: "admin", + body: {}, + }); }); expect(res.status).toBe(200); } diff --git a/apps/e2e/tests/backend/endpoints/api/v1/internal/cli-auth.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/internal/cli-auth.test.ts new file mode 100644 index 000000000..66badb6cb --- /dev/null +++ b/apps/e2e/tests/backend/endpoints/api/v1/internal/cli-auth.test.ts @@ -0,0 +1,41 @@ +import { it } from "../../../../../helpers"; +import { Project, niceBackendFetch } from "../../../../backend-helpers"; + +it("returns bounded CLI authentication metrics without exposing login secrets", async ({ expect }) => { + await Project.createAndSwitch(); + + const createdAttempt = await niceBackendFetch("/api/latest/auth/cli", { + method: "POST", + accessType: "server", + body: {}, + }); + expect(createdAttempt.status).toBe(200); + + const response = await niceBackendFetch("/api/latest/internal/cli-auth", { + method: "GET", + accessType: "admin", + }); + + expect(response.status).toBe(200); + expect(response.body.summary).toEqual({ + attempts_in_window: 1, + completed_attempts_in_window: 0, + used_attempts_in_window: 0, + expired_attempts_in_window: 0, + pending_attempts_in_window: 1, + active_tokens_in_lookup_window: 0, + attempt_window_limit: 50, + active_token_lookup_window_limit: 200, + }); + expect(response.body.recent_attempts).toHaveLength(1); + expect(response.body.recent_attempts[0]).toMatchObject({ + status: "pending", + used_at: null, + }); + expect(response.body.recent_attempts[0].id).toEqual(expect.any(String)); + expect(response.body.recent_attempts[0].created_at).toEqual(expect.any(String)); + expect(response.body.recent_attempts[0].expires_at).toEqual(expect.any(String)); + expect(response.body.active_cli_users).toEqual([]); + expect(JSON.stringify(response.body)).not.toContain(createdAttempt.body.polling_code); + expect(JSON.stringify(response.body)).not.toContain(createdAttempt.body.login_code); +}); diff --git a/apps/e2e/tests/backend/endpoints/api/v1/internal/flush-background-tasks.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/internal/flush-background-tasks.test.ts new file mode 100644 index 000000000..2dcfbf21d --- /dev/null +++ b/apps/e2e/tests/backend/endpoints/api/v1/internal/flush-background-tasks.test.ts @@ -0,0 +1,33 @@ +import { it } from "../../../../../helpers"; +import { Project, niceBackendFetch, withInternalProject } from "../../../../backend-helpers"; + +it("rejects a non-internal project", async ({ expect }) => { + await Project.createAndSwitch(); + + const response = await niceBackendFetch("/api/latest/internal/flush-background-tasks", { + method: "POST", + accessType: "admin", + body: {}, + }); + + expect(response.status).toBe(400); + expect(response.headers.get("x-stack-known-error")).toBe("SCHEMA_ERROR"); +}); + +it("accepts the internal project admin key", async ({ expect }) => { + const response = await withInternalProject(async () => { + return await niceBackendFetch("/api/latest/internal/flush-background-tasks", { + method: "POST", + accessType: "admin", + body: {}, + }); + }); + + expect(response).toMatchInlineSnapshot(` + NiceResponse { + "status": 200, + "body": { "success": true }, + "headers": Headers {