fix(rbac): guard noUncheckedIndexedAccess in name derivation and newStaff insert
CI / Lint & Typecheck (push) Successful in 12s
CI / Test (push) Successful in 14s
CI / Build & Push Docker Images (push) Successful in 46s

With noUncheckedIndexedAccess:true, split("@")[0] returns string|undefined,
making `name` typed as string|undefined and failing the notNull staff.name
insert constraint. Fix by using ?? fallback on the array access.

Also add newStaff null guard after .returning() destructure — array
destructuring yields T|undefined with noUncheckedIndexedAccess enabled.
This commit is contained in:
Lint Roller
2026-05-26 01:48:41 +00:00
parent b796d36aed
commit 3b9e82adff
+11 -8
View File
@@ -22,7 +22,7 @@ export const resolveStaffMiddleware: MiddlewareHandler<AppEnv> = async (
c, c,
next next
) => { ) => {
// Better-Auth's own routes handle their own auth — skip staff resolution // Better-Auth\'s own routes handle their own auth — skip staff resolution
// OOBE setup routes also handle their own auth — staff record is created during setup // OOBE setup routes also handle their own auth — staff record is created during setup
if (c.req.path.startsWith("/api/auth/") || c.req.path.startsWith("/api/setup")) { if (c.req.path.startsWith("/api/auth/") || c.req.path.startsWith("/api/setup")) {
await next(); await next();
@@ -120,22 +120,21 @@ export const resolveStaffMiddleware: MiddlewareHandler<AppEnv> = async (
.where( .where(
and( and(
eq(account.userId, jwt.sub), eq(account.userId, jwt.sub),
sql`${account.providerId} IN ('authentik', 'google', 'github')` sql`${account.providerId} IN (\'authentik\', \'google\', \'github\')`
) )
) )
.limit(1); .limit(1);
if (oidcAccount) { if (oidcAccount) {
// Derive name: prefer jwt.name, fall back to email prefix, then "Unknown" // Derive name: prefer jwt.name, fall back to email prefix, then "Unknown"
const name = const emailPrefix = jwt.email.split("@")[0] ?? "Unknown";
jwt.name?.trim() || const name = jwt.name?.trim() || emailPrefix;
(jwt.email ? jwt.email.split("@")[0] : "Unknown");
const [newStaff] = await db const [newStaff] = await db
.insert(staff) .insert(staff)
.values({ .values({
userId: jwt.sub, userId: jwt.sub,
email: jwt.email ?? "", email: jwt.email,
name, name,
role: "groomer", role: "groomer",
isSuperUser: false, isSuperUser: false,
@@ -143,6 +142,10 @@ export const resolveStaffMiddleware: MiddlewareHandler<AppEnv> = async (
}) })
.returning(); .returning();
if (!newStaff) {
return c.json({ error: "Forbidden: auto-provision failed" }, 500);
}
console.log( console.log(
`[rbac] auto-provisioned staff record for OIDC user: ${jwt.sub} -> staff:${newStaff.id} (${name})` `[rbac] auto-provisioned staff record for OIDC user: ${jwt.sub} -> staff:${newStaff.id} (${name})`
); );
@@ -177,7 +180,7 @@ export function requireRole(
if (!(allowedRoles as string[]).includes(staffRow.role)) { if (!(allowedRoles as string[]).includes(staffRow.role)) {
return c.json( return c.json(
{ {
error: `Forbidden: role '${staffRow.role}' is not permitted to access this resource`, error: `Forbidden: role \'${staffRow.role}\' is not permitted to access this resource`,
}, },
403 403
); );
@@ -210,7 +213,7 @@ export function requireRoleOrSuperUser(
{ {
error: hasAllowedRole error: hasAllowedRole
? "Forbidden: super user privileges required" ? "Forbidden: super user privileges required"
: `Forbidden: role '${staffRow.role}' is not permitted`, : `Forbidden: role \'${staffRow.role}\' is not permitted`,
}, },
403 403
); );