first commit
This commit is contained in:
125
docs/api-stack/api/api-auth-static-principal/README.md
Normal file
125
docs/api-stack/api/api-auth-static-principal/README.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# @suaveplan/api-auth-static-principal — fixed-principal session resolver for @suaveplan/api: returns a configured Principal verbatim on every request. Used in plugin integration tests and the api-plugin-example fixture.
|
||||
|
||||
Fixed-`Principal` session resolver for the `@suaveplan/api` plugin
|
||||
system. Returns the configured `Principal` verbatim on every request —
|
||||
no token decoding, no network I/O, no internal state. Used in plugin
|
||||
integration tests and the `api-plugin-example` fixture, where the goal is to
|
||||
exercise the entire downstream pipeline (RBAC gate, tenant resolver chain
|
||||
reading `principal.tenantId`, tenant-scoped DB) with a deterministic
|
||||
identity carried in config.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [adapter](./src/adapter/adapter.md) — Bundles the static-principal resolver, the (absent) security-scheme contribution, and a pinned `csrf: false` into a sin…
|
||||
- [adapter](./src/adapter/adapter.md) — Bundles the static-principal resolver, the (absent) security-scheme contribution, and a pinned `csrf: false` into a sin…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the `AUTH_STATIC_PRINCIPAL.*` error-code namespace and the `AuthStaticPrincipalError` concrete subclass of `@su…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Defines the `AUTH_STATIC_PRINCIPAL.*` error-code namespace and the `AuthStaticPrincipalError` concrete subclass of `@su…
|
||||
- [factory](./src/factory/create-static-principal-resolver.md) — Constructs a `SessionResolver` from `@suaveplan/api-auth` that returns the configured `Principal` verbatim on every req…
|
||||
- [create-static-principal-resolver](./src/factory/create-static-principal-resolver.md) — Constructs a `SessionResolver` from `@suaveplan/api-auth` that returns the configured `Principal` verbatim on every req…
|
||||
- [options](./src/options/options.md) — Owns the runtime contract between the test harness call site and the resolver factory. The schema validates both the ou…
|
||||
- [options](./src/options/options.md) — Owns the runtime contract between the test harness call site and the resolver factory. The schema validates both the ou…
|
||||
- [security-scheme](./src/security-scheme/security-scheme.md) — Provides the per-adapter `SecuritySchemeContribution` slot of `AuthAdapter`. The static-principal adapter is a test-onl…
|
||||
- [security-scheme](./src/security-scheme/security-scheme.md) — Provides the per-adapter `SecuritySchemeContribution` slot of `AuthAdapter`. The static-principal adapter is a test-onl…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Why does this exist?
|
||||
|
||||
Per the API foundation design doc §13.2, the testing kit needs an auth
|
||||
adapter that supplies a fixed `Principal` without any token decoding. The
|
||||
§13.2 example uses it directly:
|
||||
|
||||
```ts
|
||||
adapters: {
|
||||
"auth.session": {
|
||||
kind: "static-principal",
|
||||
principal: { id: "u1", scopes: ["billing:write"], tenantId: "acme" },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Tests exercise the whole pipeline — RBAC, tenant resolution, tenant-scoped
|
||||
DB — with a deterministic principal in config. No mocks, no JWKS setup, no
|
||||
token signing.
|
||||
|
||||
## Surface
|
||||
|
||||
| Subpath | Export |
|
||||
| -------------- | --------------------------------------------------------------------------------------- |
|
||||
| `./resolver` | `createStaticPrincipalResolver(opts): SessionResolver` |
|
||||
| `./adapter` | `createStaticPrincipalAdapter(opts): AuthAdapter` |
|
||||
| `./error-codes`| `AUTH_STATIC_PRINCIPAL_CODES`, `AuthStaticPrincipalError`, `AuthStaticPrincipalErrorCode` |
|
||||
|
||||
The root barrel re-exports the resolver, adapter, error codes, options
|
||||
schema (`StaticPrincipalResolverOptionsSchema` /
|
||||
`parseStaticPrincipalResolverOptions`), and the empty
|
||||
`staticPrincipalSecurityScheme()` contribution.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { createStaticPrincipalAdapter } from "@suaveplan/api-auth-static-principal/adapter";
|
||||
|
||||
const adapter = createStaticPrincipalAdapter({
|
||||
principal: {
|
||||
id: "u1",
|
||||
scopes: ["billing:write"],
|
||||
tenantId: "acme",
|
||||
claims: { sub: "u1" },
|
||||
},
|
||||
});
|
||||
|
||||
host.registerAuth(adapter);
|
||||
// every request now produces `principal === { id: "u1", ... }`.
|
||||
```
|
||||
|
||||
Skip auth on selected paths to test `publicRoutes` behaviour without
|
||||
rewiring the adapter chain:
|
||||
|
||||
```ts
|
||||
const adapter = createStaticPrincipalAdapter({
|
||||
principal: { id: "u1", scopes: [], claims: {} },
|
||||
nullForPaths: ["/healthz", "/webhooks/*"],
|
||||
applyTo: (req) => !req.headers.has("x-skip-auth"),
|
||||
});
|
||||
```
|
||||
|
||||
## Conformance
|
||||
|
||||
The resolver passes `runAuthSessionConformance` from
|
||||
`@suaveplan/api-auth/testing` in seeded-principal mode. See
|
||||
`src/factory/create-static-principal-resolver.spec-conformance.test.ts` and
|
||||
`tests/conformance/api-auth.test.ts`.
|
||||
|
||||
## Errors
|
||||
|
||||
`AUTH_STATIC_PRINCIPAL.INVALID_OPTIONS` — outer options bag failed Zod
|
||||
validation (wrong shape, missing required fields, unknown top-level keys).
|
||||
|
||||
`AUTH_STATIC_PRINCIPAL.INVALID_PRINCIPAL` — the supplied `principal` field
|
||||
is malformed (missing `id`, non-string `id`, non-array `scopes`, unknown
|
||||
keys inside `principal`, …).
|
||||
|
||||
Both fire synchronously at construction time. The resolver itself never
|
||||
throws at request time.
|
||||
|
||||
## Stage-5 wiring (forward-looking)
|
||||
|
||||
When `api-host/testing` ships its Stage-5 `createTestHost` surface, this
|
||||
adapter is registered in the adapter table under
|
||||
`kind: "static-principal"`, so tests will declare it inline:
|
||||
|
||||
```ts
|
||||
const host = createTestHost({
|
||||
adapters: {
|
||||
"auth.session": {
|
||||
kind: "static-principal",
|
||||
principal: { id: "u1", scopes: ["billing:write"], tenantId: "acme" },
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Until then, callers wire the adapter directly via
|
||||
`createStaticPrincipalAdapter()` as shown above.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# `adapter` — composite `AuthAdapter` factory
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Bundles the static-principal resolver, the (absent) security-scheme
|
||||
contribution, and a pinned `csrf: false` into a single `AuthAdapter` value.
|
||||
This is the call-site shape for the canonical "test host with a known
|
||||
Principal" wiring — `host.registerAuth(createStaticPrincipalAdapter({...}))`
|
||||
gives the test the entire auth pipeline without further plumbing.
|
||||
|
||||
The factory delegates construction-time validation to
|
||||
`createStaticPrincipalResolver`, so misconfigured options surface as the
|
||||
same typed `AuthStaticPrincipalError` they do at the resolver level. The
|
||||
adapter holds no state of its own.
|
||||
|
||||
## Features
|
||||
|
||||
- Single-call construction of the resolver + scheme + csrf triple.
|
||||
- Pins `csrf: false` because the static-principal layer has no session
|
||||
cookie to forge against — every request carries the configured Principal
|
||||
by construction.
|
||||
- `securityScheme` omitted (matching `api-auth-anonymous` topology) so the
|
||||
generated OpenAPI document doesn't advertise a real auth flow for
|
||||
routes wired to this adapter.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createStaticPrincipalAdapter } from "@suaveplan/api-auth-static-principal/adapter";
|
||||
|
||||
const adapter = createStaticPrincipalAdapter({
|
||||
principal: {
|
||||
id: "u1",
|
||||
scopes: ["billing:write"],
|
||||
tenantId: "acme",
|
||||
claims: { sub: "u1" },
|
||||
},
|
||||
});
|
||||
|
||||
host.registerAuth(adapter);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Compose with `applyTo` / `nullForPaths` to skip auth on test-only routes:
|
||||
|
||||
```ts
|
||||
import { createStaticPrincipalAdapter } from "@suaveplan/api-auth-static-principal/adapter";
|
||||
|
||||
const adapter = createStaticPrincipalAdapter({
|
||||
principal: { id: "u1", scopes: [], claims: {} },
|
||||
nullForPaths: ["/healthz", "/webhooks/*"],
|
||||
});
|
||||
|
||||
// host.registerAuth(adapter) — /healthz and /webhooks/* resolve to null;
|
||||
// every other route gets the configured Principal.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Description |
|
||||
| ------------------------------------- | -------- | ---------------------------------------------------------- |
|
||||
| `createStaticPrincipalAdapter(opts)` | function | Returns `AuthAdapter` composed from the four building blocks. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`csrf: false` is the load-bearing pin: a static-principal host can't honour
|
||||
a real CSRF check (there's no session cookie carrying a synchronizer
|
||||
token), so the host should skip CSRF entirely for routes wired to this
|
||||
adapter. If a downstream test needs CSRF behaviour exercised in isolation,
|
||||
compose with a separate adapter that pins `csrf: true` and let the host's
|
||||
adapter-chain merge rule pick the safer value (the host's policy is to use
|
||||
the strictest `csrf` flag in the chain).
|
||||
|
||||
`validateAuthAdapter` accepts this adapter's shape (security scheme absent;
|
||||
csrf set to a boolean). The package's adapter test asserts the validation
|
||||
contract is satisfied without re-implementing the validator.
|
||||
@@ -0,0 +1,87 @@
|
||||
# `error-codes` — stable code registry for the static-principal adapter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the `AUTH_STATIC_PRINCIPAL.*` error-code namespace and the
|
||||
`AuthStaticPrincipalError` concrete subclass of
|
||||
`@suaveplan/error/SuaveplanError`. This module is the single source of truth
|
||||
for every failure code the package can surface to a caller — wiring this
|
||||
registry into the host's error envelope is how a malformed test-time
|
||||
Principal configuration reaches the developer rather than silently producing a
|
||||
broken auth chain.
|
||||
|
||||
Codes are produced via `defineCodes("AUTH_STATIC_PRINCIPAL", [...])` so the
|
||||
full literal value is e.g. `"AUTH_STATIC_PRINCIPAL.INVALID_OPTIONS"`. The
|
||||
class augments the base error with `authStaticPrincipalCode`, providing a
|
||||
narrowed code property when consumers have already discriminated by
|
||||
`instanceof`.
|
||||
|
||||
## Features
|
||||
|
||||
- Stable, frozen `AUTH_STATIC_PRINCIPAL_CODES` map built via `defineCodes` so
|
||||
the prefix can't drift.
|
||||
- Discriminated-union friendly: `AuthStaticPrincipalErrorCode` is a literal
|
||||
union over the registry entries.
|
||||
- Carries arbitrary `context` (Zod issues, the offending field path) via the
|
||||
base `SuaveplanError` options bag.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
AUTH_STATIC_PRINCIPAL_CODES,
|
||||
AuthStaticPrincipalError,
|
||||
} from "@suaveplan/api-auth-static-principal/error-codes";
|
||||
|
||||
throw new AuthStaticPrincipalError(
|
||||
AUTH_STATIC_PRINCIPAL_CODES.INVALID_OPTIONS,
|
||||
"createStaticPrincipalResolver: options failed schema validation"
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Attach Zod issues so the caller can pinpoint the bad field without re-running
|
||||
validation:
|
||||
|
||||
```ts
|
||||
import {
|
||||
AUTH_STATIC_PRINCIPAL_CODES,
|
||||
AuthStaticPrincipalError,
|
||||
} from "@suaveplan/api-auth-static-principal/error-codes";
|
||||
import z from "zod";
|
||||
|
||||
const parsed = OptionsSchema.safeParse(input);
|
||||
if (!parsed.success) {
|
||||
throw new AuthStaticPrincipalError(
|
||||
AUTH_STATIC_PRINCIPAL_CODES.INVALID_PRINCIPAL,
|
||||
"principal failed schema validation",
|
||||
{ context: { issues: parsed.error.issues } }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Description |
|
||||
| ----------------------------------- | -------- | ------------------------------------------------------------------------ |
|
||||
| `AUTH_STATIC_PRINCIPAL_CODES` | constant | Frozen map of code constants. Keys are the short names; values are full. |
|
||||
| `AuthStaticPrincipalErrorCode` | type | Literal union over the registry values. |
|
||||
| `AuthStaticPrincipalError` | class | `SuaveplanError` subclass typed against the code registry. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`defineCodes` joins the prefix with each suffix using a single `.` so the
|
||||
resulting strings match the host's structured-log code field. The class
|
||||
stores the code twice — once in the inherited `code` slot and once in the
|
||||
narrower `authStaticPrincipalCode` slot — so callers that already narrowed by
|
||||
`instanceof` get a precisely-typed code field without a follow-up `as`.
|
||||
|
||||
The package's static-principal resolver never throws at request time; both
|
||||
codes are construction-time only. This is deliberate — every per-request
|
||||
failure for fixed-principal tests should manifest in the test assertion, not
|
||||
in the auth pipeline.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,82 @@
|
||||
# `options` — Zod-validated options for `createStaticPrincipalResolver`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Owns the runtime contract between the test harness call site and the
|
||||
resolver factory. The schema validates both the outer options bag
|
||||
(`{ principal, nullForPaths?, applyTo? }`) and the nested `Principal` shape
|
||||
(non-empty `id`, readonly `scopes`, optional `tenantId`, default-`{}`
|
||||
`claims`). On failure it raises a typed `AuthStaticPrincipalError` carrying
|
||||
the originating Zod issues — splitting between `INVALID_OPTIONS` (anything
|
||||
outside `principal`) and `INVALID_PRINCIPAL` (anything inside it).
|
||||
|
||||
This module is the only place the package touches Zod at runtime. Keeping it
|
||||
isolated lets the resolver factory stay a thin three-line function and lets
|
||||
the conformance test target the resolver behaviour without invoking schema
|
||||
side-effects.
|
||||
|
||||
## Features
|
||||
|
||||
- Strict outer schema — unknown keys at the top level reject with
|
||||
`INVALID_OPTIONS`.
|
||||
- Discriminated error codes: `principal` issues map to `INVALID_PRINCIPAL`;
|
||||
everything else maps to `INVALID_OPTIONS`.
|
||||
- Pre-flight type check rejects non-object inputs with `INVALID_OPTIONS`
|
||||
before Zod sees them — a cleaner message than the Zod default for `null` /
|
||||
arrays.
|
||||
- `claims` defaults to `{}` so callers don't have to write
|
||||
`claims: {}` for the common case.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { parseStaticPrincipalResolverOptions } from "@suaveplan/api-auth-static-principal/resolver";
|
||||
|
||||
const opts = parseStaticPrincipalResolverOptions({
|
||||
principal: {
|
||||
id: "u_1",
|
||||
scopes: ["billing:write"],
|
||||
tenantId: "acme",
|
||||
claims: { email: "user@acme.test" },
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Validate options that include both `applyTo` and `nullForPaths`:
|
||||
|
||||
```ts
|
||||
import { parseStaticPrincipalResolverOptions } from "@suaveplan/api-auth-static-principal/resolver";
|
||||
|
||||
const opts = parseStaticPrincipalResolverOptions({
|
||||
principal: { id: "u_1", scopes: [], claims: {} },
|
||||
nullForPaths: ["/webhooks/*", "/healthz"],
|
||||
applyTo: (req) => !req.headers.has("x-skip-auth"),
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Description |
|
||||
| -------------------------------------------- | -------- | ----------------------------------------------------------------- |
|
||||
| `StaticPrincipalResolverOptionsSchema` | Zod | Strict object schema for the outer bag. |
|
||||
| `parseStaticPrincipalResolverOptions(raw)` | function | Returns the validated options or throws `AuthStaticPrincipalError`. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The pre-flight `typeof`/`Array.isArray` check lets the function return a
|
||||
single canonical error for "you handed me something that isn't an object" —
|
||||
Zod's own behaviour on `null` is to emit
|
||||
`{ code: "invalid_type", expected: "object" }`, which is correct but verbose.
|
||||
Splitting into a pre-flight + Zod pass is one extra branch but keeps the
|
||||
caller-facing failure message stable.
|
||||
|
||||
`claims` is recorded as `z.record(z.unknown())` so any free-form JWT/claims
|
||||
payload validates. Coercing to readonly happens at the TypeScript boundary
|
||||
(`StaticPrincipalResolverOptions`), not in Zod — Zod's `.readonly()` is
|
||||
type-level only and would re-wrap arrays at runtime unnecessarily.
|
||||
@@ -0,0 +1,75 @@
|
||||
# `security-scheme` — OpenAPI contribution for the static-principal adapter
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides the per-adapter `SecuritySchemeContribution` slot of
|
||||
`AuthAdapter`. The static-principal adapter is a test-only construct: there
|
||||
is no real authentication flow to document on routes that use it. This
|
||||
module returns `undefined` deliberately — which `validateAuthAdapter` from
|
||||
`@suaveplan/api-auth` permits — so the generated OpenAPI document emits no
|
||||
security entry for routes under this adapter, mirroring how the anonymous
|
||||
tail adapter is treated.
|
||||
|
||||
Wrapping the absence in a function gives the package a symmetric surface
|
||||
with adapters that compute their contribution dynamically, and gives the
|
||||
test suite a callable to target for the "intentionally absent" assertion.
|
||||
|
||||
## Features
|
||||
|
||||
- Returns `undefined` so the host omits a `security` entry on routes wired
|
||||
to this adapter.
|
||||
- Permitted by `validateAuthAdapter` (the anonymous-style topology).
|
||||
- Callable surface for symmetry with adapters that compute their scheme.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { staticPrincipalSecurityScheme } from "@suaveplan/api-auth-static-principal/adapter";
|
||||
import type { AuthAdapter } from "@suaveplan/api-auth";
|
||||
|
||||
const adapter: AuthAdapter = {
|
||||
resolver,
|
||||
securityScheme: staticPrincipalSecurityScheme(),
|
||||
csrf: false,
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
When composing static-principal with another adapter that supplies a real
|
||||
scheme, drop this contribution and use the other adapter's scheme in the
|
||||
composite — the host requires only one to be present per route. The
|
||||
function still acts as documentation that the static-principal layer
|
||||
contributes nothing of its own:
|
||||
|
||||
```ts
|
||||
import { staticPrincipalSecurityScheme } from "@suaveplan/api-auth-static-principal/adapter";
|
||||
|
||||
if (staticPrincipalSecurityScheme() !== undefined) {
|
||||
// unreachable in this version — the function is documented to return
|
||||
// undefined. Tests assert this invariant.
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Description |
|
||||
| ----------------------------------- | -------- | ------------------------------------------------ |
|
||||
| `staticPrincipalSecurityScheme()` | function | Returns `undefined` — no OpenAPI contribution. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`validateAuthAdapter` treats `securityScheme: undefined` as a permitted
|
||||
topology (the "no auth configured" path used by `api-auth-anonymous`). The
|
||||
generated OpenAPI document omits a `security` array on routes wired to an
|
||||
adapter that omits the scheme; combined with the host's `publicRoutes`
|
||||
decision, this matches the runtime behaviour of the static-principal
|
||||
adapter in tests.
|
||||
|
||||
Changing this to return a real scheme in a future version would be a
|
||||
breaking change for downstream consumers' OpenAPI snapshots; treat the
|
||||
"returns undefined" behaviour as a load-bearing contract.
|
||||
Reference in New Issue
Block a user