feat(auth): add oauth require availability configuration on startup (#1358)

This commit is contained in:
Copilot
2025-08-30 22:15:20 +00:00
committed by GitHub
parent 523dad5134
commit 5ca637a7e6
4 changed files with 51 additions and 23 deletions

View File

@@ -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`."
}
]
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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)