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 }