Add CSRF checks to login.
parent
91c7591c19
commit
ff4e05fd0b
|
|
@ -20,6 +20,7 @@ type PageData struct {
|
||||||
CurrentURL string
|
CurrentURL string
|
||||||
ContentTemplate string
|
ContentTemplate string
|
||||||
BodyClass string
|
BodyClass string
|
||||||
|
CSRFToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,11 @@ func LoginHandler(app *App) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
session := r.Context().Value("session").(*sessions.Session)
|
session := r.Context().Value("session").(*sessions.Session)
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
|
if !app.validateCSRFToken(r, session) {
|
||||||
|
http.Error(w, "Invalid CSRF token", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
username := r.FormValue("username")
|
username := r.FormValue("username")
|
||||||
password := r.FormValue("password")
|
password := r.FormValue("password")
|
||||||
user, err := models.GetUserByUsername(app.DB, username)
|
user, err := models.GetUserByUsername(app.DB, username)
|
||||||
|
|
@ -53,6 +58,7 @@ func LoginHandler(app *App) http.HandlerFunc {
|
||||||
BasePath: app.Config.ThreadrDir,
|
BasePath: app.Config.ThreadrDir,
|
||||||
StaticPath: app.Config.ThreadrDir + "/static",
|
StaticPath: app.Config.ThreadrDir + "/static",
|
||||||
CurrentURL: r.URL.RequestURI(),
|
CurrentURL: r.URL.RequestURI(),
|
||||||
|
CSRFToken: app.csrfToken(session),
|
||||||
},
|
},
|
||||||
Error: "",
|
Error: "",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
<p class="field-error" style="text-align: center; font-size: 1em;">{{.Error}}</p>
|
<p class="field-error" style="text-align: center; font-size: 1em;">{{.Error}}</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
<form method="post" action="{{.BasePath}}/login/">
|
<form method="post" action="{{.BasePath}}/login/">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||||
<label for="username">Username:</label>
|
<label for="username">Username:</label>
|
||||||
<input type="text" id="username" name="username" required autocomplete="username"><br>
|
<input type="text" id="username" name="username" required autocomplete="username"><br>
|
||||||
<label for="password">Password:</label>
|
<label for="password">Password:</label>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue