Files
website/internal/platform/csrf/csrf.go
2025-10-29 08:36:10 +00:00

64 lines
2.1 KiB
Go

// Package csrf
// Path: /internal/platform/csrf
// File: csrf.go
//
// Purpose
//
// Centralized CSRF protection wrapper using justinas/nosurf.
// Applies default CSRF protections across the entire HTTP handler tree
// after SCS session load/save wrapping.
//
// Responsibilities (as implemented here)
// 1. Construct a nosurf middleware handler over the provided http.Handler.
// 2. Configure the base CSRF cookie using values from the App configuration.
// 3. Enforce HttpOnly and SameSite=Lax defaults.
// 4. Enable Secure flag automatically in production mode.
//
// HTTP stack order (per bootstrap)
//
// Gin Router → SCS LoadAndSave → CSRF Wrapper → http.Server
//
// Design notes
// - The nosurf package automatically:
// - Inserts CSRF token into responses (e.g., via nosurf.Token(c.Request))
// - Validates token on state-changing requests (POST, PUT, etc.)
// - CSRF cookie name is configurable via config.Config.
// - Secure flag is tied to cfg.HttpServer.ProductionMode (recommended).
// - Global protection: all routed POSTs are covered automatically.
//
// TODOs (observations from current implementation)
// - Expose helper to fetch token into Gin templates via context key.
// - Consider SameSiteStrictMode once OAuth/external logins are defined.
// - Add domain and MaxAge settings for more precise control.
// - Provide per-route opt-outs if needed for webhook endpoints.
//
// Change log
//
// [2025-10-29] Documentation updated to reflect middleware position and cookie policy.
package csrf
import (
"net/http"
"synlotto-website/internal/platform/config"
"github.com/justinas/nosurf"
)
// Wrap applies nosurf CSRF middleware to the given handler,
// configuring the CSRF cookie based on App configuration.
//
// Caller must ensure this is positioned *outside* SCS LoadAndSave
// so CSRF can access session data when generating/validating tokens.
func Wrap(h http.Handler, cfg config.Config) http.Handler {
cs := nosurf.New(h)
cs.SetBaseCookie(http.Cookie{
Name: cfg.CSRF.CookieName,
Path: "/",
HttpOnly: true,
Secure: cfg.HttpServer.ProductionMode,
SameSite: http.SameSiteLaxMode,
})
return cs
}