spread

https://git.tonybtw.com/spread.git git://git.tonybtw.com/spread.git
1,297 bytes raw
1
package app
2
3
import (
4
	"context"
5
	"crypto/subtle"
6
	"net/http"
7
)
8
9
const csrfCookie = "spread_csrf"
10
11
// csrf implements double-submit-cookie CSRF protection: a random token is kept
12
// in an httponly cookie and echoed into every form; unsafe requests must send a
13
// matching csrf_token field. Combined with the SameSite=Lax session/csrf
14
// cookies, this blocks cross-site state changes.
15
func (a *App) csrf(next http.Handler) http.Handler {
16
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17
		tok := ""
18
		if c, err := r.Cookie(csrfCookie); err == nil {
19
			tok = c.Value
20
		}
21
		if tok == "" {
22
			tok = randomToken()
23
			http.SetCookie(w, &http.Cookie{
24
				Name:     csrfCookie,
25
				Value:    tok,
26
				Path:     "/",
27
				HttpOnly: true,
28
				Secure:   a.secureCookie,
29
				SameSite: http.SameSiteLaxMode,
30
			})
31
		}
32
33
		switch r.Method {
34
		case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
35
			if subtle.ConstantTimeCompare([]byte(r.PostFormValue("csrf_token")), []byte(tok)) != 1 {
36
				http.Error(w, "invalid or missing CSRF token", http.StatusForbidden)
37
				return
38
			}
39
		}
40
41
		next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), csrfKey, tok)))
42
	})
43
}
44
45
func csrfToken(r *http.Request) string {
46
	t, _ := r.Context().Value(csrfKey).(string)
47
	return t
48
}