Files
Trong Huu Nguyen 0d6d4a0dff fix(http): encode the unauthenticated response as JSON
The response was built by concatenating the request path into a JSON
string. The path is percent-decoded, so it can contain quotes and
backslashes, which produced malformed JSON and let a caller inject
arbitrary keys into the object.
2026-07-28 11:43:54 +02:00

43 lines
1.6 KiB
Go

package http
import (
"encoding/json"
"net/http"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
// DisallowNonNavigationalRequests checks if the request is non-navigational, and if so, responds with a 401.
// We do this to separate between redirects for browser navigation and redirects for resource requests.
//
// This should only be used for endpoints that are only supposed to be _navigated to_ from a browser.
// The 401 response prevents redirecting non-navigation requests to the identity provider, which usually results in
// a CORS error for typical Fetch or XHR requests from the browser.
//
// This depends on the presence of the Fetch metadata headers, mostly present in modern browsers.
// For compatibility with older browsers, requests without these headers are still allowed to pass through.
func DisallowNonNavigationalRequests(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if HasSecFetchMetadata(r) && !IsNavigationRequest(r) {
span := trace.SpanFromContext(r.Context())
span.SetAttributes(attribute.Bool("request.disallowed", true))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
ErrorPath string `json:"error_path"`
}{
Error: "unauthenticated",
ErrorDescription: "this is an interactive endpoint; user-agents must be navigated to this endpoint",
ErrorPath: r.URL.Path,
})
return
}
next.ServeHTTP(w, r)
})
}