Files
httpauth/CLAUDE.md

112 lines
4.8 KiB
Markdown
Raw Normal View History

# httpauth
Provider-agnostic HTTP middleware for identity enrichment and RBAC authorization.
## Purpose
`httpauth` is the shared foundation for all `httpauth-*` provider modules. It provides
`EnrichmentMiddleware`, `AuthzMiddleware`, two `rbac.PermissionProvider` implementations
(`ClaimsPermissionProvider` and `CachedPermissionProvider`), and `SetTokenData` — the
bridge between a provider-specific `AuthMiddleware` and the rest of the auth stack.
Any `AuthMiddleware` that calls `SetTokenData` after token verification is compatible.
Downstream code reads identity exclusively via `rbac.FromContext` — no provider type
leaks past the middleware boundary.
## Tier & Dependencies
**Tier:** 3 (transport auth layer; depends only on Tier 0 `rbac`; no external SDK)
**Module:** `code.nochebuena.dev/go/httpauth`
**Direct imports:** `code.nochebuena.dev/go/rbac` only
`httpauth` does not import `logz`, `httpmw`, `httputil`, Firebase, or any JWT library.
It has no logger parameter — errors are returned as HTTP responses.
## Key Design Decisions
- **`rbac.PermissionProvider` without redefinition:** `AuthzMiddleware` accepts
`rbac.PermissionProvider` directly. `rbac` is the single source of truth for this
interface. Provider-specific modules (e.g. `httpauth-firebase`) previously defined
their own `PermissionProvider` locally — that duplication is removed.
- **`SetTokenData` as the integration contract:** Provider-specific `AuthMiddleware`
implementations call `SetTokenData(ctx, uid, claims)` after verifying the token.
The context keys are unexported typed structs. `EnrichmentMiddleware` reads them
via the unexported helpers `getUID` and `getClaims` in the same package.
- **Two permission strategies:** `ClaimsPermissionProvider` (JWT-embedded, no DB call)
and `CachedPermissionProvider` (TTL-backed runtime resolution) are first-class
implementations. Choose based on token size and revocation requirements.
- **Cache falls through on error:** `CachedPermissionProvider` treats cache errors as
misses — a cache outage degrades gracefully to the inner provider.
## Patterns
**Full stack with self-issued JWT:**
```go
r.Use(jwtauth.AuthMiddleware(signer, publicPaths, nil))
r.Use(httpauth.EnrichmentMiddleware(myEnricher, httpauth.WithTenantHeader("X-Tenant-ID")))
// Simple app — permissions embedded in JWT:
claimsProvider := httpauth.NewClaimsPermissionProvider("permisos")
r.With(httpauth.AuthzMiddleware(claimsProvider, "usuarios", rbac.Permission(1))).
Get("/usuarios", handler)
// Complex app — runtime resolution with cache:
cachedProvider := httpauth.NewCachedPermissionProvider(dbProvider, valkeyCache, 5*time.Minute)
r.With(httpauth.AuthzMiddleware(cachedProvider, "usuarios", rbac.Permission(1))).
Get("/usuarios", handler)
```
**Provider-specific AuthMiddleware calling SetTokenData:**
```go
// Inside httpauth-jwt or httpauth-firebase AuthMiddleware, after token verification:
ctx := httpauth.SetTokenData(r.Context(), verified.UID, verified.Claims)
next.ServeHTTP(w, r.WithContext(ctx))
```
**Reading identity in a handler:**
```go
identity, ok := rbac.FromContext(r.Context())
if !ok {
// should not happen if EnrichmentMiddleware is in the chain
}
```
**Implementing Cache (e.g. with Valkey):**
```go
type valkeyCache struct{ client valkey.Client }
func (c *valkeyCache) Get(ctx context.Context, key string) (int64, bool, error) { ... }
func (c *valkeyCache) Set(ctx context.Context, key string, val int64, ttl time.Duration) error { ... }
```
**Cache key for manual invalidation:** `rbac:{uid}:{resource}`
## What to Avoid
- Do not call `SetTokenData` from application or domain layer code. It is the
exclusive responsibility of provider-specific `AuthMiddleware` implementations.
- Do not put `AuthzMiddleware` before `EnrichmentMiddleware` in the chain.
`AuthzMiddleware` reads `rbac.Identity` from context; if enrichment has not run,
it will return 401.
- Do not import `httpauth` from service or domain layers. It is a transport package.
- Do not define a local `PermissionProvider` interface in provider modules that import
this package — use `rbac.PermissionProvider` directly.
## Testing Notes
- `compliance_test.go` verifies at compile time that mock types satisfy `IdentityEnricher`
and `Cache`, and that `rbac.PermissionProvider` is satisfied by the two built-in
provider implementations.
- `EnrichmentMiddleware` tests use `injectTokenData(uid, claims, next)` — a helper
that calls `SetTokenData` to bypass a real upstream `AuthMiddleware`.
- `AuthzMiddleware` tests pre-populate context with `rbac.SetInContext` — no enrichment
middleware needed.
- `ClaimsPermissionProvider` tests exercise both `float64` (JSON decode) and `int64`
paths for the mask value.
- `CachedPermissionProvider` tests exercise cache hit, miss, cache error fallthrough,
and inner provider error propagation.