first commit
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# `factory/create-static-principal-resolver` — fixed-Principal resolver
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Constructs a `SessionResolver` from `@suaveplan/api-auth` that returns the
|
||||
configured `Principal` verbatim on every request — no token decoding, no
|
||||
JWKS, no I/O. This is the workhorse adapter for plugin integration tests,
|
||||
the `api-plugin-example` fixture, and any environment where the goal is to
|
||||
exercise the downstream pipeline (RBAC gate, tenant resolver chain reading
|
||||
`principal.tenantId`, tenant-scoped DB) with a deterministic identity carried
|
||||
in config.
|
||||
|
||||
The factory consumes options previously validated by the `options` module so
|
||||
construction is total: every misuse maps to either
|
||||
`AUTH_STATIC_PRINCIPAL.INVALID_OPTIONS` or `AUTH_STATIC_PRINCIPAL.INVALID_PRINCIPAL`
|
||||
and surfaces synchronously at construction time. The resolver itself never
|
||||
throws at request time.
|
||||
|
||||
## Features
|
||||
|
||||
- Defensive clone on every `resolve()` — callers cannot mutate one returned
|
||||
Principal and observe the change on subsequent calls.
|
||||
- `applyTo(req)` predicate for arbitrary "return null for this request"
|
||||
rules.
|
||||
- `nullForPaths` suffix-glob list (`/webhooks/*`) and exact-match patterns
|
||||
for the most common public-route test setup.
|
||||
- Idempotent no-op `dispose()` matching the `SessionResolver` contract.
|
||||
- Returned objects are frozen (top-level + `scopes` + `claims`) to make
|
||||
attempted mutations fail loudly in strict mode.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createStaticPrincipalResolver } from "@suaveplan/api-auth-static-principal/resolver";
|
||||
|
||||
const resolver = createStaticPrincipalResolver({
|
||||
principal: {
|
||||
id: "u1",
|
||||
scopes: ["billing:write"],
|
||||
tenantId: "acme",
|
||||
claims: { sub: "u1" },
|
||||
},
|
||||
});
|
||||
|
||||
const principal = await resolver.resolve(
|
||||
new Request("https://example.test/api/billing/invoices")
|
||||
);
|
||||
// principal.id === "u1"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Combine `applyTo` and `nullForPaths` to model a mixed-route test surface:
|
||||
|
||||
```ts
|
||||
import { createStaticPrincipalResolver } from "@suaveplan/api-auth-static-principal/resolver";
|
||||
|
||||
const resolver = createStaticPrincipalResolver({
|
||||
principal: {
|
||||
id: "u1",
|
||||
scopes: ["billing:write", "billing:read"],
|
||||
tenantId: "acme",
|
||||
claims: { sub: "u1" },
|
||||
},
|
||||
nullForPaths: ["/webhooks/*", "/healthz"],
|
||||
applyTo: (req) => !req.headers.has("x-skip-auth"),
|
||||
});
|
||||
|
||||
// /healthz and /webhooks/stripe resolve to null.
|
||||
// Requests with the `x-skip-auth` header resolve to null.
|
||||
// Everything else returns the configured Principal.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Description |
|
||||
| ----------------------------------- | -------- | ------------------------------------------------------------------ |
|
||||
| `createStaticPrincipalResolver(o)` | function | Returns a `SessionResolver` carrying `o.principal` on every match. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The clone strategy is: deep-freeze a single template at construction, then
|
||||
clone-from-template per `resolve()`. The per-call clone is unavoidable —
|
||||
otherwise two callers each freezing the returned object would freeze each
|
||||
other's state. The per-call clone copies the top level plus `scopes` (array
|
||||
spread) and `claims` (object spread); deeper structures inside `claims` are
|
||||
not cloned. This matches the `Principal` contract: `claims` values are
|
||||
`unknown`, so the package can't responsibly deep-clone them. Callers passing
|
||||
mutable nested claims values are responsible for not mutating them in-place.
|
||||
|
||||
`pathMatches` accepts only exact-match and a single suffix glob (`/foo/*`)
|
||||
on purpose — richer matching belongs in `applyTo`. The trailing slash is
|
||||
preserved when stripping the `*` so `/foo/*` matches `/foo/x` but not
|
||||
`/foox`. The pattern dialect is documented on the spec and exercised by the
|
||||
test file.
|
||||
Reference in New Issue
Block a user