Add CSRF checks to login.
parent
91c7591c19
commit
ff4e05fd0b
|
|
@ -20,6 +20,7 @@ type PageData struct {
|
|||
CurrentURL string
|
||||
ContentTemplate string
|
||||
BodyClass string
|
||||
CSRFToken string
|
||||
}
|
||||
|
||||
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) {
|
||||
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: "",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -28,4 +29,4 @@
|
|||
{{template "cookie_banner" .}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
Loading…
Reference in New Issue