Files
httpauth/cache.go
Rene Nochebuena 18e5a16f7e feat(httpauth): initial release — provider-agnostic HTTP auth middleware
Provides SetTokenData for upstream AuthMiddleware implementations,
EnrichmentMiddleware and AuthzMiddleware compatible with any provider that
calls SetTokenData, ClaimsPermissionProvider for JWT-embedded permissions,
and CachedPermissionProvider for TTL-backed runtime resolution via any
Cache implementation.
2026-05-07 21:37:25 -06:00

45 lines
1.4 KiB
Go

package httpauth
import (
"context"
"fmt"
"time"
"code.nochebuena.dev/go/rbac"
)
// Cache abstracts the caching backend for permission masks.
// Implementations are typically backed by Valkey or Redis.
type Cache interface {
Get(ctx context.Context, key string) (int64, bool, error)
Set(ctx context.Context, key string, value int64, ttl time.Duration) error
}
type cachedPermissionProvider struct {
inner rbac.PermissionProvider
cache Cache
ttl time.Duration
}
// NewCachedPermissionProvider wraps inner with a TTL-based cache layer.
// Cache key format: "rbac:{uid}:{resource}".
// On cache miss, falls through to inner and populates the cache.
// On cache error, falls through to inner silently — never fails due to cache unavailability.
// For explicit invalidation, delete "rbac:{uid}:{resource}" directly via your Cache.
func NewCachedPermissionProvider(inner rbac.PermissionProvider, cache Cache, ttl time.Duration) rbac.PermissionProvider {
return &cachedPermissionProvider{inner: inner, cache: cache, ttl: ttl}
}
func (p *cachedPermissionProvider) ResolveMask(ctx context.Context, uid, resource string) (rbac.PermissionMask, error) {
key := fmt.Sprintf("rbac:%s:%s", uid, resource)
if val, ok, err := p.cache.Get(ctx, key); err == nil && ok {
return rbac.PermissionMask(val), nil
}
mask, err := p.inner.ResolveMask(ctx, uid, resource)
if err != nil {
return 0, err
}
_ = p.cache.Set(ctx, key, int64(mask), p.ttl)
return mask, nil
}