Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
# @suaveplan/api-rbac - Role/scope/rule-based authorisation middleware for the Suaveplan API plugin system. Produces a framework-agnostic `Middleware` that consumes the resolved `Principal` plus a route's `RouteAuthSpec` (scope name, @suaveplan/rules Rule, or public bypass), emits 401 when a non-public route has no principal, 403 on missing-scope or rule-deny, and increments `api.rbac.denials_total{plugin, route, reason}`. Ships a conflict-aware `ScopeRegistry` plus `registerPluginScopes(registry, manifest)` so plugin-declared scopes auto-register at install and duplicates raise `RBAC.SCOPE_CONFLICT`. Universal, Zod-validated boundaries, real OTel surface.
Scope + rule-based authorisation middleware for the Suaveplan API plugin
system. Produces a framework-agnostic `Middleware` that consumes the
resolved `Principal` from `RequestContext` and the matched route's
`RouteAuthSpec`, performs the gate, and either calls `next()` or throws a
typed `RbacError`. Every denial increments
`api.rbac.denials_total{plugin, route, reason}` with `reason ∈
{ "missing-scope", "rule-deny", "no-principal" }`.
<!-- begin:auto-docs -->
## Documentation tree
- [error-codes](./src/error-codes/error-codes.md) — Stable, frozen registry of every `RBAC.*` error code surfaced by the `@suaveplan/api-rbac` authorisation middleware and…
- [error-codes](./src/error-codes/error-codes.md) — Stable, frozen registry of every `RBAC.*` error code surfaced by the `@suaveplan/api-rbac` authorisation middleware and…
- [instruments](./src/instruments/instruments.md) — Owns the single OpenTelemetry counter `api.rbac.denials_total` and the factory that constructs the `RbacInstruments` re…
- [instruments](./src/instruments/instruments.md) — Owns the single OpenTelemetry counter `api.rbac.denials_total` and the factory that constructs the `RbacInstruments` re…
- [middleware](./src/middleware/middleware.md) — Ships `createRbacMiddleware(options)` — the per-request authorisation gate. The factory produces a framework-agnostic `…
- [middleware](./src/middleware/middleware.md) — Ships `createRbacMiddleware(options)` — the per-request authorisation gate. The factory produces a framework-agnostic `…
- [registry](./src/registry/registry.md) — Owns the shared, conflict-aware registry of plugin-declared scopes plus the `registerPluginScopes(registry, manifest)`…
- [registry](./src/registry/registry.md) — Owns the shared, conflict-aware registry of plugin-declared scopes plus the `registerPluginScopes(registry, manifest)`…
- [spec](./src/spec/spec.md) — Defines the canonical discriminated `RouteAuthSpec` union, its Zod schema, the inbound shape adapters fold down to it,…
- [spec](./src/spec/spec.md) — Defines the canonical discriminated `RouteAuthSpec` union, its Zod schema, the inbound shape adapters fold down to it,…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/api-rbac
```
## Quick start
```ts
import { createRbacMiddleware } from "@suaveplan/api-rbac";
const middleware = createRbacMiddleware({
routeFor: () => ({
plugin: "billing",
path: "/invoices",
auth: { kind: "scope", scope: "billing:read" },
}),
});
```
The middleware is the abstract `Middleware` from `@suaveplan/api-types` —
adapters (`@suaveplan/api-router-hono`, `api-router-in-memory`, etc.) plug
it into their pipeline directly.
## Modules
- [`error-codes`](./src/error-codes/error-codes.md) — `RBAC_CODES` registry
(`MISSING_SCOPE`, `RULE_DENY`, `NO_PRINCIPAL`, `SCOPE_CONFLICT`,
`INVALID_SPEC`) and the `RbacError` class.
- [`instruments`](./src/instruments/instruments.md) — the single counter
`api.rbac.denials_total` plus `createRbacInstruments` / `recordRbacDenial`.
- [`middleware`](./src/middleware/middleware.md) — `createRbacMiddleware`
and `statusForRbacError` mapping.
- [`registry`](./src/registry/registry.md) — `ScopeRegistry` plus
`registerPluginScopes(registry, manifest)` for install-time conflict
detection.
- [`spec`](./src/spec/spec.md) — the canonical `RouteAuthSpec` union, the
Zod `RouteAuthSpecSchema`, and the `normaliseRouteAuth` fold.
## OpenTelemetry
- counter `api.rbac.denials_total{plugin, route, reason}` — emitted on
every deny path.
Reason label values: `"missing-scope" | "rule-deny" | "no-principal"`.
## Error codes
| Code | HTTP | When |
| ---------------------- | ---- | -------------------------------------------------------- |
| `RBAC.NO_PRINCIPAL` | 401 | non-public route reached with `principal === null` |
| `RBAC.MISSING_SCOPE` | 403 | principal lacks the required scope |
| `RBAC.RULE_DENY` | 403 | `@suaveplan/rules` evaluation returned `passed === false`|
| `RBAC.SCOPE_CONFLICT` | 500 | two plugins claim the same scope name |
| `RBAC.INVALID_SPEC` | 500 | `RouteAuthSpec` failed Zod validation |
## License
MIT