forked from wrenn/wrenn
Co-authored-by: Tasnim Kabir Sadik <tksadik@omukk.dev> Reviewed-on: wrenn/wrenn#50
39 lines
1.2 KiB
Go
39 lines
1.2 KiB
Go
package api
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net/http"
|
|
|
|
"git.omukk.dev/wrenn/wrenn/pkg/auth"
|
|
)
|
|
|
|
// requireCSRF enforces double-submit CSRF: the wrenn_csrf cookie value must
|
|
// equal the X-CSRF-Token header. Skipped for safe methods (GET/HEAD/OPTIONS)
|
|
// and for requests authenticated via X-API-Key (SDK clients are not
|
|
// vulnerable to cross-site request forgery against cookie auth).
|
|
func requireCSRF() func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodGet, http.MethodHead, http.MethodOptions:
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
// API-key auth path: no CSRF check needed.
|
|
if ac, ok := auth.FromContext(r.Context()); ok && ac.APIKeyID.Valid {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
cookie, err := r.Cookie(csrfCookieName)
|
|
header := r.Header.Get("X-CSRF-Token")
|
|
if err != nil || cookie.Value == "" || header == "" ||
|
|
subtle.ConstantTimeCompare([]byte(cookie.Value), []byte(header)) != 1 {
|
|
writeError(w, http.StatusForbidden, "csrf_failed", "missing or invalid CSRF token")
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|