Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user