docs(httpauth-firebase): fix rbac tier reference from 1 to 0
rbac is a Tier 0 module (no micro-lib dependencies). The dependency line incorrectly cited it as Tier 1. The module's own tier (4) is unchanged — it remains the auth layer above the transport infrastructure.
This commit is contained in:
55
docs/adr/ADR-002-rbac-identity-output-contract.md
Normal file
55
docs/adr/ADR-002-rbac-identity-output-contract.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# ADR-002: rbac.Identity as the Output Contract
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
`AuthMiddleware` verifies a Firebase JWT and extracts a UID and claims map from the
|
||||
decoded token. Downstream code needs a richer identity: application-specific role,
|
||||
tenant, display name, email. Several design options exist:
|
||||
|
||||
1. Expose Firebase token fields (`auth.Token`) directly in context — ties all
|
||||
downstream code to Firebase types.
|
||||
2. Define a custom identity struct in `httpauth-firebase` — decouples from Firebase
|
||||
but creates a module-specific contract that other `httpauth-*` modules cannot share.
|
||||
3. Use `rbac.Identity` as the shared identity type — all `httpauth-*` modules
|
||||
produce the same type; downstream code and `AuthzMiddleware` depend on `rbac`
|
||||
only, not on any Firebase types.
|
||||
|
||||
## Decision
|
||||
|
||||
`EnrichmentMiddleware` calls `rbac.SetInContext(ctx, identity)` to store the
|
||||
enriched identity. `AuthzMiddleware` reads it with `rbac.FromContext(ctx)`.
|
||||
Downstream business logic and service handlers use `rbac.FromContext` directly —
|
||||
they never import `httpauth-firebase`.
|
||||
|
||||
The flow is:
|
||||
|
||||
1. `AuthMiddleware` (this module): verifies Firebase JWT → stores `uid` + `claims`
|
||||
in context under unexported keys local to this package.
|
||||
2. `EnrichmentMiddleware` (this module): reads `uid` + `claims` → calls
|
||||
`IdentityEnricher.Enrich` → stores `rbac.Identity` via `rbac.SetInContext`.
|
||||
3. `AuthzMiddleware` (this module): reads `rbac.Identity` via `rbac.FromContext` →
|
||||
calls `PermissionProvider.ResolveMask` → allows or rejects.
|
||||
|
||||
The intermediate `uid` + `claims` context values are stored under unexported typed
|
||||
keys (`ctxUIDKey{}`, `ctxClaimsKey{}`). They are internal to `httpauth-firebase`
|
||||
and not part of the public API.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Business logic and service layers only need to import `rbac` to read the caller's
|
||||
identity. They have no knowledge of Firebase, JWTs, or token claims.
|
||||
- Switching from Firebase to another provider (e.g. Auth0) requires replacing the
|
||||
`AuthMiddleware` module. `EnrichmentMiddleware`, `AuthzMiddleware`, and all
|
||||
downstream code remain unchanged because they operate on `rbac.Identity`.
|
||||
- `IdentityEnricher` is the application's extension point: it receives the Firebase
|
||||
UID and raw claims and returns a fully populated `rbac.Identity` with role and
|
||||
tenant. This is the only place where app-specific user store queries should occur.
|
||||
- `rbac.Identity.WithTenant(tenantID)` is called in `EnrichmentMiddleware` if a
|
||||
tenant header is configured. The base identity from `Enrich` is immutable; a new
|
||||
value is returned.
|
||||
- Global ADR-003 (context helpers live with data owners) is applied here: `rbac`
|
||||
owns `SetInContext` and `FromContext` because `rbac.Identity` is a RBAC concern,
|
||||
not an auth transport concern.
|
||||
Reference in New Issue
Block a user