- Add pact.TokenScope/HasTokenScopes and bouncer.ScopeLimit on Principal - Append backend_api_tokens migration; Allows evaluates scope before superuser - Composite guard routes Bearer g15_… to token lookup; smoke tests cover path Co-authored-by: Cursor <cursoragent@cursor.com>
75 lines
2.2 KiB
Go
75 lines
2.2 KiB
Go
package bouncer
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
type userKey struct{}
|
|
|
|
// ScopeLimit restricts a principal to the permission patterns of its personal
|
|
// API token. Nil means unrestricted (a JWT session). Only a token guard sets
|
|
// Scope, already filtered to scopes whose methods include the request method.
|
|
// Full is true for framework scopes that act as the owner on every allowed
|
|
// method ("*") or on GET/HEAD ("admin:read"). Otherwise Scopes holds one
|
|
// pattern slice per method-allowed scope (D-03 literal reading).
|
|
type ScopeLimit struct {
|
|
Full bool
|
|
Scopes [][]string
|
|
}
|
|
|
|
// Principal is the authenticated identity stored on the request context.
|
|
// PreferredLocale empty means no override. TokensValidAfter zero means no cutoff.
|
|
// IsSuperuser and PermissionGrants are set only for backend-admin principals.
|
|
// A grant ending in ".*" matches permission codes by prefix.
|
|
// Scope is set only for personal API token principals.
|
|
type Principal struct {
|
|
ID uint
|
|
MustChangePassword bool
|
|
PreferredLocale string
|
|
TokensValidAfter time.Time
|
|
Backend bool `json:"-"`
|
|
IsSuperuser bool `json:"-"`
|
|
PermissionGrants map[string]bool `json:"-"`
|
|
Scope *ScopeLimit `json:"-"`
|
|
}
|
|
|
|
// WithUser stores the verified principal on ctx.
|
|
func WithUser(ctx context.Context, user *Principal) context.Context {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
return context.WithValue(ctx, userKey{}, user)
|
|
}
|
|
|
|
// User returns the verified principal from ctx.
|
|
func User(ctx context.Context) (*Principal, bool) {
|
|
if ctx == nil {
|
|
return nil, false
|
|
}
|
|
u, ok := ctx.Value(userKey{}).(*Principal)
|
|
return u, ok && u != nil
|
|
}
|
|
|
|
type credentialKey struct{}
|
|
|
|
// WithCredential stores the resolved credential (e.g. *ApiToken) on ctx.
|
|
func WithCredential(ctx context.Context, cred any) context.Context {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
return context.WithValue(ctx, credentialKey{}, cred)
|
|
}
|
|
|
|
// Credential returns the resolved credential from ctx.
|
|
func Credential(ctx context.Context) (any, bool) {
|
|
if ctx == nil {
|
|
return nil, false
|
|
}
|
|
c := ctx.Value(credentialKey{})
|
|
if c == nil {
|
|
return nil, false
|
|
}
|
|
return c, true
|
|
}
|