Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
9438983f32
|
|||
|
3c6636f905
|
22
CHANGELOG.md
22
CHANGELOG.md
@@ -5,6 +5,28 @@ All notable changes to this module will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.0] — 2026-05-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `NewChainPermissionProvider(providers ...rbac.PermissionProvider) rbac.PermissionProvider` —
|
||||||
|
tries each provider in order and returns the first non-zero mask; propagates errors
|
||||||
|
immediately without consulting subsequent providers; primary use case is a JWT claims
|
||||||
|
fast-path (`ClaimsPermissionProvider`) chained with a DB-backed fallback
|
||||||
|
(`CachedPermissionProvider`)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Dependency `code.nochebuena.dev/go/rbac` bumped from v0.9.0 to v1.0.0
|
||||||
|
|
||||||
|
### Unchanged
|
||||||
|
|
||||||
|
`SetTokenData`, `EnrichmentMiddleware`, `AuthzMiddleware`, `IdentityEnricher`,
|
||||||
|
`WithTenantHeader`, `Cache`, `NewClaimsPermissionProvider`, and
|
||||||
|
`NewCachedPermissionProvider` are API-compatible with v0.1.0.
|
||||||
|
|
||||||
|
[1.0.0]: https://code.nochebuena.dev/go/httpauth/releases/tag/v1.0.0
|
||||||
|
|
||||||
## [0.1.0] - 2026-05-08
|
## [0.1.0] - 2026-05-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
39
chain_provider.go
Normal file
39
chain_provider.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package httpauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"code.nochebuena.dev/go/rbac"
|
||||||
|
)
|
||||||
|
|
||||||
|
type chainPermissionProvider struct {
|
||||||
|
providers []rbac.PermissionProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChainPermissionProvider returns an rbac.PermissionProvider that tries each
|
||||||
|
// provider in order and returns the first non-zero mask. If all providers return 0,
|
||||||
|
// the result is 0. On error from any provider, the error is propagated immediately
|
||||||
|
// and subsequent providers are not consulted.
|
||||||
|
//
|
||||||
|
// Primary use case: JWT claims fast-path with DB fallback.
|
||||||
|
//
|
||||||
|
// chain := httpauth.NewChainPermissionProvider(
|
||||||
|
// httpauth.NewClaimsPermissionProvider("permisos"), // JWT claims — no DB call
|
||||||
|
// httpauth.NewCachedPermissionProvider(dbProvider, cache, 5*time.Minute), // fallback
|
||||||
|
// )
|
||||||
|
func NewChainPermissionProvider(providers ...rbac.PermissionProvider) rbac.PermissionProvider {
|
||||||
|
return &chainPermissionProvider{providers: providers}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *chainPermissionProvider) ResolveMask(ctx context.Context, uid, resource string) (rbac.PermissionMask, error) {
|
||||||
|
for _, p := range c.providers {
|
||||||
|
mask, err := p.ResolveMask(ctx, uid, resource)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if mask != 0 {
|
||||||
|
return mask, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
4
go.mod
4
go.mod
@@ -1,5 +1,5 @@
|
|||||||
module code.nochebuena.dev/go/httpauth
|
module code.nochebuena.dev/go/httpauth
|
||||||
|
|
||||||
go 1.25
|
go 1.26
|
||||||
|
|
||||||
require code.nochebuena.dev/go/rbac v0.9.0
|
require code.nochebuena.dev/go/rbac v1.0.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,2 +1,2 @@
|
|||||||
code.nochebuena.dev/go/rbac v0.9.0 h1:2fQngWIOeluIaMmo+H2ajT0NVw8GjNFJVi6pbdB3f/o=
|
code.nochebuena.dev/go/rbac v1.0.0 h1:FnsU1HU6vvwchKuZNxDa9RPIFeNwJi0vShWvHKABMws=
|
||||||
code.nochebuena.dev/go/rbac v0.9.0/go.mod h1:LzW8tTJmdbu6HHN26NZZ3HzzdlZAd1sp6aml25Cfz5c=
|
code.nochebuena.dev/go/rbac v1.0.0/go.mod h1:LzW8tTJmdbu6HHN26NZZ3HzzdlZAd1sp6aml25Cfz5c=
|
||||||
|
|||||||
@@ -299,3 +299,52 @@ func TestCachedPermissionProvider_InnerError(t *testing.T) {
|
|||||||
t.Error("expected error from inner, got nil")
|
t.Error("expected error from inner, got nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- ChainPermissionProvider ---
|
||||||
|
|
||||||
|
func TestChainPermissionProvider_FirstNonZero(t *testing.T) {
|
||||||
|
first := &mockProvider{mask: 515}
|
||||||
|
second := &mockProvider{mask: 999}
|
||||||
|
p := NewChainPermissionProvider(first, second)
|
||||||
|
mask, err := p.ResolveMask(context.Background(), "uid1", "usuarios")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if mask != 515 {
|
||||||
|
t.Errorf("want 515 from first provider, got %d", mask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainPermissionProvider_Fallthrough(t *testing.T) {
|
||||||
|
first := &mockProvider{mask: 0}
|
||||||
|
second := &mockProvider{mask: 42}
|
||||||
|
p := NewChainPermissionProvider(first, second)
|
||||||
|
mask, err := p.ResolveMask(context.Background(), "uid1", "usuarios")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if mask != 42 {
|
||||||
|
t.Errorf("want 42 from second provider, got %d", mask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainPermissionProvider_AllZero(t *testing.T) {
|
||||||
|
p := NewChainPermissionProvider(&mockProvider{mask: 0}, &mockProvider{mask: 0})
|
||||||
|
mask, err := p.ResolveMask(context.Background(), "uid1", "usuarios")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if mask != 0 {
|
||||||
|
t.Errorf("want 0 when all providers return 0, got %d", mask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainPermissionProvider_ErrorPropagates(t *testing.T) {
|
||||||
|
first := &mockProvider{err: errors.New("db error")}
|
||||||
|
second := &mockProvider{mask: 42}
|
||||||
|
p := NewChainPermissionProvider(first, second)
|
||||||
|
_, err := p.ResolveMask(context.Background(), "uid1", "usuarios")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error from first provider, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user