diff --git a/internal/controller/oidc_controller.go b/internal/controller/oidc_controller.go index 0c5ac918..f075f909 100644 --- a/internal/controller/oidc_controller.go +++ b/internal/controller/oidc_controller.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "slices" "strconv" "strings" @@ -204,20 +205,7 @@ func (controller *OIDCController) authorize(c *gin.Context) { return } - ticket := controller.oidc.CreateAuthorizeRequestTicket(*req) - - values := AuthorizeScreenParams{ - LoginFor: FrontendLoginForOIDC, - OIDCTicket: ticket, - OIDCScope: req.Scope, - OIDCName: client.Name, - } - - if slices.Contains(prompts, service.OIDCPromptLogin) { - values.OIDCPrompt = service.OIDCPromptLogin - } else if slices.Contains(prompts, service.OIDCPromptNone) { - values.OIDCPrompt = service.OIDCPromptNone - } + forceLogin := slices.Contains(prompts, service.OIDCPromptLogin) if req.MaxAge != "" && userContext != nil { maxAge, err := strconv.Atoi(req.MaxAge) @@ -236,11 +224,61 @@ func (controller *OIDCController) authorize(c *gin.Context) { if userContext.Authenticated { authTime := time.Unix(userContext.AuthTime, 0) if authTime.Add(time.Duration(maxAge) * time.Second).Before(time.Now()) { - values.OIDCPrompt = service.OIDCPromptLogin + forceLogin = true } } } + ticket := controller.oidc.CreateAuthorizeRequestTicket(*req) + + if client.SkipAuthorization && userContext != nil && userContext.Authenticated && !forceLogin { + controller.oidc.DeleteAuthorizeRequestTicket(ticket) + sub := controller.oidc.CreateSub(*userContext, req.ClientID) + if err := controller.oidc.DeleteOldSession(c, sub); err != nil { + controller.authorizeError(c, authorizeErrorParams{ + err: err, + reason: "Failed to delete old sessions", + reasonPublic: "Failed to delete old sessions", + callback: req.RedirectURI, + callbackError: "server_error", + state: req.State, + }) + return + } + code := controller.oidc.CreateCode(*req, *userContext) + redirectURL, err := url.Parse(req.RedirectURI) + if err != nil { + controller.authorizeError(c, authorizeErrorParams{ + err: err, + reason: "Failed to build callback URL", + reasonPublic: "Failed to build callback URL", + callback: req.RedirectURI, + callbackError: "server_error", + state: req.State, + }) + return + } + redirectQueries := redirectURL.Query() + redirectQueries.Set("code", code) + redirectQueries.Set("state", req.State) + redirectURL.RawQuery = redirectQueries.Encode() + c.Redirect(http.StatusFound, redirectURL.String()) + return + } + + values := AuthorizeScreenParams{ + LoginFor: FrontendLoginForOIDC, + OIDCTicket: ticket, + OIDCScope: req.Scope, + OIDCName: client.Name, + } + + if forceLogin { + values.OIDCPrompt = service.OIDCPromptLogin + } else if slices.Contains(prompts, service.OIDCPromptNone) { + values.OIDCPrompt = service.OIDCPromptNone + } + queries, err := query.Values(values) if err != nil { diff --git a/internal/model/config.go b/internal/model/config.go index 97c92510..64690fd2 100644 --- a/internal/model/config.go +++ b/internal/model/config.go @@ -273,6 +273,7 @@ type OIDCClientConfig struct { ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile,omitempty"` TrustedRedirectURIs []string `description:"List of trusted redirect URIs." yaml:"trustedRedirectUris,omitempty"` Name string `description:"Client name in UI." yaml:"name,omitempty"` + SkipAuthorization bool `description:"Skip the authorization consent screen for this client." yaml:"skipAuthorization,omitempty"` } type ACLsConfig struct {