ultisuite-backend/internal/api/auth/oauth_context.go
R3D347HR4Y 525edb188a
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(authentik): enhance OIDC flow with new logout redirect and branding support
- Added a new blueprint for OIDC logout that invalidates the Authentik session and redirects to a specified landing page.
- Introduced custom CSS and JS files for branding, improving the visual integration of Authentik flows.
- Updated Nginx configuration to serve the new branding assets and handle specific routes for signup and password recovery.
- Enhanced the flow completion logic to support OIDC bridge functionality, including session management and redirect handling.
- Implemented unit tests for the new OIDC bridge and flow context functionalities to ensure reliability.
2026-06-21 00:12:53 +02:00

77 lines
2.7 KiB
Go

package authapi
import (
"net/http"
"net/url"
"strings"
"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
)
type oauthFlowContextResponse struct {
FlowQuery string `json:"flowQuery"`
}
// PrepareOAuthFlowContext returns the Authentik flow executor query string that binds
// embedded authentication to the pending OIDC authorize request (see Authentik flow-executor docs).
func (h *Handler) PrepareOAuthFlowContext(w http.ResponseWriter, r *http.Request) {
if h.oidcClientID == "" {
apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "oidc_not_configured", "OIDC client id not configured", nil)
return
}
returnTo := strings.TrimSpace(r.URL.Query().Get("returnTo"))
if returnTo == "" {
returnTo = "/mail/inbox"
}
if !strings.HasPrefix(returnTo, "/") || strings.HasPrefix(returnTo, "//") {
apiresponse.WriteError(w, r, http.StatusBadRequest, "invalid_return_to", "returnTo must be a relative path", nil)
return
}
verifier, challenge, err := newPKCEPair()
if err != nil {
apiresponse.WriteError(w, r, http.StatusInternalServerError, "pkce_failed", err.Error(), nil)
return
}
state, err := randomOAuthState()
if err != nil {
apiresponse.WriteError(w, r, http.StatusInternalServerError, "state_failed", err.Error(), nil)
return
}
setOAuthBridgeCookies(w, r, verifier, state, returnTo)
flowQuery := buildOAuthFlowQuery(h.appURL, h.oidcClientID, state, challenge)
apiresponse.WriteJSON(w, http.StatusOK, oauthFlowContextResponse{FlowQuery: flowQuery})
}
func buildOAuthFlowQuery(appURL, clientID, state, codeChallenge string) string {
base := suiteAuthAppOrigin(appURL)
oauthParams := url.Values{}
oauthParams.Set("client_id", clientID)
oauthParams.Set("redirect_uri", base+"/api/auth/callback")
oauthParams.Set("response_type", "code")
oauthParams.Set("scope", "openid profile email offline_access")
oauthParams.Set("state", state)
oauthParams.Set("code_challenge", codeChallenge)
oauthParams.Set("code_challenge_method", "S256")
authorizePath := "/auth/application/o/authorize/?" + oauthParams.Encode()
flowParams := url.Values{}
flowParams.Set("next", authorizePath)
return flowParams.Encode()
}
func buildOAuthAuthorizeURL(appURL, clientID, state, codeChallenge string) string {
base := suiteAuthAppOrigin(appURL)
oauthParams := url.Values{}
oauthParams.Set("client_id", clientID)
oauthParams.Set("redirect_uri", base+"/api/auth/callback")
oauthParams.Set("response_type", "code")
oauthParams.Set("scope", "openid profile email offline_access")
oauthParams.Set("state", state)
oauthParams.Set("code_challenge", codeChallenge)
oauthParams.Set("code_challenge_method", "S256")
return base + "/auth/application/o/authorize/?" + oauthParams.Encode()
}