Add central login recovery and confirmed shared sign-out
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 44s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 44s
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
This commit is contained in:
parent
89694ad6df
commit
074c2ce498
9 changed files with 334 additions and 49 deletions
|
|
@ -66,14 +66,15 @@ func (p *pendingStateStore) Delete(state string) {
|
|||
|
||||
// AuthorizeHandler implements GET /authorize and GET /authorize/callback.
|
||||
type AuthorizeHandler struct {
|
||||
ClientConfig map[string]*domain.Client
|
||||
Auth domain.AuthProvider
|
||||
MFA domain.MFAProvider
|
||||
Sessions *SessionStore
|
||||
Logins *LoginSessionStore
|
||||
Handoffs *HandoffStore
|
||||
Issuer string
|
||||
Emitter telemetry.Emitter
|
||||
AccountPortalURL string
|
||||
ClientConfig map[string]*domain.Client
|
||||
Auth domain.AuthProvider
|
||||
MFA domain.MFAProvider
|
||||
Sessions *SessionStore
|
||||
Logins *LoginSessionStore
|
||||
Handoffs *HandoffStore
|
||||
Issuer string
|
||||
Emitter telemetry.Emitter
|
||||
|
||||
pending *pendingStateStore
|
||||
once sync.Once
|
||||
|
|
@ -256,7 +257,7 @@ func (h *AuthorizeHandler) serveAuthorize(w http.ResponseWriter, r *http.Request
|
|||
PKCEChallengeMethod: codeChallengeMethod,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "upstream auth provider error", http.StatusBadGateway)
|
||||
h.browserFailure(w, r, http.StatusBadGateway, "upstream auth provider error")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +275,7 @@ func (h *AuthorizeHandler) ServeHTTPCallback(w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
if r.Method != http.MethodGet {
|
||||
w.Header().Set("Allow", "GET, POST")
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
h.browserFailure(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -287,12 +288,12 @@ func (h *AuthorizeHandler) ServeHTTPCallback(w http.ResponseWriter, r *http.Requ
|
|||
// Recover pending state keyed by state param.
|
||||
ps, ok := h.pending.Load(state)
|
||||
if !ok {
|
||||
http.Error(w, "unknown or expired state", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "unknown or expired state")
|
||||
return
|
||||
}
|
||||
if time.Now().After(ps.ExpiresAt) {
|
||||
h.pending.Delete(state)
|
||||
http.Error(w, "authorization request expired", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "authorization request expired")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -315,7 +316,7 @@ func (h *AuthorizeHandler) ServeHTTPCallback(w http.ResponseWriter, r *http.Requ
|
|||
return
|
||||
}
|
||||
h.pending.Delete(state)
|
||||
http.Error(w, "authentication failed", http.StatusUnauthorized)
|
||||
h.authenticationFailure(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -330,12 +331,12 @@ func (h *AuthorizeHandler) ServeHTTPCallback(w http.ResponseWriter, r *http.Requ
|
|||
Result: "failure",
|
||||
ErrorType: "mfa_check_error",
|
||||
})
|
||||
http.Error(w, "mfa check error", http.StatusInternalServerError)
|
||||
h.browserFailure(w, r, http.StatusInternalServerError, "mfa check error")
|
||||
return
|
||||
}
|
||||
if decision.RequireMFA {
|
||||
if handed, herr := h.maybeEnrollmentHandoff(ctx, w, r, ps, result.Username); herr != nil {
|
||||
http.Error(w, "enrollment check error", http.StatusInternalServerError)
|
||||
h.browserFailure(w, r, http.StatusInternalServerError, "enrollment check error")
|
||||
return
|
||||
} else if handed {
|
||||
return
|
||||
|
|
@ -349,7 +350,7 @@ func (h *AuthorizeHandler) ServeHTTPCallback(w http.ResponseWriter, r *http.Requ
|
|||
if err := h.MFA.ValidateMFAToken(ctx, result.Username, mfaToken); err != nil {
|
||||
if errors.Is(err, domain.ErrMFANotEnrolled) {
|
||||
if handed, herr := h.maybeEnrollmentHandoff(ctx, w, r, ps, result.Username); herr != nil {
|
||||
http.Error(w, "enrollment check error", http.StatusInternalServerError)
|
||||
h.browserFailure(w, r, http.StatusInternalServerError, "enrollment check error")
|
||||
return
|
||||
} else if handed {
|
||||
return
|
||||
|
|
@ -357,7 +358,7 @@ func (h *AuthorizeHandler) ServeHTTPCallback(w http.ResponseWriter, r *http.Requ
|
|||
}
|
||||
h.pending.Delete(state)
|
||||
h.emitMFAFailure(ctx, ps.ClientID)
|
||||
http.Error(w, "MFA validation failed", http.StatusUnauthorized)
|
||||
h.browserFailure(w, r, http.StatusUnauthorized, "MFA validation failed")
|
||||
return
|
||||
}
|
||||
h.pending.Delete(state)
|
||||
|
|
@ -398,7 +399,7 @@ func (h *AuthorizeHandler) decideAssurance(ctx context.Context, ps *PendingState
|
|||
func (h *AuthorizeHandler) serveMFASubmission(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "invalid form", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "invalid form")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -407,17 +408,17 @@ func (h *AuthorizeHandler) serveMFASubmission(w http.ResponseWriter, r *http.Req
|
|||
|
||||
ps, ok := h.pending.Load(state)
|
||||
if !ok {
|
||||
http.Error(w, "unknown or expired state", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "unknown or expired state")
|
||||
return
|
||||
}
|
||||
if time.Now().After(ps.ExpiresAt) {
|
||||
h.pending.Delete(state)
|
||||
http.Error(w, "authorization request expired", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "authorization request expired")
|
||||
return
|
||||
}
|
||||
if ps.AuthenticatedUser == "" {
|
||||
h.pending.Delete(state)
|
||||
http.Error(w, "mfa challenge not active", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "mfa challenge not active")
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(mfaToken) == "" {
|
||||
|
|
@ -428,7 +429,7 @@ func (h *AuthorizeHandler) serveMFASubmission(w http.ResponseWriter, r *http.Req
|
|||
if err := h.MFA.ValidateMFAToken(ctx, ps.AuthenticatedUser, mfaToken); err != nil {
|
||||
h.pending.Delete(state)
|
||||
h.emitMFAFailure(ctx, ps.ClientID)
|
||||
http.Error(w, "MFA validation failed", http.StatusUnauthorized)
|
||||
h.browserFailure(w, r, http.StatusUnauthorized, "MFA validation failed")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -485,7 +486,7 @@ func (h *AuthorizeHandler) completeAuthorization(w http.ResponseWriter, r *http.
|
|||
// Redirect to client with code and state.
|
||||
redirectTo, err := url.Parse(ps.RedirectURI)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid redirect_uri", http.StatusInternalServerError)
|
||||
h.browserFailure(w, r, http.StatusInternalServerError, "invalid redirect_uri")
|
||||
return
|
||||
}
|
||||
q := redirectTo.Query()
|
||||
|
|
@ -515,7 +516,7 @@ func (h *AuthorizeHandler) startHandoff(w http.ResponseWriter, r *http.Request,
|
|||
}
|
||||
token, err := h.Handoffs.Issue(kind, ps)
|
||||
if err != nil {
|
||||
http.Error(w, "handoff error", http.StatusInternalServerError)
|
||||
h.browserFailure(w, r, http.StatusInternalServerError, "handoff error")
|
||||
return
|
||||
}
|
||||
loc, err := appendHandoff(dest, token)
|
||||
|
|
@ -530,18 +531,18 @@ func (h *AuthorizeHandler) startHandoff(w http.ResponseWriter, r *http.Request,
|
|||
func (h *AuthorizeHandler) serveRegisterFromPending(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.Header().Set("Allow", "GET")
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
h.browserFailure(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
state := r.URL.Query().Get("state")
|
||||
ps, ok := h.pending.Load(state)
|
||||
if !ok {
|
||||
http.Error(w, "unknown or expired state", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "unknown or expired state")
|
||||
return
|
||||
}
|
||||
if time.Now().After(ps.ExpiresAt) {
|
||||
h.pending.Delete(state)
|
||||
http.Error(w, "authorization request expired", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "authorization request expired")
|
||||
return
|
||||
}
|
||||
h.startHandoff(w, r, ps, HandoffRegister)
|
||||
|
|
@ -550,20 +551,20 @@ func (h *AuthorizeHandler) serveRegisterFromPending(w http.ResponseWriter, r *ht
|
|||
func (h *AuthorizeHandler) serveHandoffReturn(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.Header().Set("Allow", "GET")
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
h.browserFailure(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
token := r.URL.Query().Get("kc_handoff")
|
||||
env, err := h.Handoffs.Consume(token)
|
||||
switch {
|
||||
case errors.Is(err, errHandoffExpired):
|
||||
http.Error(w, "handoff expired", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "handoff expired")
|
||||
return
|
||||
case errors.Is(err, errHandoffReplay):
|
||||
http.Error(w, "handoff already used", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "handoff already used")
|
||||
return
|
||||
case err != nil:
|
||||
http.Error(w, "invalid handoff", http.StatusBadRequest)
|
||||
h.browserFailure(w, r, http.StatusBadRequest, "invalid handoff")
|
||||
return
|
||||
}
|
||||
client, ok := h.ClientConfig[env.ClientID]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue