F03-01: go-oidc embeds the provider's response body in its error for a non-200 discovery response, which cmd/server logged at startup. Discovery now runs through a status-recording transport and a non-200 response is reported as "provider answered HTTP <status>" only; transport errors keep their cause. A test with a sentinel body proves it reaches neither the error nor the log. F03-02: the CSRF middleware exempts only GET, so HEAD and OPTIONS under /api/v1 without X-MealQuest-CSRF get 403 csrf, matching CONTEXT.MD. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
43 lines
1.3 KiB
Go
43 lines
1.3 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"log/slog"
|
|
"net/http"
|
|
|
|
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/auth"
|
|
)
|
|
|
|
// CSRFHeader carries the session's CSRF token, which the client reads from
|
|
// GET /api/v1/me.
|
|
const CSRFHeader = "X-MealQuest-CSRF"
|
|
|
|
// requireCSRF passes GET requests to next; every other
|
|
// request must carry CSRFHeader equal to its session's CSRF token or get 403
|
|
// csrf. It runs behind requireSession, which puts the session in the context.
|
|
func requireCSRF(logger *slog.Logger) func(http.Handler) http.Handler {
|
|
forbidden := apiErrorHandler(logger, http.StatusForbidden, "csrf", "missing or invalid CSRF token")
|
|
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) {
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
s, ok := auth.SessionFrom(r.Context())
|
|
if !ok || s.CSRFToken == "" {
|
|
// An empty token would match a missing header.
|
|
logger.ErrorContext(r.Context(), "csrf check without a session token", "session", ok)
|
|
internal(w, r)
|
|
return
|
|
}
|
|
if subtle.ConstantTimeCompare([]byte(r.Header.Get(CSRFHeader)), []byte(s.CSRFToken)) != 1 {
|
|
forbidden(w, r)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|