2.6 KiB
2.6 KiB
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<string>. - Two failure modes —
AUTH_UNAUTHENTICATED(401) for anonymous callers,AUTH_INSUFFICIENT_ROLE(403) for callers whoseprincipal.rolesdoes not intersect the required set. - Emits
auth.role_check_total{outcome}for every call, withoutcome ∈ "allowed" | "denied". - Carries
context.requiredandcontext.actualon the 403 error so audit logs / OpenAPI responses can show the precise mismatch.
Usage: Basic
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:
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 whenrolesis empty. - Throws
AuthError(AUTH_UNAUTHENTICATED)(401) whenc.get("user")is null/undefined. - Throws
AuthError(AUTH_INSUFFICIENT_ROLE)(403) when no role inprincipal.rolesis 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.