diff --git a/authenticate/handlers.go b/authenticate/handlers.go index 3402eb617..a8730d791 100644 --- a/authenticate/handlers.go +++ b/authenticate/handlers.go @@ -382,11 +382,6 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request) return nil, httputil.NewError(http.StatusBadRequest, fmt.Errorf("state malformed, size: %d", len(statePayload))) } - // verify that the returned timestamp is valid - if err := cryptutil.ValidTimestamp(statePayload[1]); err != nil { - return nil, httputil.NewError(http.StatusBadRequest, err) - } - // Use our AEAD construct to enforce secrecy and authenticity: // mac: to validate the nonce again, and above timestamp // decrypt: to prevent leaking 'redirect_uri' to IdP or logs @@ -401,6 +396,17 @@ func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request) return nil, httputil.NewError(http.StatusBadRequest, err) } + // verify that the returned timestamp is valid + if err := cryptutil.ValidTimestamp(statePayload[1]); err != nil { + return nil, httputil.NewError(http.StatusBadRequest, err).WithDescription(fmt.Sprintf(` +The request expired. This may be because a login attempt took too long, or because the server's clock is out of sync. + +Try again by following this link: [%s](%s). + +Or contact your administrator. +`, redirectURL.String(), redirectURL.String())) + } + idp, err := options.GetIdentityProviderForID(redirectURL.Query().Get(urlutil.QueryIdentityProviderID)) if err != nil { return nil, err diff --git a/internal/httputil/errors.go b/internal/httputil/errors.go index 0218f072c..3b4408913 100644 --- a/internal/httputil/errors.go +++ b/internal/httputil/errors.go @@ -16,7 +16,8 @@ type HTTPError struct { // HTTP status codes as registered with IANA. Status int // Err is the wrapped error. - Err error + Err error + Description string // DebugURL is the URL to the debug endpoint. DebugURL *url.URL // The request ID. @@ -26,7 +27,7 @@ type HTTPError struct { } // NewError returns an error that contains a HTTP status and error. -func NewError(status int, err error) error { +func NewError(status int, err error) *HTTPError { return &HTTPError{Status: status, Err: err} } @@ -54,6 +55,7 @@ func (e *HTTPError) ErrorResponse(ctx context.Context, w http.ResponseWriter, r response := struct { Status int StatusText string `json:"-"` + Description string `json:"description,omitempty"` RequestID string `json:",omitempty"` CanDebug bool `json:"-"` DebugURL *url.URL `json:",omitempty"` @@ -61,6 +63,7 @@ func (e *HTTPError) ErrorResponse(ctx context.Context, w http.ResponseWriter, r }{ Status: e.Status, StatusText: StatusText(e.Status), + Description: e.Description, RequestID: reqID, CanDebug: e.Status/100 == 4 && (e.DebugURL != nil || reqID != ""), DebugURL: e.DebugURL, @@ -85,6 +88,7 @@ func (e *HTTPError) ErrorResponse(ctx context.Context, w http.ResponseWriter, r m := map[string]any{ "canDebug": response.CanDebug, + "description": response.Description, "requestId": response.RequestID, "status": response.Status, "statusText": response.StatusText, @@ -101,3 +105,9 @@ func (e *HTTPError) ErrorResponse(ctx context.Context, w http.ResponseWriter, r http.Error(w, err.Error(), http.StatusInternalServerError) } } + +// WithDescription sets the description in the HTTP error. +func (e *HTTPError) WithDescription(description string) *HTTPError { + e.Description = description + return e +} diff --git a/ui/src/components/ErrorPage.tsx b/ui/src/components/ErrorPage.tsx index 6e4b33200..a903e558e 100644 --- a/ui/src/components/ErrorPage.tsx +++ b/ui/src/components/ErrorPage.tsx @@ -62,6 +62,11 @@ export const ErrorPage: FC = ({ data }) => { {data?.status || 500}{" "} {data?.statusText || "Internal Server Error"} + {data?.description ? ( + {data.description} + ) : ( + <> + )} {!!data?.errorMessageFirstParagraph && ( diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 26e65a0a7..dbf62c207 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -104,6 +104,7 @@ export type ErrorPageData = BasePageData & { requestId?: string; status?: number; statusText?: string; + description?: string; errorMessageFirstParagraph?: string; policyEvaluationTraces?: PolicyEvaluationTrace[]; };