mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-29 03:24:07 +00:00
ref!(auth/jwt): variadic requiredScopes in NewAccessTokenValidator
Distinguishes the two validator constructors by signature:
- NewIDTokenValidator(iss, aud, azp []string) — allowlist semantics
- NewAccessTokenValidator(iss, aud []string, requiredScopes ...string) — requirement semantics
Variadic scopes read naturally at the call site:
NewAccessTokenValidator(issuers, audiences, "openid", "profile")
Three-state semantics preserved:
no args → scope not checked
[]string{}... → scope must be present (any value)
"openid", ... → scope must contain all listed values
Also removes the old gracePeriod parameter from both constructors
(was 0 at all call sites; set GracePeriod on the struct directly
if a non-default value is needed).
Adds TestCov_NewAccessTokenValidator_Scopes covering all three cases.
This commit is contained in:
parent
26bdc0a3db
commit
0d99234914
@ -1924,7 +1924,7 @@ func TestCov_NewIDTokenValidator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCov_NewAccessTokenValidator(t *testing.T) {
|
func TestCov_NewAccessTokenValidator(t *testing.T) {
|
||||||
v := NewAccessTokenValidator([]string{"iss"}, []string{"aud"}, nil)
|
v := NewAccessTokenValidator([]string{"iss"}, []string{"aud"})
|
||||||
if v.Checks&ChecksConfigured == 0 {
|
if v.Checks&ChecksConfigured == 0 {
|
||||||
t.Fatal("expected ChecksConfigured")
|
t.Fatal("expected ChecksConfigured")
|
||||||
}
|
}
|
||||||
@ -1932,12 +1932,76 @@ func TestCov_NewAccessTokenValidator(t *testing.T) {
|
|||||||
t.Fatal("expected CheckJTI and CheckClientID for access token")
|
t.Fatal("expected CheckJTI and CheckClientID for access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
v2 := NewAccessTokenValidator(nil, nil, nil)
|
v2 := NewAccessTokenValidator(nil, nil)
|
||||||
if v2.Checks&CheckIss != 0 {
|
if v2.Checks&CheckIss != 0 {
|
||||||
t.Fatal("expected no CheckIss for nil iss")
|
t.Fatal("expected no CheckIss for nil iss")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCov_NewAccessTokenValidator_Scopes(t *testing.T) {
|
||||||
|
iss := []string{"https://example.com"}
|
||||||
|
aud := []string{"https://api.example.com"}
|
||||||
|
|
||||||
|
t.Run("nil_no_scope_check", func(t *testing.T) {
|
||||||
|
// No scope args: CheckScope not set, scope claim not validated.
|
||||||
|
v := NewAccessTokenValidator(iss, aud)
|
||||||
|
if v.Checks&CheckScope != 0 {
|
||||||
|
t.Fatal("expected CheckScope not set for nil scopes")
|
||||||
|
}
|
||||||
|
if v.RequiredScopes != nil {
|
||||||
|
t.Fatal("expected nil RequiredScopes")
|
||||||
|
}
|
||||||
|
// Validate passes even with no scope claim.
|
||||||
|
claims := goodClaims()
|
||||||
|
claims.Scope = nil
|
||||||
|
claims.JTI = "jti-x"
|
||||||
|
if err := v.Validate(nil, claims, testNow); err != nil {
|
||||||
|
t.Fatalf("expected no error without scope check, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty_presence_only", func(t *testing.T) {
|
||||||
|
// Empty spread: CheckScope set, any non-empty scope accepted.
|
||||||
|
v := NewAccessTokenValidator(iss, aud, []string{}...)
|
||||||
|
if v.Checks&CheckScope == 0 {
|
||||||
|
t.Fatal("expected CheckScope set for empty non-nil scopes")
|
||||||
|
}
|
||||||
|
if v.RequiredScopes == nil {
|
||||||
|
t.Fatal("expected non-nil RequiredScopes")
|
||||||
|
}
|
||||||
|
// Validate passes when scope is present.
|
||||||
|
if err := v.Validate(nil, goodClaims(), testNow); err != nil {
|
||||||
|
t.Fatalf("expected no error with scope present, got %v", err)
|
||||||
|
}
|
||||||
|
// Validate fails when scope is absent.
|
||||||
|
claims := goodClaims()
|
||||||
|
claims.Scope = nil
|
||||||
|
err := v.Validate(nil, claims, testNow)
|
||||||
|
if !errors.Is(err, ErrMissingClaim) {
|
||||||
|
t.Fatalf("expected ErrMissingClaim for absent scope, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("specific_scope", func(t *testing.T) {
|
||||||
|
// Specific scope: CheckScope set, token must contain "openid".
|
||||||
|
v := NewAccessTokenValidator(iss, aud, "openid")
|
||||||
|
if v.Checks&CheckScope == 0 {
|
||||||
|
t.Fatal("expected CheckScope set")
|
||||||
|
}
|
||||||
|
// Validate passes when scope contains "openid".
|
||||||
|
if err := v.Validate(nil, goodClaims(), testNow); err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
// Validate fails when "openid" is absent from scope.
|
||||||
|
claims := goodClaims()
|
||||||
|
claims.Scope = SpaceDelimited{"profile"}
|
||||||
|
err := v.Validate(nil, claims, testNow)
|
||||||
|
if !errors.Is(err, ErrInsufficientScope) {
|
||||||
|
t.Fatalf("expected ErrInsufficientScope, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestCov_Validate_Unconfigured(t *testing.T) {
|
func TestCov_Validate_Unconfigured(t *testing.T) {
|
||||||
v := &Validator{} // zero value
|
v := &Validator{} // zero value
|
||||||
err := v.Validate(nil, goodClaims(), testNow)
|
err := v.Validate(nil, goodClaims(), testNow)
|
||||||
|
|||||||
@ -82,7 +82,7 @@
|
|||||||
// use [NewAccessTokenValidator] with [TokenClaims] (which includes the
|
// use [NewAccessTokenValidator] with [TokenClaims] (which includes the
|
||||||
// client_id and scope fields):
|
// client_id and scope fields):
|
||||||
//
|
//
|
||||||
// v := jwt.NewAccessTokenValidator(issuers, audiences, relyingParties)
|
// v := jwt.NewAccessTokenValidator(issuers, audiences, "openid", "profile")
|
||||||
// if err := v.Validate(nil, &claims, time.Now()); err != nil { /* ... */ }
|
// if err := v.Validate(nil, &claims, time.Now()); err != nil { /* ... */ }
|
||||||
//
|
//
|
||||||
// - [NewAccessToken] creates a JWS with the correct "at+jwt" typ header
|
// - [NewAccessToken] creates a JWS with the correct "at+jwt" typ header
|
||||||
|
|||||||
@ -335,12 +335,16 @@ func NewIDTokenValidator(iss, aud, azp []string) *Validator {
|
|||||||
// Pass the allowed issuers and audiences, or nil to skip that check.
|
// Pass the allowed issuers and audiences, or nil to skip that check.
|
||||||
// Use []string{"*"} to require the claim be present without restricting its value.
|
// Use []string{"*"} to require the claim be present without restricting its value.
|
||||||
//
|
//
|
||||||
// Checks enabled by default: iss, exp, aud, sub, client_id, iat, jti. and scope.
|
// Checks enabled by default: iss, exp, aud, sub, client_id, iat, jti, and scope.
|
||||||
// Not checked: nbf, auth_time, and, azp.
|
// requiredScopes controls scope validation:
|
||||||
// Populate RequiredScopes to enforce specific scope values (overrides CheckScope).
|
// - no args: scope not checked
|
||||||
|
// - []string{}...: scope must be present (any value accepted)
|
||||||
|
// - "openid", "profile", ...: scope must contain all listed values
|
||||||
|
//
|
||||||
|
// Not checked: nbf, auth_time, azp.
|
||||||
//
|
//
|
||||||
// https://www.rfc-editor.org/rfc/rfc9068.html#section-2.2
|
// https://www.rfc-editor.org/rfc/rfc9068.html#section-2.2
|
||||||
func NewAccessTokenValidator(iss, aud, scopes []string) *Validator {
|
func NewAccessTokenValidator(iss, aud []string, requiredScopes ...string) *Validator {
|
||||||
checks := ChecksConfigured | CheckSub | CheckExp | CheckIAt | CheckJTI | CheckClientID
|
checks := ChecksConfigured | CheckSub | CheckExp | CheckIAt | CheckJTI | CheckClientID
|
||||||
if iss != nil {
|
if iss != nil {
|
||||||
checks |= CheckIss
|
checks |= CheckIss
|
||||||
@ -348,7 +352,7 @@ func NewAccessTokenValidator(iss, aud, scopes []string) *Validator {
|
|||||||
if aud != nil {
|
if aud != nil {
|
||||||
checks |= CheckAud
|
checks |= CheckAud
|
||||||
}
|
}
|
||||||
if scopes != nil {
|
if requiredScopes != nil {
|
||||||
checks |= CheckScope
|
checks |= CheckScope
|
||||||
}
|
}
|
||||||
return &Validator{
|
return &Validator{
|
||||||
@ -356,7 +360,7 @@ func NewAccessTokenValidator(iss, aud, scopes []string) *Validator {
|
|||||||
GracePeriod: defaultGracePeriod,
|
GracePeriod: defaultGracePeriod,
|
||||||
Iss: iss,
|
Iss: iss,
|
||||||
Aud: aud,
|
Aud: aud,
|
||||||
RequiredScopes: scopes,
|
RequiredScopes: requiredScopes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user