Files
suaveplan/openspec/archive/2026-05-26-api-auth-bearer-static/specs/api-auth-bearer-static/spec.md
2026-05-27 23:29:53 +01:00

3.7 KiB

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"