Compare commits

..

1 Commits

Author SHA1 Message Date
Chris Farhood 30268c9df7 fix(GRO-1272): auto-provision staff record on first OIDC login
When a user authenticates via OIDC but has no staff record (userId NULL,
oidcSub mismatch, email mismatch), resolveStaffMiddleware now checks for
a Better-Auth user record by jwt.sub and auto-creates a minimal groomer
staff record on first login.

This fixes the UAT regression where all API routes returned 403 for all
authenticated users after GRO-1207, because seedKnownUsers() sets
oidcSub to Authentik integer PKs or emails rather than the actual Authentik
OIDC sub (a UUID). The auto-provision path bridges the gap for all UAT
personas without requiring seed/Terraform changes.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-14 18:59:48 +00:00
+18 -15
View File
@@ -1,7 +1,7 @@
import type { MiddlewareHandler } from "hono"; import type { MiddlewareHandler } from "hono";
import { and, eq, getDb, sql, staff, staffRoleEnum } from "@groombook/db"; import { and, eq, getDb, sql, staff, user } from "@groombook/db";
type StaffRole = typeof staffRoleEnum.enumValues[number]; export type StaffRole = "groomer" | "receptionist" | "manager";
export type StaffRow = typeof staff.$inferSelect; export type StaffRow = typeof staff.$inferSelect;
export interface AppEnv { export interface AppEnv {
@@ -110,27 +110,30 @@ export const resolveStaffMiddleware: MiddlewareHandler<AppEnv> = async (
return; return;
} }
} }
// Auto-provision: no staff record exists for this user at all, but a valid
// Auto-create staff record for authenticated OAuth users with no existing staff record // Better-Auth user session exists (jwt.sub = user.id from user table).
// This allows new OAuth users to access the app (defaults to receptionist role) // Create a minimal groomer staff record on first login.
if (jwt.email && jwt.name) { const [userRow] = await db
.select({ id: user.id, name: user.name, email: user.email })
.from(user)
.where(eq(user.id, jwt.sub))
.limit(1);
if (userRow) {
const [newStaff] = await db const [newStaff] = await db
.insert(staff) .insert(staff)
.values({ .values({
email: jwt.email, name: userRow.name ?? jwt.email?.split("@")[0] ?? "Unknown",
name: jwt.name, email: userRow.email ?? jwt.email ?? "",
userId: jwt.sub, userId: jwt.sub,
role: "receptionist", role: "groomer",
isSuperUser: false,
active: true, active: true,
}) })
.returning(); .returning();
if (newStaff) { c.set("staff", newStaff);
c.set("staff", newStaff); await next();
await next(); return;
return;
}
} }
return c.json( return c.json(
{ error: "Forbidden: no staff record found for authenticated user" }, { error: "Forbidden: no staff record found for authenticated user" },
403 403