feat(auth): initial implementation — authmw and rbac (v1.0.0)
Introduces code.nochebuena.dev/einherjar/auth — the provider-agnostic HTTP
authentication and authorization layer of the Einherjar framework. Absorbs
two micro-lib packages (httpauth, rbac) as sub-packages, replacing the
Identity-only context model with a SecurityBag-native design and adding a
composable enrichment chain.
authmw:
- BagEnricher function type — enriches the request-scoped SecurityBag after
the base Identity is built; registered via WithBagEnricher; multiple
enrichers run in order, each receiving the bag from the previous
- IdentityEnricher interface — application-layer contract for loading user
data from uid+claims
- EnrichmentMiddleware — builds SecurityBag from uid+claims, runs enricher
chain, stores via security.SetBagInContext; 401 on missing uid, 500 on
enricher error; routes all errors through httputil.Error
- AuthzMiddleware — per-route permission gate; 401 on missing identity,
403 on provider error (fail-closed) or insufficient permissions
- EnrichOpt type + WithTenantHeader (reads TenantID from header, implemented
as a BagEnricher) + WithBagEnricher (registers custom enrichers for
hardware IDs, grant codes, or any bag attribute)
- SetTokenData / GetClaims — integration contract for auth-jwt / auth-firebase
rbac:
- NewClaimsPermissionProvider — reads flat JWT claim bitmasks from context;
wildcard "*" fallback; handles int64/float64/json.Number; zero DB calls
- NewCachedPermissionProvider — TTL cache wrapping any PermissionProvider;
default key "rbac:{uid}:{resource}" or "rbac:{tenantID}:{uid}:{resource}";
TenantID sourced from SecurityBag automatically; accepts ...CachedOpt
- CachedOpt type + WithCacheKey — overrides the key function for extra
dimensions (hardware IDs, grant codes read from bag attributes)
- NewChainPermissionProvider — tries providers in order; first non-zero wins;
errors short-circuit; typical pattern: claims → cached DB fallback
- Cache interface — pluggable backend satisfied by cache-valkey via duck typing
Compliance test (package auth_test) enforces CT-6 (≤1 exported TypeSpec/file),
compile-time interface satisfaction, and behavioural coverage across the full
middleware and provider surface: enrichment success/failure, tenant header,
custom BagEnricher, bag-in-context, authz allowed/denied/error, claims
hit/wildcard/missing/float64, cached hit/miss/error/tenant-key/custom-key,
chain first-non-zero/fallthrough/error.
Depends on contracts v1.0.0, core v1.0.0, web v1.0.0.
- identifiable.go: package-level Module variable (observability.Identifiable) for version
identification — auth is middleware-only; not registered with the launcher
This commit is contained in:
47
CHANGELOG.md
Normal file
47
CHANGELOG.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## v1.0.0
|
||||
|
||||
Initial release.
|
||||
|
||||
### `authmw`
|
||||
|
||||
- `BagEnricher` type — `func(bag security.SecurityBag, r *http.Request) security.SecurityBag`;
|
||||
enriches the request-scoped SecurityBag after the base Identity is built. Register via
|
||||
`WithBagEnricher`. Multiple enrichers run in registration order, each receiving the bag
|
||||
returned by the previous one.
|
||||
- `SetTokenData` — integration contract for provider packages (auth-jwt, auth-firebase).
|
||||
Stores uid and raw claims in context via typed keys; consumed by `EnrichmentMiddleware`.
|
||||
- `GetClaims` — exported accessor for raw token claims stored by `SetTokenData`. Available
|
||||
to custom `IdentityEnricher` implementations and `ClaimsPermissionProvider`.
|
||||
- `EnrichmentMiddleware` — builds a `security.SecurityBag` from uid+claims. Calls the
|
||||
application `IdentityEnricher`, wraps the Identity in a SecurityBag, runs all registered
|
||||
`BagEnricher` functions in order, then stores the bag via `security.SetBagInContext`.
|
||||
Accepts `logging.Logger`; routes errors through `httputil.Error` (401 on missing token,
|
||||
500 on enricher failure).
|
||||
- `AuthzMiddleware` — per-route permission gate. Returns 401 on missing identity, 403 on
|
||||
provider error or insufficient permissions (fail-closed).
|
||||
- `IdentityEnricher` interface — implemented by the application to load user data from uid+claims.
|
||||
- `EnrichOpt` type — `func(*enrichConfig)`.
|
||||
- `WithTenantHeader(header string) EnrichOpt` — reads Identity.TenantID from a named request
|
||||
header. Implemented as a `BagEnricher` internally.
|
||||
- `WithBagEnricher(fn BagEnricher) EnrichOpt` — registers a custom enricher. Use for any
|
||||
attribute beyond TenantID: hardware IDs, grant codes, etc.
|
||||
|
||||
### `rbac`
|
||||
|
||||
- `NewClaimsPermissionProvider` — reads pre-computed bitmasks from JWT claims in context.
|
||||
Flat format: `claims[claimsKey][resource] = mask`. Wildcard `"*"` fallback.
|
||||
Handles int64, float64, json.Number.
|
||||
- `NewCachedPermissionProvider` — wraps any `security.PermissionProvider` with TTL caching.
|
||||
Default cache key: `"rbac:{uid}:{resource}"` (single-tenant) or
|
||||
`"rbac:{tenantID}:{uid}:{resource}"` (multi-tenant). TenantID sourced from the SecurityBag
|
||||
in context automatically. Accepts `...CachedOpt` for customization.
|
||||
- `CachedOpt` type — `func(*cachedConfig)`.
|
||||
- `WithCacheKey(fn func(security.SecurityBag, string, string) string) CachedOpt` — overrides
|
||||
the default cache key function. Use when additional bag attributes (hardware IDs, grant codes)
|
||||
must be part of the key.
|
||||
- `NewChainPermissionProvider` — tries providers in order; returns first non-zero mask. Errors
|
||||
short-circuit.
|
||||
- `Cache` interface — pluggable cache backend. Satisfied by `einherjar/cache-valkey` via duck
|
||||
typing.
|
||||
Reference in New Issue
Block a user