package handlers import ( "html/template" "net/http" "github.com/gorilla/csrf" "sneak.berlin/go/pixa/internal/seal" ) // csrfKeySalt provides domain separation for the CSRF authentication key, // derived from the signing key so tokens survive restarts without extra // configuration and never reuse the session or encrypted-URL key material. const csrfKeySalt = "pixa-csrf-v1" // newCSRFProtect builds the CSRF-protection middleware for the // state-mutating HTML form routes. The token lives in its own cookie, // independent of the session cookie, so it also protects the login POST // where no session exists yet (login CSRF). // // When plaintext is true (local HTTP development), requests are marked // plaintext so the library neither demands an https Referer nor sets a // Secure cookie the browser would withhold over http. In production the // service runs behind a TLS-terminating proxy, so plaintext is false and // the library enforces its https Referer origin check. func newCSRFProtect( signingKey string, plaintext bool, ) (func(http.Handler) http.Handler, error) { key, err := seal.DeriveKey([]byte(signingKey), csrfKeySalt) if err != nil { return nil, err } protect := csrf.Protect( key[:], csrf.Path("/"), csrf.Secure(!plaintext), csrf.SameSite(csrf.SameSiteStrictMode), ) if !plaintext { return protect, nil } return func(next http.Handler) http.Handler { protected := protect(next) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { protected.ServeHTTP(w, csrf.PlaintextHTTPRequest(r)) }) }, nil } // CSRF returns the CSRF-protection middleware for the login and generator // form routes. func (s *Handlers) CSRF() func(http.Handler) http.Handler { return s.csrfProtect } // csrfField returns the hidden form input carrying the CSRF token for the // given request, to be embedded verbatim in a rendered form. func csrfField(r *http.Request) template.HTML { return csrf.TemplateField(r) }