Files
2026-05-27 23:29:53 +01:00

81 lines
3.7 KiB
Markdown

<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: api-auth-bearer-static (api-auth-bearer-static)
**Package**: `@suaveplan/api-auth-bearer-static`
**Status**: PROPOSED
## ADDED Requirements
### Requirement: Static token table resolves to Principal
The package SHALL match incoming bearer tokens against a fixed table supplied at construction time. A matching token produces the associated `Principal`; an unknown token produces `null`. Token comparison MUST be constant-time so an attacker cannot probe valid token prefixes via timing.
#### Scenario: known token returns its Principal
- **WHEN** a request carries `Authorization: Bearer T1` AND the table contains `{ token: "T1", principal: P }`
- **THEN** `resolveSession()` returns `P`
#### Scenario: unknown token returns null
- **WHEN** a request carries `Authorization: Bearer NOT_IN_TABLE`
- **THEN** `resolveSession()` returns `null` AND emits `AUTH_BEARER_STATIC_UNKNOWN_TOKEN` on the failure counter
#### Scenario: token match is constant-time
- **WHEN** two unknown tokens of equal length are compared against the table
- **THEN** the comparison loop runs to completion for both (no early exit on first mismatched byte)
**Verified by:** `src/factory/create-bearer-static-resolver.test.ts::"known token returns its Principal"` AND `src/factory/create-bearer-static-resolver.test.ts::"unknown token returns null and increments failure counter"` AND `src/constant-time/token-match.test.ts::"comparison runs to completion regardless of mismatch position"`
---
### Requirement: Optional mutual-TLS gate
A token entry MAY carry `mtlsRequired: true`. When set, the resolver MUST require the request to carry a verified peer certificate (surfaced by the host listener); absence MUST produce `null` and emit `AUTH_BEARER_STATIC_MTLS_REQUIRED`.
#### Scenario: mTLS-gated token rejects requests without peer cert
- **WHEN** an mTLS-gated token is presented but `request.tls.peerCertificate` is undefined
- **THEN** `resolveSession()` returns `null` AND emits `AUTH_BEARER_STATIC_MTLS_REQUIRED`
#### Scenario: mTLS-gated token accepts requests with peer cert
- **WHEN** the same token is presented AND `request.tls.peerCertificate` is present
- **THEN** `resolveSession()` returns the Principal
**Verified by:** `src/mtls/mtls-gate.test.ts::"rejects mtls-required token without peer certificate"` AND `src/mtls/mtls-gate.test.ts::"accepts mtls-required token with peer certificate"`
---
### Requirement: Direct and worker-proxy surfaces both pass conformance
The adapter SHALL pass `runAuthSessionConformance` under both surfaces.
#### Scenario: direct surface conforms
- **WHEN** `runAuthSessionConformance("bearer-static-direct", () => createBearerStaticResolver(...))` runs
- **THEN** every invariant passes
#### Scenario: worker-proxy surface conforms
- **WHEN** `runAuthSessionConformance("bearer-static-worker-proxy", ...)` runs over a real `MessageChannel`
- **THEN** every invariant passes
**Verified by:** `src/factory/create-bearer-static-resolver.spec-conformance.test.ts::"bearer-static-direct conforms to AuthSession contract"` AND `src/proxy/create-bearer-static-resolver-worker-proxy.spec-conformance.test.ts::"bearer-static-worker-proxy conforms to AuthSession contract"`
---
### Requirement: Options Zod-validated
`BearerStaticResolverOptions` MUST be Zod-validated. Empty `tokens` array OR a non-string token entry MUST throw `AUTH_BEARER_STATIC_INVALID_OPTIONS` at factory construction.
#### Scenario: empty tokens array throws
- **WHEN** `createBearerStaticResolver({ tokens: [] })`
- **THEN** a `SuaveplanError` with code `AUTH_BEARER_STATIC_INVALID_OPTIONS` is thrown synchronously
**Verified by:** `src/options/bearer-static-resolver-options.test.ts::"throws on empty tokens array"`
<!-- markdownlint-enable MD024 -->