2.8 KiB
mTLS gate — @suaveplan/api-auth-bearer-static
(no submodules)
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 thetlsfield was not attached (returnsundefined). - Treats
peerCertificate: nullidentically to absent. mtlsRequired === falseshort-circuits without inspecting the request.
Usage: Basic
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
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 | undefinedcheckMtlsGate(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.