From 5ca637a7e6c6280b4bb01cd5379cd6e339648a2a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 30 Aug 2025 22:15:20 +0000 Subject: [PATCH] feat(auth): add oauth require availability configuration on startup (#1358) --- config-raw.json | 5 +++++ pkg/initialize/init.go | 6 +++++ pkg/modules/auth/openid/openid.go | 30 ++++++++++++++----------- pkg/modules/auth/openid/providers.go | 33 +++++++++++++++++++--------- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/config-raw.json b/config-raw.json index 0bc1e6f40..332551252 100644 --- a/config-raw.json +++ b/config-raw.json @@ -717,6 +717,11 @@ "key": "forceuserinfo", "default_value": "false", "comment": "This option forces the use of the OpenID Connect UserInfo endpoint to retrieve user information instead of relying on claims from the ID token. When set to `true`, user data (email, name, username) will always be obtained from the UserInfo endpoint even if the information is available in the token claims. This is useful for providers that don't include complete user information in their tokens or when you need the most up-to-date user data. Allowed value is either `true` or `false`." + }, + { + "key": "requireavailability", + "default_value": "false", + "comment": "This option requires the OpenID Connect provider to be available during Vikunja startup. When set to `true`, Vikunja will crash if it cannot connect to the provider during initialization, allowing container orchestrators like Kubernetes to handle the failure by restarting the application. This is useful in environments where you want to ensure all authentication providers are available before the application starts serving requests. Allowed value is either `true` or `false`." } ] } diff --git a/pkg/initialize/init.go b/pkg/initialize/init.go index 9629df886..fe5209abd 100644 --- a/pkg/initialize/init.go +++ b/pkg/initialize/init.go @@ -97,6 +97,12 @@ func FullInitWithoutAsync() { // Connect to ldap if enabled ldap.InitializeLDAPConnection() + // Check all OpenID Connect providers at startup + _, err := openid.GetAllProviders() + if err != nil { + log.Errorf("Error initializing OpenID Connect providers: %s", err) + } + // Load translations i18n.Init() diff --git a/pkg/modules/auth/openid/openid.go b/pkg/modules/auth/openid/openid.go index 5b26835f7..3c8ba9aa9 100644 --- a/pkg/modules/auth/openid/openid.go +++ b/pkg/modules/auth/openid/openid.go @@ -52,19 +52,20 @@ type Callback struct { // Provider is the structure of an OpenID Connect provider type Provider struct { - Name string `json:"name"` - Key string `json:"key"` - OriginalAuthURL string `json:"-"` - AuthURL string `json:"auth_url"` - LogoutURL string `json:"logout_url"` - ClientID string `json:"client_id"` - Scope string `json:"scope"` - EmailFallback bool `json:"email_fallback"` - UsernameFallback bool `json:"username_fallback"` - ForceUserInfo bool `json:"force_user_info"` - ClientSecret string `json:"-"` - openIDProvider *oidc.Provider - Oauth2Config *oauth2.Config `json:"-"` + Name string `json:"name"` + Key string `json:"key"` + OriginalAuthURL string `json:"-"` + AuthURL string `json:"auth_url"` + LogoutURL string `json:"logout_url"` + ClientID string `json:"client_id"` + Scope string `json:"scope"` + EmailFallback bool `json:"email_fallback"` + UsernameFallback bool `json:"username_fallback"` + ForceUserInfo bool `json:"force_user_info"` + RequireAvailability bool `json:"-"` + ClientSecret string `json:"-"` + openIDProvider *oidc.Provider + Oauth2Config *oauth2.Config `json:"-"` } type claims struct { @@ -83,6 +84,9 @@ func init() { func (p *Provider) setOicdProvider() (err error) { p.openIDProvider, err = oidc.NewProvider(context.Background(), p.OriginalAuthURL) + if err != nil && p.RequireAvailability { + log.Fatalf("OpenID Connect provider '%s' is not available and require_availability is enabled: %s", p.Name, err) + } return err } diff --git a/pkg/modules/auth/openid/providers.go b/pkg/modules/auth/openid/providers.go index d9b3e39e5..113a97d16 100644 --- a/pkg/modules/auth/openid/providers.go +++ b/pkg/modules/auth/openid/providers.go @@ -126,6 +126,7 @@ func getProviderFromMap(pi map[string]interface{}, key string) (provider *Provid "emailfallback", "usernamefallback", "forceuserinfo", + "requireavailability", }, requiredKeys..., ) @@ -193,17 +194,29 @@ func getProviderFromMap(pi map[string]interface{}, key string) (provider *Provid } } + var requireAvailability = false + requireAvailabilityValue, exists := pi["requireavailability"] + if exists { + requireAvailabilityTypedValue, ok := requireAvailabilityValue.(bool) + if ok { + requireAvailability = requireAvailabilityTypedValue + } else { + log.Errorf("requireavailability is not a boolean for provider %s, value: %v", key, requireAvailabilityValue) + } + } + provider = &Provider{ - Name: name, - Key: key, - AuthURL: pi["authurl"].(string), - OriginalAuthURL: pi["authurl"].(string), - ClientSecret: pi["clientsecret"].(string), - LogoutURL: logoutURL, - Scope: scope, - EmailFallback: emailFallback, - UsernameFallback: usernameFallback, - ForceUserInfo: forceUserInfo, + Name: name, + Key: key, + AuthURL: pi["authurl"].(string), + OriginalAuthURL: pi["authurl"].(string), + ClientSecret: pi["clientsecret"].(string), + LogoutURL: logoutURL, + Scope: scope, + EmailFallback: emailFallback, + UsernameFallback: usernameFallback, + ForceUserInfo: forceUserInfo, + RequireAvailability: requireAvailability, } cl, is := pi["clientid"].(int)