1package csrf
2
3import (
4	"net/http"
5	"time"
6
7	"github.com/gorilla/securecookie"
8)
9
10// store represents the session storage used for CSRF tokens.
11type store interface {
12	// Get returns the real CSRF token from the store.
13	Get(*http.Request) ([]byte, error)
14	// Save stores the real CSRF token in the store and writes a
15	// cookie to the http.ResponseWriter.
16	// For non-cookie stores, the cookie should contain a unique (256 bit) ID
17	// or key that references the token in the backend store.
18	// csrf.GenerateRandomBytes is a helper function for generating secure IDs.
19	Save(token []byte, w http.ResponseWriter) error
20}
21
22// cookieStore is a signed cookie session store for CSRF tokens.
23type cookieStore struct {
24	name     string
25	maxAge   int
26	secure   bool
27	httpOnly bool
28	path     string
29	domain   string
30	sc       *securecookie.SecureCookie
31}
32
33// Get retrieves a CSRF token from the session cookie. It returns an empty token
34// if decoding fails (e.g. HMAC validation fails or the named cookie doesn't exist).
35func (cs *cookieStore) Get(r *http.Request) ([]byte, error) {
36	// Retrieve the cookie from the request
37	cookie, err := r.Cookie(cs.name)
38	if err != nil {
39		return nil, err
40	}
41
42	token := make([]byte, tokenLength)
43	// Decode the HMAC authenticated cookie.
44	err = cs.sc.Decode(cs.name, cookie.Value, &token)
45	if err != nil {
46		return nil, err
47	}
48
49	return token, nil
50}
51
52// Save stores the CSRF token in the session cookie.
53func (cs *cookieStore) Save(token []byte, w http.ResponseWriter) error {
54	// Generate an encoded cookie value with the CSRF token.
55	encoded, err := cs.sc.Encode(cs.name, token)
56	if err != nil {
57		return err
58	}
59
60	cookie := &http.Cookie{
61		Name:     cs.name,
62		Value:    encoded,
63		MaxAge:   cs.maxAge,
64		HttpOnly: cs.httpOnly,
65		Secure:   cs.secure,
66		Path:     cs.path,
67		Domain:   cs.domain,
68	}
69
70	// Set the Expires field on the cookie based on the MaxAge
71	// If MaxAge <= 0, we don't set the Expires attribute, making the cookie
72	// session-only.
73	if cs.maxAge > 0 {
74		cookie.Expires = time.Now().Add(
75			time.Duration(cs.maxAge) * time.Second)
76	}
77
78	// Write the authenticated cookie to the response.
79	http.SetCookie(w, cookie)
80
81	return nil
82}
83