Files
httpauth/README.md

113 lines
3.5 KiB
Markdown
Raw Permalink Normal View History

# httpauth
Provider-agnostic HTTP middleware for identity enrichment and RBAC authorization.
## Overview
Three composable `func(http.Handler) http.Handler` middleware functions and two `rbac.PermissionProvider` implementations:
| Component | Responsibility |
|---|---|
| `EnrichmentMiddleware` | Calls app-provided `IdentityEnricher`; stores `rbac.Identity` in context |
| `AuthzMiddleware` | Resolves permission mask via `rbac.PermissionProvider`; gates request |
| `ClaimsPermissionProvider` | Reads pre-computed masks from JWT claims — no DB call |
| `CachedPermissionProvider` | Wraps any provider with a TTL cache; falls through on miss or error |
| `SetTokenData` | Injects uid + claims from any verified token into the request context |
Any upstream `AuthMiddleware` that calls `SetTokenData` is compatible — Firebase, self-issued JWT, API key, etc.
## Installation
```
require code.nochebuena.dev/go/httpauth v0.1.0
```
## Usage
### With JWT-embedded permissions (simple apps)
```go
// Auth middleware (e.g. httpauth-jwt) calls httpauth.SetTokenData after verification.
// JWT claims include: { "permisos": { "usuarios": 515, "roles": 6 } }
r.Use(jwtauth.AuthMiddleware(signer, publicPaths, nil))
r.Use(httpauth.EnrichmentMiddleware(myEnricher))
claimsProvider := httpauth.NewClaimsPermissionProvider("permisos")
r.With(httpauth.AuthzMiddleware(claimsProvider, "usuarios", rbac.Permission(1))).
Get("/usuarios", handler)
```
### With runtime resolution + cache (complex apps)
```go
r.Use(jwtauth.AuthMiddleware(signer, publicPaths, nil))
r.Use(httpauth.EnrichmentMiddleware(myEnricher, httpauth.WithTenantHeader("X-Tenant-ID")))
dbProvider := myapp.NewDBPermissionProvider(db)
cachedProvider := httpauth.NewCachedPermissionProvider(dbProvider, valkeyCache, 5*time.Minute)
r.With(httpauth.AuthzMiddleware(cachedProvider, "usuarios", rbac.Permission(1))).
Get("/usuarios", handler)
```
## Interfaces
### IdentityEnricher
```go
type IdentityEnricher interface {
Enrich(ctx context.Context, uid string, claims map[string]any) (rbac.Identity, error)
}
```
Implement in your application to load user data and return an `rbac.Identity`.
### Cache
```go
type Cache interface {
Get(ctx context.Context, key string) (int64, bool, error)
Set(ctx context.Context, key string, value int64, ttl time.Duration) error
}
```
Implement with Valkey, Redis, or any in-memory store. Cache keys follow the format `rbac:{uid}:{resource}`.
### rbac.PermissionProvider (from the `rbac` package)
```go
type PermissionProvider interface {
ResolveMask(ctx context.Context, uid, resource string) (rbac.PermissionMask, error)
}
```
`AuthzMiddleware` accepts any implementation — `ClaimsPermissionProvider`, `CachedPermissionProvider`, or your own.
## SetTokenData
```go
func SetTokenData(ctx context.Context, uid string, claims map[string]any) context.Context
```
Called by provider-specific `AuthMiddleware` implementations after token verification. `EnrichmentMiddleware` reads the injected values automatically.
## Options
| Option | Description |
|---|---|
| `WithTenantHeader(header)` | Reads `TenantID` from the named request header. If absent, `TenantID` remains `""`. |
## HTTP status codes
| Condition | Status |
|---|---|
| No uid in context (EnrichmentMiddleware) | 401 |
| Enricher error | 500 |
| No `rbac.Identity` in context (AuthzMiddleware) | 401 |
| Permission denied or provider error | 403 |
## Cache key format
`CachedPermissionProvider` uses `rbac:{uid}:{resource}` as the cache key. To invalidate manually, delete the key directly via your `Cache` implementation.