From b78e45b5c5dbd1f7264157cee1f2bfe20bf3a6d6 Mon Sep 17 00:00:00 2001 From: The Dogfather Date: Sat, 28 Mar 2026 01:23:10 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(auth):=20dev=20login=20403=20=E2=80=94?= =?UTF-8?q?=20resolve=20staff=20by=20id,=20not=20oidcSub=20(GRO-150)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DevLoginSelector stores the staff database id in localStorage and sends it as X-Dev-User-Id. The resolveStaffMiddleware incorrectly looked up staff by oidcSub instead of id, causing all API endpoints to return 403 for every user in dev mode. Co-Authored-By: Paperclip --- apps/api/src/__tests__/rbac.test.ts | 2 +- apps/api/src/middleware/rbac.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/src/__tests__/rbac.test.ts b/apps/api/src/__tests__/rbac.test.ts index b052507..d8c26bf 100644 --- a/apps/api/src/__tests__/rbac.test.ts +++ b/apps/api/src/__tests__/rbac.test.ts @@ -165,7 +165,7 @@ describe("resolveStaffMiddleware", () => { }); const res = await app.request("/test", { - headers: { "X-Dev-User-Id": GROOMER.oidcSub! }, + headers: { "X-Dev-User-Id": GROOMER.id }, }); expect(res.status).toBe(200); expect(capturedStaff!.role).toBe("groomer"); diff --git a/apps/api/src/middleware/rbac.ts b/apps/api/src/middleware/rbac.ts index 24a6753..98d9405 100644 --- a/apps/api/src/middleware/rbac.ts +++ b/apps/api/src/middleware/rbac.ts @@ -41,11 +41,11 @@ export const resolveStaffMiddleware: MiddlewareHandler = async ( await next(); return; } - // Treat X-Dev-User-Id as the oidcSub + // Treat X-Dev-User-Id as the staff database id (the frontend stores staff.id) const [row] = await db .select() .from(staff) - .where(eq(staff.oidcSub, devUserId)); + .where(eq(staff.id, devUserId)); if (!row) { return c.json( { error: "Forbidden: no staff record found for X-Dev-User-Id" }, From dc67b2bf449c8b4fbd7e0463b8f93a4487184b66 Mon Sep 17 00:00:00 2001 From: "groombook-engineer[bot]" <269742240+groombook-engineer[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 02:53:20 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix(gro-158):=20admin=20page=20blank=20?= =?UTF-8?q?=E2=80=94=20TypeError:=20b.filter=20is=20not=20a=20function=20(?= =?UTF-8?q?#141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes TypeError: b.filter is not a function on admin page.\n\nReviewed by: groombook-cto[bot], groombook-ceo[bot]\nCI: all checks passing --- apps/web/src/pages/Appointments.tsx | 15 ++++++++++++--- apps/web/src/pages/DevLoginSelector.tsx | 3 ++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/web/src/pages/Appointments.tsx b/apps/web/src/pages/Appointments.tsx index 4d64b1b..386354d 100644 --- a/apps/web/src/pages/Appointments.tsx +++ b/apps/web/src/pages/Appointments.tsx @@ -131,9 +131,18 @@ export function AppointmentsPage() { setError(null); Promise.all([ loadAppointments(), - fetch("/api/clients").then((r) => r.json() as Promise).then(setClients), - fetch("/api/services").then((r) => r.json() as Promise).then(setServices), - fetch("/api/staff").then((r) => r.json() as Promise).then(setStaff), + fetch("/api/clients").then((r) => { + if (!r.ok) throw new Error(`HTTP ${r.status}`); + return r.json() as Promise; + }).then(setClients), + fetch("/api/services").then((r) => { + if (!r.ok) throw new Error(`HTTP ${r.status}`); + return r.json() as Promise; + }).then(setServices), + fetch("/api/staff").then((r) => { + if (!r.ok) throw new Error(`HTTP ${r.status}`); + return r.json() as Promise; + }).then(setStaff), ]) .catch((e: unknown) => setError(e instanceof Error ? e.message : "Unknown error")) .finally(() => setLoading(false)); diff --git a/apps/web/src/pages/DevLoginSelector.tsx b/apps/web/src/pages/DevLoginSelector.tsx index e171613..6de753b 100644 --- a/apps/web/src/pages/DevLoginSelector.tsx +++ b/apps/web/src/pages/DevLoginSelector.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; interface StaffUser { id: string; + userId: string | null; name: string; email: string; role: string; @@ -66,7 +67,7 @@ export function DevLoginSelector() { {staff.map((s) => (