package middleware import ( "context" "net/http" "code.nonshy.com/nonshy/website/pkg/config" "code.nonshy.com/nonshy/website/pkg/log" "code.nonshy.com/nonshy/website/pkg/session" "code.nonshy.com/nonshy/website/pkg/templates" "github.com/google/uuid" ) // CSRF middleware. Other places to look: pkg/session/session.go, pkg/templates/template_funcs.go func CSRF(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get or create the cookie CSRF value. token := MakeCSRFCookie(r, w) ctx := context.WithValue(r.Context(), session.CSRFKey, token) // If it's a JSON post, allow it thru. if r.Header.Get("Content-Type") == "application/json" { handler.ServeHTTP(w, r.WithContext(ctx)) return } // If we are running a POST request, validate the CSRF form value. if r.Method != http.MethodGet { r.ParseMultipartForm(config.MultipartMaxMemory) check := r.FormValue(config.CSRFInputName) if check != token { log.Error("CSRF mismatch! %s <> %s", check, token) templates.MakeErrorPage( "CSRF Error", "An error occurred while processing your request. Please go back and try again.", http.StatusForbidden, )(w, r.WithContext(ctx)) return } } handler.ServeHTTP(w, r.WithContext(ctx)) }) } // MakeCSRFCookie gets or creates the CSRF cookie and returns its value. func MakeCSRFCookie(r *http.Request, w http.ResponseWriter) string { // Has a token already? cookie, err := r.Cookie(config.CSRFCookieName) if err == nil { // log.Debug("MakeCSRFCookie: user has token %s", cookie.Value) return cookie.Value } // Generate a new CSRF token. token := uuid.New().String() cookie = &http.Cookie{ Name: config.CSRFCookieName, Value: token, HttpOnly: true, Path: "/", } // log.Debug("MakeCSRFCookie: giving cookie value %s to user", token) http.SetCookie(w, cookie) return token }