feat(web): auth provider section in settings page (GRO-391) #216

Merged
groombook-engineer[bot] merged 15 commits from feat/gro-392-oobe-auth-provider-bootstrap into main 2026-04-03 08:16:17 +00:00
2 changed files with 65 additions and 2 deletions
Showing only changes of commit 652061f55d - Show all commits
+64 -1
View File
@@ -124,7 +124,7 @@ function buildWithStaff(
// ─── Import middleware ────────────────────────────────────────────────────────
const { resolveStaffMiddleware, requireRole, requireSuperUser } = await import(
const { resolveStaffMiddleware, requireRole, requireSuperUser, requireRoleOrSuperUser } = await import(
"../middleware/rbac.js"
);
@@ -326,3 +326,66 @@ describe("requireSuperUser", () => {
expect(contentType).toContain("application/json");
});
});
// ─── requireRoleOrSuperUser tests ─────────────────────────────────────────────
describe("requireRoleOrSuperUser", () => {
it("allows a manager to access manager-only routes", async () => {
const app = buildWithStaff(MANAGER, requireRoleOrSuperUser("manager"));
const res = await app.request("/test");
expect(res.status).toBe(200);
});
it("allows a super user with receptionist role to access manager-only routes (GRO-412 bug fix)", async () => {
// GRO-412: a receptionist granted super user via Staff UI should access admin routes
const superReceptionist: StaffRow = {
...RECEPTIONIST,
isSuperUser: true,
};
const app = buildWithStaff(superReceptionist, requireRoleOrSuperUser("manager"));
const res = await app.request("/test");
expect(res.status).toBe(200);
});
it("allows a super user with groomer role to access manager-only routes", async () => {
const superGroomer: StaffRow = {
...GROOMER,
isSuperUser: true,
};
const app = buildWithStaff(superGroomer, requireRoleOrSuperUser("manager"));
const res = await app.request("/test");
expect(res.status).toBe(200);
});
it("blocks a non-super-user receptionist from manager-only routes", async () => {
const app = buildWithStaff(RECEPTIONIST, requireRoleOrSuperUser("manager"));
const res = await app.request("/test");
expect(res.status).toBe(403);
const body = await res.json();
expect(body.error).toMatch(/super user privileges required/i);
});
it("blocks a non-super-user groomer from manager-only routes", async () => {
const app = buildWithStaff(GROOMER, requireRoleOrSuperUser("manager"));
const res = await app.request("/test");
expect(res.status).toBe(403);
const body = await res.json();
expect(body.error).toMatch(/super user privileges required/i);
});
it("allows a manager with multiple allowed roles", async () => {
const app = buildWithStaff(MANAGER, requireRoleOrSuperUser("manager", "receptionist"));
const res = await app.request("/test");
expect(res.status).toBe(200);
});
it("allows a super user with disallowed role to access route with multiple allowed roles", async () => {
const superGroomer: StaffRow = {
...GROOMER,
isSuperUser: true,
};
const app = buildWithStaff(superGroomer, requireRoleOrSuperUser("manager", "receptionist"));
const res = await app.request("/test");
expect(res.status).toBe(200);
});
});
+1 -1
View File
@@ -110,7 +110,7 @@ api.route("/auth", authRouter);
api.on(["GET"], "/staff/*", requireRole("manager", "receptionist", "groomer"));
// Staff write routes: manager OR super-user (combined guard — avoids AND stacking)
api.on(["POST", "PATCH", "DELETE"], "/staff/*", requireRoleOrSuperUser("manager"));
api.use("/admin/*", requireRole("manager"));
api.use("/admin/*", requireRoleOrSuperUser("manager"));
api.use("/admin/settings/*", requireSuperUser());
api.use("/reports/*", requireRole("manager"));
api.use("/invoices/*", requireRole("manager"));