first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# 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 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.