# 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.