Add internal/forwarded: parse the trusted proxy list (comma-separated IPs and CIDRs, validated at load), resolve each request's client IP and scheme, and expose them through context helpers. X-Forwarded-For and X-Forwarded-Proto are honoured only when the TCP peer is trusted; the client is the rightmost X-Forwarded-For address that is not a trusted proxy. Malformed forwarded headers from a trusted peer get 400. The router resolves every request first; a slog handler adds client_ip to request-scoped log lines. The auth routes refuse requests whose resolved scheme differs from MEALQUEST_PUBLIC_URL's, so a proxy that is not trusted fails loudly instead of breaking Secure cookies. The OIDC redirect URI is still built only from MEALQUEST_PUBLIC_URL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
35 lines
1.3 KiB
Go
35 lines
1.3 KiB
Go
package server
|
|
|
|
import (
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/forwarded"
|
|
)
|
|
|
|
// resolveOrigin resolves every request's client address and scheme with
|
|
// forwarded.Resolve and puts them in the context (read them with
|
|
// forwarded.ClientIP and forwarded.Scheme). Forwarded headers are honoured
|
|
// only from a peer in trusted. A malformed forwarded header from a trusted
|
|
// peer gets 400 bad_request.
|
|
func resolveOrigin(logger *slog.Logger, trusted forwarded.Trusted) func(http.Handler) http.Handler {
|
|
badRequest := apiErrorHandler(logger, http.StatusBadRequest, "bad_request", "invalid forwarded headers")
|
|
internal := apiErrorHandler(logger, http.StatusInternalServerError, "internal", "internal error")
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
o, err := forwarded.Resolve(r, trusted)
|
|
switch {
|
|
case errors.Is(err, forwarded.ErrInvalidHeader):
|
|
logger.WarnContext(r.Context(), "rejected forwarded headers", "peer", r.RemoteAddr, "error", err)
|
|
badRequest(w, r)
|
|
case err != nil:
|
|
logger.ErrorContext(r.Context(), "resolve request origin", "error", err)
|
|
internal(w, r)
|
|
default:
|
|
next.ServeHTTP(w, r.WithContext(forwarded.NewContext(r.Context(), o)))
|
|
}
|
|
})
|
|
}
|
|
}
|