- 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.
77 lines
2.7 KiB
Go
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()
|
|
}
|