Add CSRF checks to login.

jocadbz
Joca 2026-03-06 14:50:50 -03:00
parent 91c7591c19
commit ff4e05fd0b
Signed by: jocadbz
GPG Key ID: B1836DCE2F50BDF7
4 changed files with 64 additions and 1 deletions

View File

@ -20,6 +20,7 @@ type PageData struct {
CurrentURL string
ContentTemplate string
BodyClass string
CSRFToken string
}
type Config struct {

55
handlers/csrf.go Normal file
View File

@ -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
}

View File

@ -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: "",
}

View File

@ -17,6 +17,7 @@
<p class="field-error" style="text-align: center; font-size: 1em;">{{.Error}}</p>
{{end}}
<form method="post" action="{{.BasePath}}/login/">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required autocomplete="username"><br>
<label for="password">Password:</label>