auth: enable JWT validation, support roles claim
- Enable auth in gateway config with JWKS URL - Check both 'permissions' (users) and 'roles' (service accounts) - Allow any Authentik issuer (service accounts have per-provider issuers) - Required capability: llm:inference
This commit is contained in:
+42
-12
@@ -3,6 +3,7 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -107,42 +108,64 @@ func (v *Validator) ValidateBearerToken(authHeader string) (jwt.MapClaims, error
|
||||
}
|
||||
}
|
||||
|
||||
// Check iss (issuer)
|
||||
if iss, ok := claims["iss"].(string); !ok || iss != v.issuer {
|
||||
return nil, fmt.Errorf("invalid issuer: expected %s, got %s", v.issuer, iss)
|
||||
// Check iss (issuer) - if configured, must match exactly or be from Authentik
|
||||
// Service accounts have per-provider issuers, so we allow any authentik.riotpiao.com issuer
|
||||
if v.issuer != "" {
|
||||
iss, ok := claims["iss"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing issuer claim")
|
||||
}
|
||||
// Allow exact match or any Authentik issuer
|
||||
if iss != v.issuer && !isAuthentikIssuer(iss) {
|
||||
return nil, fmt.Errorf("invalid issuer: expected %s or Authentik, got %s", v.issuer, iss)
|
||||
}
|
||||
}
|
||||
|
||||
// Check aud (audience)
|
||||
if aud, ok := claims["aud"].(string); !ok || aud != v.audience {
|
||||
return nil, fmt.Errorf("invalid audience: expected %s, got %s", v.audience, aud)
|
||||
// Check aud (audience) - if configured, must match
|
||||
if v.audience != "" {
|
||||
if aud, ok := claims["aud"].(string); !ok || aud != v.audience {
|
||||
return nil, fmt.Errorf("invalid audience: expected %s, got %s", v.audience, aud)
|
||||
}
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// CheckPermissions checks if claims contain required permission(s).
|
||||
// Checks both "permissions" (user tokens) and "roles" (service account tokens).
|
||||
// Returns true if any required permission is found or wildcard "*" exists.
|
||||
func (v *Validator) CheckPermissions(claims jwt.MapClaims, required ...string) bool {
|
||||
permsIface, ok := claims["permissions"]
|
||||
// Check both claims - users have "permissions", service accounts have "roles"
|
||||
for _, claimKey := range []string{"permissions", "roles"} {
|
||||
if v.checkClaimList(claims, claimKey, required...) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkClaimList checks if a specific claim contains any required value.
|
||||
func (v *Validator) checkClaimList(claims jwt.MapClaims, claimKey string, required ...string) bool {
|
||||
valuesIface, ok := claims[claimKey]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
perms, ok := permsIface.([]interface{})
|
||||
values, ok := valuesIface.([]interface{})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, perm := range perms {
|
||||
permStr, ok := perm.(string)
|
||||
for _, val := range values {
|
||||
valStr, ok := val.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if permStr == "*" {
|
||||
if valStr == "*" {
|
||||
return true
|
||||
}
|
||||
for _, req := range required {
|
||||
if permStr == req {
|
||||
if valStr == req {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -151,6 +174,13 @@ func (v *Validator) CheckPermissions(claims jwt.MapClaims, required ...string) b
|
||||
return false
|
||||
}
|
||||
|
||||
// isAuthentikIssuer checks if issuer is from our Authentik instance.
|
||||
func isAuthentikIssuer(iss string) bool {
|
||||
return len(iss) > 0 &&
|
||||
(iss == "https://authentik.riotpiao.com" ||
|
||||
strings.HasPrefix(iss, "https://authentik.riotpiao.com/"))
|
||||
}
|
||||
|
||||
// DecodeToken decodes JWT payload without verification (for debugging/testing).
|
||||
func DecodeToken(tokenString string) (jwt.MapClaims, error) {
|
||||
claims := jwt.MapClaims{}
|
||||
|
||||
Reference in New Issue
Block a user