# middleware/require-role ## Purpose `requireRole(...roles)` builds a Hono middleware that gates a route on the principal carrying at least one of the supplied roles. The factory throws an `AuthError(AUTH_INVALID_INPUT)` at construction time when called with no roles — a guard against accidentally building a no-op middleware. ## Features - Constant-time membership check against an internal `Set`. - Two failure modes — `AUTH_UNAUTHENTICATED` (401) for anonymous callers, `AUTH_INSUFFICIENT_ROLE` (403) for callers whose `principal.roles` does not intersect the required set. - Emits `auth.role_check_total{outcome}` for every call, with `outcome ∈ "allowed" | "denied"`. - Carries `context.required` and `context.actual` on the 403 error so audit logs / OpenAPI responses can show the precise mismatch. ## Usage: Basic ```ts import { requireAuth, requireRole } from "@suaveplan/api-plugin-auth/middleware"; app.use("/admin/*", requireAuth(), requireRole("admin")); ``` ## Usage: Advanced Compose multiple roles for resource-level scoping. Combine with `AuthService` lookups to project derived roles (e.g. organisation membership) onto the request before the middleware runs: ```ts import { requireAuth, requireRole } from "@suaveplan/api-plugin-auth/middleware"; app.use("/projects/:id/edit", requireAuth(), async (c, next) => { const principal = c.get("user"); if (!principal) return next(); const isOwner = await checkOwnership(c.req.param("id"), principal.id); if (isOwner) { c.set("user", { ...principal, roles: [...principal.roles, "owner"] }); } await next(); }, requireRole("admin", "owner")); ``` ## API Reference ### `function requireRole(...roles: readonly string[]): MiddlewareHandler` - Throws `AuthError(AUTH_INVALID_INPUT)` at construction when `roles` is empty. - Throws `AuthError(AUTH_UNAUTHENTICATED)` (401) when `c.get("user")` is null/undefined. - Throws `AuthError(AUTH_INSUFFICIENT_ROLE)` (403) when no role in `principal.roles` is in the required set. - Emits `auth.role_check_total{outcome="allowed"|"denied"}` on every request. ## Implementation Notes - The required-roles set is constructed once per factory call. Reusing the same middleware across multiple routes is constant memory. - The error context carries the actual roles as well as the required ones so the 403 response can surface a "missing role: admin" message in operator logs without re-reading the principal. - The middleware never inspects `principal.tenantId` — tenant-scoped role gates are the responsibility of `@suaveplan/api-tenant`'s middleware, layered downstream.