From ff4e05fd0b3df262082147d6a873b0d9accae919 Mon Sep 17 00:00:00 2001 From: Jocadbz Date: Fri, 6 Mar 2026 14:50:50 -0300 Subject: [PATCH] Add CSRF checks to login. --- handlers/app.go | 1 + handlers/csrf.go | 55 ++++++++++++++++++++++++++++++++++++++ handlers/login.go | 6 +++++ templates/pages/login.html | 3 ++- 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 handlers/csrf.go diff --git a/handlers/app.go b/handlers/app.go index ffc0881..b5ca225 100644 --- a/handlers/app.go +++ b/handlers/app.go @@ -20,6 +20,7 @@ type PageData struct { CurrentURL string ContentTemplate string BodyClass string + CSRFToken string } type Config struct { diff --git a/handlers/csrf.go b/handlers/csrf.go new file mode 100644 index 0000000..9b7eee8 --- /dev/null +++ b/handlers/csrf.go @@ -0,0 +1,55 @@ +package handlers + +import ( + "crypto/rand" + "crypto/subtle" + "encoding/base64" + "net/http" + + "github.com/gorilla/sessions" +) + +const csrfSessionKey = "csrf_token" + +func (app *App) ensureCSRFToken(session *sessions.Session) (string, error) { + if token, ok := session.Values[csrfSessionKey].(string); ok && token != "" { + return token, nil + } + + raw := make([]byte, 32) + if _, err := rand.Read(raw); err != nil { + return "", err + } + + token := base64.RawURLEncoding.EncodeToString(raw) + session.Values[csrfSessionKey] = token + return token, nil +} + +func (app *App) csrfToken(session *sessions.Session) string { + token, err := app.ensureCSRFToken(session) + if err != nil { + return "" + } + return token +} + +func (app *App) validateCSRFToken(r *http.Request, session *sessions.Session) bool { + expected, ok := session.Values[csrfSessionKey].(string) + if !ok || expected == "" { + return false + } + + provided := r.Header.Get("X-CSRF-Token") + if provided == "" { + provided = r.FormValue("csrf_token") + } + if provided == "" { + provided = r.URL.Query().Get("csrf_token") + } + if len(provided) != len(expected) { + return false + } + + return subtle.ConstantTimeCompare([]byte(provided), []byte(expected)) == 1 +} diff --git a/handlers/login.go b/handlers/login.go index 71dd808..c2d84db 100644 --- a/handlers/login.go +++ b/handlers/login.go @@ -13,6 +13,11 @@ func LoginHandler(app *App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { session := r.Context().Value("session").(*sessions.Session) if r.Method == http.MethodPost { + if !app.validateCSRFToken(r, session) { + http.Error(w, "Invalid CSRF token", http.StatusForbidden) + return + } + username := r.FormValue("username") password := r.FormValue("password") user, err := models.GetUserByUsername(app.DB, username) @@ -53,6 +58,7 @@ func LoginHandler(app *App) http.HandlerFunc { BasePath: app.Config.ThreadrDir, StaticPath: app.Config.ThreadrDir + "/static", CurrentURL: r.URL.RequestURI(), + CSRFToken: app.csrfToken(session), }, Error: "", } diff --git a/templates/pages/login.html b/templates/pages/login.html index 7ed6371..01f7b17 100644 --- a/templates/pages/login.html +++ b/templates/pages/login.html @@ -17,6 +17,7 @@

{{.Error}}

{{end}}
+
@@ -28,4 +29,4 @@ {{template "cookie_banner" .}} -{{end}} \ No newline at end of file +{{end}}