diff --git a/api/oidc.go b/api/oidc.go index 172ec705..b5e60b45 100644 --- a/api/oidc.go +++ b/api/oidc.go @@ -57,9 +57,26 @@ func NewOIDC(conf *config.Configuration, db *database.GormDatabase, userChangeNo log.Fatal().Err(err).Msg("failed to initialize OIDC provider") } + externalProvider := provider + if !conf.OIDC.ExternalSecret { + externalProvider, err = rp.NewRelyingPartyOIDC( + context.Background(), + conf.OIDC.Issuer, + conf.OIDC.ClientID, + "", // no secret, PKCE only + conf.OIDC.RedirectURL, + conf.OIDC.Scopes, + opts..., + ) + if err != nil { + log.Fatal().Err(err).Msg("failed to initialize OIDC provider for external flow") + } + } + return &OIDCAPI{ DB: db, Provider: provider, + ExternalProvider: externalProvider, UserChangeNotifier: userChangeNotifier, UsernameClaim: conf.OIDC.UsernameClaim, PasswordStrength: conf.PassStrength, @@ -88,6 +105,7 @@ type pendingElevation struct { type OIDCAPI struct { DB *database.GormDatabase Provider rp.RelyingParty + ExternalProvider rp.RelyingParty UserChangeNotifier *UserChangeNotifier UsernameClaim string PasswordStrength int @@ -316,7 +334,7 @@ func (a *OIDCAPI) ExternalAuthorizeHandler(ctx *gin.Context) { rp.WithCodeChallenge(req.CodeChallenge), } ctx.JSON(http.StatusOK, &model.OIDCExternalAuthorizeResponse{ - AuthorizeURL: rp.AuthURL(state, a.Provider, authOpts...), + AuthorizeURL: rp.AuthURL(state, a.ExternalProvider, authOpts...), State: state, }) } @@ -363,12 +381,12 @@ func (a *OIDCAPI) ExternalTokenHandler(ctx *gin.Context) { rp.CodeExchangeOpt(rp.WithURLParam("redirect_uri", session.RedirectURI)), rp.WithCodeVerifier(req.CodeVerifier), } - tokens, err := rp.CodeExchange[*oidc.IDTokenClaims](ctx.Request.Context(), req.Code, a.Provider, exchangeOpts...) + tokens, err := rp.CodeExchange[*oidc.IDTokenClaims](ctx.Request.Context(), req.Code, a.ExternalProvider, exchangeOpts...) if err != nil { ctx.AbortWithError(http.StatusUnauthorized, fmt.Errorf("token exchange failed: %w", err)) return } - info, err := rp.Userinfo[*oidc.UserInfo](ctx.Request.Context(), tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.GetSubject(), a.Provider) + info, err := rp.Userinfo[*oidc.UserInfo](ctx.Request.Context(), tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.GetSubject(), a.ExternalProvider) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to get user info: %w", err)) return diff --git a/config/config.go b/config/config.go index 71a9963c..69f18e44 100644 --- a/config/config.go +++ b/config/config.go @@ -63,6 +63,7 @@ type OIDC struct { Issuer string ClientID string ClientSecret string + ExternalSecret bool UsernameClaim string RedirectURL string AutoRegister bool @@ -112,9 +113,10 @@ func Get() (*Configuration, []FutureLog) { UploadedImagesDir: "data/images", PluginsDir: "data/plugins", OIDC: OIDC{ - UsernameClaim: "preferred_username", - AutoRegister: true, - Scopes: []string{"openid", "profile", "email"}, + UsernameClaim: "preferred_username", + AutoRegister: true, + Scopes: []string{"openid", "profile", "email"}, + ExternalSecret: true, }, } @@ -172,6 +174,7 @@ func Get() (*Configuration, []FutureLog) { add(parseString(&c.OIDC.Issuer, EnvOIDCIssuer)) add(parseString(&c.OIDC.ClientID, EnvOIDCClientID)) add(parseString(&c.OIDC.ClientSecret, EnvOIDCClientSecret)) + add(parseBool(&c.OIDC.ExternalSecret, EnvOIDCExternalSecret)) add(parseString(&c.OIDC.UsernameClaim, EnvOIDCUsernameClaim)) add(parseString(&c.OIDC.RedirectURL, EnvOIDCRedirectURL)) add(parseBool(&c.OIDC.AutoRegister, EnvOIDCAutoRegister)) diff --git a/config/keys.go b/config/keys.go index 6578fd13..db233388 100644 --- a/config/keys.go +++ b/config/keys.go @@ -36,6 +36,7 @@ const ( EnvOIDCIssuer = "GOTIFY_OIDC_ISSUER" EnvOIDCClientID = "GOTIFY_OIDC_CLIENTID" EnvOIDCClientSecret = "GOTIFY_OIDC_CLIENTSECRET" + EnvOIDCExternalSecret = "GOTIFY_OIDC_EXTERNALSECRET" EnvOIDCUsernameClaim = "GOTIFY_OIDC_USERNAMECLAIM" EnvOIDCRedirectURL = "GOTIFY_OIDC_REDIRECTURL" EnvOIDCAutoRegister = "GOTIFY_OIDC_AUTOREGISTER"