first commit
This commit is contained in:
90
docs/api-stack/api/api-auth/src/csrf-policy/csrf-policy.md
Normal file
90
docs/api-stack/api/api-auth/src/csrf-policy/csrf-policy.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# `csrf-policy` — `CsrfPolicy.requiresCsrf`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Exposes a single pure predicate, `CsrfPolicy.requiresCsrf(scheme)`, that
|
||||
defaults CSRF protection ON for any cookie-based scheme and OFF for any scheme
|
||||
where the caller must actively attach the credential. This default codifies the
|
||||
design-doc §6 rule "Cookie-based auth adapters trigger CSRF verification" so
|
||||
every concrete adapter inherits the right behaviour without restating it.
|
||||
|
||||
The package exposes only the default; the host (`api-host`) applies per-route
|
||||
`csrf: false` overrides on top. Keeping the default and the override separate
|
||||
means a route-level opt-out is always auditable as an explicit decision in the
|
||||
manifest, never a side-effect of which adapter happened to be wired.
|
||||
|
||||
## Features
|
||||
|
||||
- Pure function — no side effects, no module-load cost.
|
||||
- Frozen container object so test code can `expect(CsrfPolicy)` reliably and
|
||||
consumers cannot monkey-patch the predicate.
|
||||
- Total over the `OpenApiSecurityScheme` discriminated union — no `default`
|
||||
branch means an added scheme type at the schema layer will surface a
|
||||
type-level reminder to revisit the policy.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { CsrfPolicy } from "@suaveplan/api-auth/csrf-policy";
|
||||
|
||||
const needsCsrf = CsrfPolicy.requiresCsrf({
|
||||
type: "apiKey",
|
||||
in: "cookie",
|
||||
name: "sp_session",
|
||||
}); // → true
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Drive a host-side CSRF middleware off the policy, with a per-route override
|
||||
applied at the call site:
|
||||
|
||||
```ts
|
||||
import { CsrfPolicy } from "@suaveplan/api-auth/csrf-policy";
|
||||
import type { OpenApiSecurityScheme } from "@suaveplan/api-auth/security-scheme";
|
||||
|
||||
interface RouteCsrfDecision {
|
||||
readonly required: boolean;
|
||||
readonly reason: "scheme-default" | "route-override";
|
||||
}
|
||||
|
||||
function decideCsrf(
|
||||
scheme: OpenApiSecurityScheme,
|
||||
routeOverride: boolean | undefined,
|
||||
): RouteCsrfDecision {
|
||||
if (routeOverride !== undefined) {
|
||||
return { required: routeOverride, reason: "route-override" };
|
||||
}
|
||||
return {
|
||||
required: CsrfPolicy.requiresCsrf(scheme),
|
||||
reason: "scheme-default",
|
||||
};
|
||||
}
|
||||
|
||||
const decision = decideCsrf(
|
||||
{ type: "http", scheme: "bearer" },
|
||||
undefined,
|
||||
);
|
||||
// → { required: false, reason: "scheme-default" }
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Description |
|
||||
|----------------------------|-------------|----------------------------------------------------------|
|
||||
| `CsrfPolicy.requiresCsrf` | function | `(scheme) => boolean`. Cookie schemes → `true`. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The predicate inspects `type === "apiKey" && in === "cookie"` — apiKey-in-
|
||||
header is treated identically to `Authorization: Bearer …` for CSRF purposes
|
||||
because the caller must attach it explicitly.
|
||||
- `oauth2` and `openIdConnect` default OFF — both are bearer-token-derived in
|
||||
practice; if a deployment exposes an oauth2 access token via cookie, that's
|
||||
a custom apiKey-in-cookie scheme, not the `oauth2` scheme.
|
||||
- `Object.freeze` guards the predicate slot; the predicate itself is a regular
|
||||
method and TypeScript prevents reassignment via the `Readonly` inference.
|
||||
Reference in New Issue
Block a user