From 30b49e82e80daedc9d49dd6550bef46c2592f374 Mon Sep 17 00:00:00 2001 From: "groombook-ci[bot]" Date: Sun, 29 Mar 2026 00:36:19 +0000 Subject: [PATCH] =?UTF-8?q?fix(api):=20mount=20POST=20/api/setup=20under?= =?UTF-8?q?=20auth=20middleware=20=E2=80=94=20security=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL: Previously the entire setupRouter was mounted on the public `app` (pre-auth), meaning POST /api/setup had no authentication and any anonymous user could claim super user. Now: - GET /api/setup/status remains public (needed for OOBE redirect check) - POST /api/setup is mounted on the authenticated /api basePath, requiring authMiddleware + resolveStaffMiddleware to run first Co-Authored-By: Paperclip --- apps/api/src/index.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 0ebc7fb..e98a003 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -20,7 +20,7 @@ import { settingsRouter } from "./routes/settings.js"; import { searchRouter } from "./routes/search.js"; import { calendarRouter } from "./routes/calendar.js"; import { setupRouter } from "./routes/setup.js"; -import { getDb, businessSettings } from "@groombook/db"; +import { getDb, businessSettings, eq, staff } from "@groombook/db"; import { authMiddleware } from "./middleware/auth.js"; import { resolveStaffMiddleware, requireRole, requireSuperUser } from "./middleware/rbac.js"; import { devRouter } from "./routes/dev.js"; @@ -69,8 +69,15 @@ app.get("/api/branding", async (c) => { app.route("/api/calendar", calendarRouter); // Public setup status — no auth required, must be registered before auth middleware -// GET /api/setup/status is handled by setupRouter -app.route("/api/setup", setupRouter); +app.get("/api/setup/status", async (c) => { + const db = getDb(); + const [superUser] = await db + .select({ id: staff.id }) + .from(staff) + .where(eq(staff.isSuperUser, true)) + .limit(1); + return c.json({ needsSetup: !superUser }); +}); // Protected API routes const api = app.basePath("/api"); @@ -131,6 +138,9 @@ api.on( ); // ────────────────────────────────────────────────────────────────────────────── +// Setup: POST /api/setup (authenticated) — requires staff context from auth middleware +api.route("/setup", setupRouter); + api.route("/clients", clientsRouter); api.route("/pets", petsRouter); api.route("/services", servicesRouter);