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