Documentation
¶
Overview ¶
Package auth is wowapi's authentication kernel: it verifies OIDC/JWT bearer tokens against an injectable KeySource (JWKS-over-HTTPS in production, a local signer in tests) and maps validated claims onto an authz.Actor after the app resolves the framework user id and active capacity (D-0037, 01 §3). The Authenticator type adapts a Verifier to the framework's structural httpx.Authenticator so it can be the user leg of a product's composite.
Two properties are structural, not configurable:
- asymmetric signatures only: the verifier asserts the token's signing method is RSA or ECDSA (RS256/ES256) before touching the key, so "alg":"none" and HMAC tokens are rejected outright (algorithm-confusion defense);
- opaque failures: every bad/missing/expired/wrong-issuer/wrong-audience/ unknown-kid/bad-signature case returns errors.E(KindUnauthenticated, ...) and never echoes the token or key material. A KeySource transport fault (unreachable JWKS) surfaces as a KindExternal HARD error, not a 401, so a composite authenticator does not mask a transient outage as a clean reject.
Import law (blueprint 04 §1; boundary lint): this package imports only stdlib, kernel/errors, kernel/authz, google/uuid and the jwt library — never module, app, adapters or testkit. The DB-backed user/capacity lookup is injected as the PrincipalStore port, wired by the app.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Authenticator ¶
type Authenticator struct {
// contains filtered or unexported fields
}
Authenticator adapts a Verifier to the framework's structural httpx.Authenticator: it reads an "Authorization: Bearer <jwt>" header, verifies the token, and maps its claims to an authz.Actor via the PrincipalStore. It is the OIDC/JWT user leg of a product's composite authenticator (roadmap S1/CA-2). It satisfies httpx.Authenticator structurally — kernel/auth never imports kernel/httpx (import law).
Decline vs. fault: a missing bearer token or a non-JWT token (e.g. an API key) yields KindUnauthenticated so a composite falls through to the next scheme; a JWKS/transport fault propagates as a hard error so it is not masked as a 401.
func NewAuthenticator ¶
func NewAuthenticator(v *Verifier, ps PrincipalStore) *Authenticator
NewAuthenticator builds the OIDC/JWT authenticator over a Verifier and the app-supplied PrincipalStore (subject → framework user id + capacity check).
func (*Authenticator) Authenticate ¶
Authenticate resolves the user actor from the request's bearer JWT. It declines (KindUnauthenticated) when no bearer token is present.
type Claims ¶
type Claims struct {
jwt.RegisteredClaims
TenantID uuid.UUID `json:"tenant_id"`
CapacityID uuid.UUID `json:"capacity_id,omitempty"`
ImpersonatorUserID uuid.UUID `json:"impersonator_user_id,omitempty"`
BreakGlass bool `json:"break_glass,omitempty"`
// AMR is the standard authentication-methods-references claim (RFC 8176,
// e.g. ["pwd","mfa"]) surfaced by the IdP. Verifier.Actor propagates it to
// authz.Actor.AMR, which drives step-up (MFA) enforcement (roadmap S3). A
// malformed amr in the token (wrong JSON shape) fails the claims decode in
// Verify, so Actor never sees a token whose amr could not be parsed.
AMR []string `json:"amr,omitempty"`
}
Claims carries the wowapi-specific token payload alongside the standard registered claims. Subject (sub) maps to a user's idp_subject; TenantID and the optional CapacityID/ImpersonatorUserID/BreakGlass drive the authz.Actor.
type Config ¶
type Config struct {
Issuer string // expected iss
Audience string // expected aud
Leeway time.Duration // clock-skew tolerance (default 30s)
}
Config parameterizes a Verifier.
type JWKSConfig ¶
type JWKSConfig struct {
// Issuer is the token issuer (the iss claim). When JWKSURI is empty it is
// used for OIDC discovery at <issuer>/.well-known/openid-configuration.
Issuer string
// JWKSURI is the explicit JWKS endpoint. When empty it is discovered from
// Issuer. It must be https (loopback http is permitted for tests/local IdPs).
JWKSURI string
// TTL bounds how long fetched keys are cached before a refetch (default 15m).
TTL time.Duration
// Client is the HTTP client used to fetch discovery/JWKS documents. A nil
// client defaults to one with a 10s timeout.
Client *http.Client
// Now is the clock, injectable for tests (default time.Now). It drives the
// cache TTL and the rotation-refetch throttle.
Now func() time.Time
}
JWKSConfig parameterizes a JWKS-over-HTTPS KeySource.
type KeySource ¶
type KeySource interface {
// Key returns the verification key (e.g. *rsa.PublicKey) for the given kid.
Key(ctx context.Context, kid string) (any, error)
}
KeySource resolves a token-verification key by its key id (kid). Production wires a caching JWKS-over-HTTPS adapter; tests wire a static in-memory source.
func NewJWKSKeySource ¶
func NewJWKSKeySource(cfg JWKSConfig) (KeySource, error)
NewJWKSKeySource builds a caching JWKS-over-HTTPS KeySource. It requires an Issuer (for discovery) or an explicit JWKSURI; a non-loopback URL must be https. Returns an error only for static misconfiguration; network fetches happen lazily on the first Key call.
func NewStaticKeySource ¶
NewStaticKeySource returns a KeySource that resolves kids from an in-memory map. The map is copied so later mutation by the caller cannot affect it.
type PrincipalStore ¶
type PrincipalStore interface {
// UserIDBySubject returns the framework user id for an IdP subject.
UserIDBySubject(ctx context.Context, subject string) (uuid.UUID, error)
// ValidateCapacity returns a non-nil error if capacityID is not an active
// capacity of userID in tenantID.
ValidateCapacity(ctx context.Context, userID, tenantID, capacityID uuid.UUID) error
}
PrincipalStore resolves the framework user id from the IdP subject and confirms the capacity belongs to that user in the tenant. Implemented in the app/adapters DB layer (kernel/auth may not import a database).
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier parses and validates bearer tokens and maps their claims to actors.
func NewVerifier ¶
NewVerifier builds a Verifier over keys and cfg. A zero cfg.Leeway defaults to 30s.
func (*Verifier) Actor ¶
func (v *Verifier) Actor(ctx context.Context, claims Claims, ps PrincipalStore) (authz.Actor, error)
Actor maps validated Claims onto an authz.Actor. It resolves the framework user id from the subject via ps (an unknown subject → KindUnauthenticated) and, when a capacity is present, confirms it belongs to that user in the tenant (a mismatch → KindForbidden). Impersonation and break-glass carry through.
func (*Verifier) Verify ¶
Verify parses and validates an RS256 bearer token: it asserts the signing method is RSA (rejecting "alg":"none"/HMAC), resolves the key via KeySource by the token's kid header, and checks iss/aud/exp/nbf with the configured leeway. On success it returns the validated Claims; every failure mode returns errors.E(KindUnauthenticated, ...) with no token or key material in the error.