fix(api): add requireRoleOrSuperUser OR-guard, replace AND-stacking on staff routes
CRITICAL: requireRole("manager") + requireSuperUser() stacked = AND logic,
blocking all non-super-user managers from staff CRUD.
Added requireRoleOrSuperUser() OR-guard middleware. Staff write routes now use
the combined guard: manager role OR super-user flag grants access.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
committed by
Flea Flicker
parent
1e417eccb1
commit
5f867cd048
@@ -22,7 +22,7 @@ import { calendarRouter } from "./routes/calendar.js";
|
|||||||
import { setupRouter } from "./routes/setup.js";
|
import { setupRouter } from "./routes/setup.js";
|
||||||
import { getDb, businessSettings, eq, staff } from "@groombook/db";
|
import { getDb, businessSettings, eq, staff } from "@groombook/db";
|
||||||
import { authMiddleware } from "./middleware/auth.js";
|
import { authMiddleware } from "./middleware/auth.js";
|
||||||
import { resolveStaffMiddleware, requireRole, requireSuperUser } from "./middleware/rbac.js";
|
import { resolveStaffMiddleware, requireRole, requireRoleOrSuperUser, requireSuperUser } from "./middleware/rbac.js";
|
||||||
import { devRouter } from "./routes/dev.js";
|
import { devRouter } from "./routes/dev.js";
|
||||||
import { adminSeedRouter } from "./routes/admin/seed.js";
|
import { adminSeedRouter } from "./routes/admin/seed.js";
|
||||||
import { startReminderScheduler } from "./services/reminders.js";
|
import { startReminderScheduler } from "./services/reminders.js";
|
||||||
@@ -94,9 +94,8 @@ api.route("/auth", authRouter);
|
|||||||
// Manager-only: admin settings, reports, invoices, impersonation
|
// Manager-only: admin settings, reports, invoices, impersonation
|
||||||
// Staff CRUD: all roles may READ; manager-only for CREATE/UPDATE/DELETE
|
// Staff CRUD: all roles may READ; manager-only for CREATE/UPDATE/DELETE
|
||||||
api.on(["GET"], "/staff/*", requireRole("manager", "receptionist", "groomer"));
|
api.on(["GET"], "/staff/*", requireRole("manager", "receptionist", "groomer"));
|
||||||
// Staff write routes: manager + super-user
|
// Staff write routes: manager OR super-user (combined guard — avoids AND stacking)
|
||||||
api.on(["POST", "PATCH", "DELETE"], "/staff/*", requireRole("manager"));
|
api.on(["POST", "PATCH", "DELETE"], "/staff/*", requireRoleOrSuperUser("manager"));
|
||||||
api.on(["POST", "PATCH", "DELETE"], "/staff/*", requireSuperUser());
|
|
||||||
api.use("/admin/*", requireRole("manager"));
|
api.use("/admin/*", requireRole("manager"));
|
||||||
api.use("/admin/settings/*", requireSuperUser());
|
api.use("/admin/settings/*", requireSuperUser());
|
||||||
api.use("/reports/*", requireRole("manager"));
|
api.use("/reports/*", requireRole("manager"));
|
||||||
|
|||||||
@@ -126,6 +126,37 @@ export function requireRole(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware that allows access if the staff member has any of the allowed roles OR is a super user.
|
||||||
|
* Use for routes where managers OR super-users should have access.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* api.on(["POST", "PATCH", "DELETE"], "/staff/*", requireRoleOrSuperUser("manager"));
|
||||||
|
*/
|
||||||
|
export function requireRoleOrSuperUser(
|
||||||
|
...allowedRoles: StaffRole[]
|
||||||
|
): MiddlewareHandler<AppEnv> {
|
||||||
|
return async (c, next) => {
|
||||||
|
const staffRow = c.get("staff");
|
||||||
|
if (!staffRow) {
|
||||||
|
return c.json({ error: "Forbidden: staff record not resolved" }, 403);
|
||||||
|
}
|
||||||
|
const hasAllowedRole = (allowedRoles as string[]).includes(staffRow.role);
|
||||||
|
if (hasAllowedRole || staffRow.isSuperUser) {
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
error: staffRow.isSuperUser
|
||||||
|
? `Forbidden: role '${staffRow.role}' is not permitted`
|
||||||
|
: "Forbidden: super user privileges required",
|
||||||
|
},
|
||||||
|
403
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware that enforces the staff member is a super user.
|
* Middleware that enforces the staff member is a super user.
|
||||||
* Must be applied after resolveStaffMiddleware and (typically) after requireRole.
|
* Must be applied after resolveStaffMiddleware and (typically) after requireRole.
|
||||||
|
|||||||
Reference in New Issue
Block a user