first commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# mTLS gate — `@suaveplan/api-auth-bearer-static`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The mTLS gate enforces an optional second factor on individual token table
|
||||
entries. When operators mark an entry with `mtlsRequired: true`, the
|
||||
resolver MUST refuse to surface the entry's Principal unless the host
|
||||
listener attached a verified peer certificate to the inbound request via
|
||||
`request.tls.peerCertificate`. This guards the highest-privilege service
|
||||
tokens (e.g. internal admin API) against credential leakage — the bearer
|
||||
token alone is necessary but no longer sufficient.
|
||||
|
||||
The adapter does NOT establish TLS itself. The host listener (Bun's
|
||||
TLS-enabled server, Node's `https`, a Cloudflare-Worker TLS terminator
|
||||
ahead of the worker, etc.) is responsible for completing the TLS handshake
|
||||
and surfacing the verified subject as `request.tls.peerCertificate`. This
|
||||
module just inspects what was attached.
|
||||
|
||||
## Features
|
||||
|
||||
- Pure function — no I/O, no state.
|
||||
- Defensive — accepts any `Request`-shaped input even when the `tls` field
|
||||
was not attached (returns `undefined`).
|
||||
- Treats `peerCertificate: null` identically to absent.
|
||||
- `mtlsRequired === false` short-circuits without inspecting the request.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { checkMtlsGate } from "@suaveplan/api-auth-bearer-static/mtls";
|
||||
|
||||
const request = new Request("https://example/test");
|
||||
const allowed = checkMtlsGate(request, true);
|
||||
// allowed === false — no peerCertificate attached
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
checkMtlsGate,
|
||||
extractPeerCertificate,
|
||||
} from "@suaveplan/api-auth-bearer-static/mtls";
|
||||
|
||||
// Host listener pattern — wrap the inbound request before handing off to
|
||||
// the auth chain.
|
||||
const withTls = Object.assign(request, {
|
||||
tls: { peerCertificate: parsedX509Subject },
|
||||
});
|
||||
|
||||
const cert = extractPeerCertificate(withTls);
|
||||
// cert === parsedX509Subject
|
||||
|
||||
const allowed = checkMtlsGate(withTls, true);
|
||||
// allowed === true
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface RequestTlsInfo { peerCertificate?: unknown }`
|
||||
- `extractPeerCertificate(request: Request): unknown | undefined`
|
||||
- `checkMtlsGate(request: Request, mtlsRequired: boolean): boolean`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `Request` Web type has no standardised `tls` field. Both Node's
|
||||
undici-based `Request` and Bun's polyfill ignore unknown properties on
|
||||
construction, so the host pattern is to attach `tls` as a plain property
|
||||
after the Request is built (or to wrap it in a Proxy / subclass). The
|
||||
extractor reads through that property with a single defensive type cast
|
||||
and returns `undefined` for any shape that does not satisfy the
|
||||
`{ peerCertificate: <truthy> }` invariant. The opaque certificate itself
|
||||
is not parsed here — that is the host's job, and a future
|
||||
`api-auth-mtls` adapter (out of scope for Stage 4) will own subject
|
||||
extraction.
|
||||
Reference in New Issue
Block a user