/
handler.go
97 lines (80 loc) · 2.51 KB
/
handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package csrf
import (
"net/http"
"net/url"
"time"
"github.com/Nigel2392/router/v3"
"github.com/Nigel2392/router/v3/request"
)
const (
CSRF_TOKEN_COOKIE_NAME = "csrf_token"
CSRF_TOKEN_HEADER_NAME = "X-CSRF-Token"
CSRF_TOKEN_FORMFIELD_NAME = "csrf_token"
CSRF_TOKEN_COOKIE_EXPIRE = time.Hour * 24
CSRF_TOKEN_COOKIE_MAX_AGE = 3600
CSRF_TOKEN_COOKIE_SECURE = false
CSRF_TOKEN_COOKIE_HTTP_ONLY = true
CSRF_COOKIE_SAME_SITE = http.SameSiteStrictMode
)
func Middleware(next router.Handler) router.Handler {
return router.HandleFunc(func(req *request.Request) {
defaultContext(req)
req.AddHeader("Vary", "Cookie")
var realToken []byte
tokenCookie, err := req.GetCookie(CSRF_TOKEN_COOKIE_NAME)
if err == nil {
realToken = b64decode(tokenCookie.Value)
}
if len(realToken) != tokenLength {
var t = generateToken()
contextSaveToken(req, b64encode(maskToken(realToken)))
var cookie = &http.Cookie{
Name: CSRF_TOKEN_COOKIE_NAME,
Value: b64encode(t),
HttpOnly: CSRF_TOKEN_COOKIE_HTTP_ONLY,
Secure: CSRF_TOKEN_COOKIE_SECURE,
Path: "/",
SameSite: CSRF_COOKIE_SAME_SITE,
}
req.SetCookies(cookie)
} else {
contextSaveToken(req, b64encode(maskToken(realToken)))
}
if req.Data == nil {
req.Data = request.NewTemplateData()
}
req.Data.CSRFToken = request.NewCSRFToken(Token(req))
// Check if the request method is safe.
if !unsafeMethods.Contains(req.Method()) {
// Continue to the next handler.
next.ServeHTTP(req)
return
}
if req.Request.URL.Scheme == "https" {
referer, err := url.Parse(req.GetHeader("Referer"))
// if we can't parse the referer or it's empty,
// we assume it's not specified
if err != nil || referer.String() == "" {
// Error: Referer not specified
req.Error(http.StatusForbidden, ErrRefererNotSpecified)
return
}
// if the referer doesn't share origin with the request URL,
// we have another error for that
if referer.Scheme != req.Request.URL.Scheme || referer.Host != req.Request.URL.Host {
// Error: Referer mismatch
req.Error(http.StatusForbidden, ErrRefererMismatch)
return
}
}
// Finally, we check the token itself.
sentToken := extractToken(req)
if !verifyToken(realToken, sentToken) {
// Error: Token mismatch
req.Error(http.StatusForbidden, ErrTokenMismatch)
return
}
// Continue to the next handler.
next.ServeHTTP(req)
})
}