diff --git a/pkg/security/database_schema.sql b/pkg/security/database_schema.sql index 9a652a1..34907b0 100644 --- a/pkg/security/database_schema.sql +++ b/pkg/security/database_schema.sql @@ -1545,6 +1545,8 @@ BEGIN 'username', u.username, 'email', u.email, 'user_level', u.user_level, + -- NULLIF converts empty string to NULL; string_to_array(NULL) returns NULL; + -- to_jsonb(NULL) returns NULL; COALESCE then returns '[]' for NULL/empty roles. 'roles', COALESCE(to_jsonb(string_to_array(NULLIF(u.roles, ''), ',')), '[]'::jsonb), 'exp', EXTRACT(EPOCH FROM s.expires_at)::bigint, 'iat', EXTRACT(EPOCH FROM s.created_at)::bigint diff --git a/pkg/security/oauth_server.go b/pkg/security/oauth_server.go index 7ba074c..b6e6f78 100644 --- a/pkg/security/oauth_server.go +++ b/pkg/security/oauth_server.go @@ -128,8 +128,8 @@ func NewOAuthServer(cfg OAuthServerConfig, auth *DatabaseAuthenticator) *OAuthSe if cfg.AuthCodeTTL == 0 { cfg.AuthCodeTTL = 2 * time.Minute } - // Normalize issuer: trim trailing slash to ensure consistent endpoint URL construction. - cfg.Issuer = strings.TrimRight(cfg.Issuer, "/") + // Normalize issuer: remove trailing slash to ensure consistent endpoint URL construction. + cfg.Issuer = strings.TrimSuffix(cfg.Issuer, "/") s := &OAuthServer{ cfg: cfg, auth: auth, @@ -704,9 +704,17 @@ func (s *OAuthServer) revokeHandler(w http.ResponseWriter, r *http.Request) { if s.auth != nil { s.auth.OAuthRevokeToken(r.Context(), token) //nolint:errcheck - } else if len(s.providers) > 0 { + } else { // In external-provider-only mode, attempt revocation via the first provider's auth. - s.providers[0].auth.OAuthRevokeToken(r.Context(), token) //nolint:errcheck + s.mu.RLock() + var providerAuth *DatabaseAuthenticator + if len(s.providers) > 0 { + providerAuth = s.providers[0].auth + } + s.mu.RUnlock() + if providerAuth != nil { + providerAuth.OAuthRevokeToken(r.Context(), token) //nolint:errcheck + } } w.WriteHeader(http.StatusOK) } @@ -735,8 +743,12 @@ func (s *OAuthServer) introspectHandler(w http.ResponseWriter, r *http.Request) // Resolve the authenticator to use: prefer the primary auth, then the first provider's auth. authToUse := s.auth - if authToUse == nil && len(s.providers) > 0 { - authToUse = s.providers[0].auth + if authToUse == nil { + s.mu.RLock() + if len(s.providers) > 0 { + authToUse = s.providers[0].auth + } + s.mu.RUnlock() } if authToUse == nil { w.Write([]byte(`{"active":false}`)) //nolint:errcheck