fix(api): mount POST /api/setup under auth middleware — security fix

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 <noreply@paperclip.ing>
This commit is contained in:
groombook-ci[bot]
2026-03-29 00:36:19 +00:00
committed by Flea Flicker
parent 3e4e57fa0b
commit 30b49e82e8
+13 -3
View File
@@ -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);