From a222bd45421bf82d9b0c6b48223e2f67084ca328 Mon Sep 17 00:00:00 2001 From: Flea Flicker Date: Wed, 15 Apr 2026 05:39:34 +0000 Subject: [PATCH] fix(auth): add email-based staff auto-linking in resolveStaffMiddleware Add email-based auto-linking fallback so staff records without a userId are automatically linked on first authenticated request. This fixes a UAT blocker where all authenticated API routes returned HTTP 403 after login because Better-Auth user IDs don't match seed-created staff records. Fallback chain: 1. userId match (existing fast path) 2. oidcSub match (legacy records) 3. email match + auto-link (new) --- apps/api/src/middleware/rbac.ts | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/api/src/middleware/rbac.ts b/apps/api/src/middleware/rbac.ts index b8473e8..b253eed 100644 --- a/apps/api/src/middleware/rbac.ts +++ b/apps/api/src/middleware/rbac.ts @@ -1,5 +1,5 @@ import type { MiddlewareHandler } from "hono"; -import { eq, getDb, staff } from "@groombook/db"; +import { and, eq, getDb, sql, staff } from "@groombook/db"; export type StaffRole = "groomer" | "receptionist" | "manager"; export type StaffRow = typeof staff.$inferSelect; @@ -89,14 +89,31 @@ export const resolveStaffMiddleware: MiddlewareHandler = async ( .select() .from(staff) .where(eq(staff.oidcSub, jwt.sub)); - if (!fallbackRow) { - return c.json( - { error: "Forbidden: no staff record found for authenticated user" }, - 403 - ); + if (fallbackRow) { + c.set("staff", fallbackRow); + await next(); + return; } - c.set("staff", fallbackRow); - await next(); + // Auto-link by email: staff record exists with matching email but no userId + if (jwt.email) { + const [byEmail] = await db + .select() + .from(staff) + .where(and(eq(staff.email, jwt.email), sql`${staff.userId} IS NULL`)); + if (byEmail) { + await db + .update(staff) + .set({ userId: jwt.sub, updatedAt: new Date() }) + .where(eq(staff.id, byEmail.id)); + c.set("staff", { ...byEmail, userId: jwt.sub }); + await next(); + return; + } + } + return c.json( + { error: "Forbidden: no staff record found for authenticated user" }, + 403 + ); }; /**