diff --git a/internal/auth/jwt.go b/internal/auth/jwt.go index 3fe0e80..3c12421 100644 --- a/internal/auth/jwt.go +++ b/internal/auth/jwt.go @@ -3,6 +3,7 @@ package auth import ( "context" "fmt" + "strings" "sync" "time" @@ -10,6 +11,13 @@ import ( "github.com/golang-jwt/jwt/v5" ) +// isValidIssuer checks if issuer is from Authentik (any provider/app). +// Accepts: https://authentik.riotpiao.com/application/o/{provider}/ +func isValidIssuer(iss string) bool { + return strings.Contains(iss, "authentik.riotpiao.com/application/o/") && + strings.HasSuffix(iss, "/") +} + // Validator validates JWTs against Authentik JWKS. type Validator struct { issuer string @@ -107,9 +115,13 @@ 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) - accept any Authentik provider issuer + // (portfolio-agent, memory-agent, api-gw, etc.) + // All use same signing key so JWKS validation is sufficient + if iss, ok := claims["iss"].(string); !ok { + return nil, fmt.Errorf("missing issuer claim") + } else if !isValidIssuer(iss) { + return nil, fmt.Errorf("invalid issuer: %s", iss) } // Check aud (audience) @@ -121,18 +133,32 @@ func (v *Validator) ValidateBearerToken(authHeader string) (jwt.MapClaims, error } // CheckPermissions checks if claims contain required permission(s). +// Checks both "permissions" claim (for users) and "roles" claim (for service accounts). // 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"] - if !ok { - return false + // Try permissions claim first (for user tokens) + if permsIface, ok := claims["permissions"]; ok { + if perms, ok := permsIface.([]interface{}); ok { + if v.checkPermList(perms, required...) { + return true + } + } } - perms, ok := permsIface.([]interface{}) - if !ok { - return false + // Fall back to roles claim (for service account tokens) + if rolesIface, ok := claims["roles"]; ok { + if roles, ok := rolesIface.([]interface{}); ok { + if v.checkPermList(roles, required...) { + return true + } + } } + return false +} + +// checkPermList is a helper that checks a permission/role list. +func (v *Validator) checkPermList(perms []interface{}, required ...string) bool { for _, perm := range perms { permStr, ok := perm.(string) if !ok { @@ -147,7 +173,6 @@ func (v *Validator) CheckPermissions(claims jwt.MapClaims, required ...string) b } } } - return false }