-
Notifications
You must be signed in to change notification settings - Fork 187
/
cookie_store.go
165 lines (142 loc) · 5.15 KB
/
cookie_store.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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package sessions
import (
"errors"
"fmt"
"net"
"net/http"
"strings"
"time"
"github.com/buzzfeed/sso/internal/pkg/aead"
log "github.com/buzzfeed/sso/internal/pkg/logging"
)
// ErrInvalidSession is an error for invalid sessions.
var ErrInvalidSession = errors.New("invalid session")
// CSRFStore has the functions for setting, getting, and clearing the CSRF cookie
type CSRFStore interface {
SetCSRF(http.ResponseWriter, *http.Request, string)
GetCSRF(*http.Request) (*http.Cookie, error)
ClearCSRF(http.ResponseWriter, *http.Request)
}
// SessionStore has the functions for setting, getting, and clearing the Session cookie
type SessionStore interface {
ClearSession(http.ResponseWriter, *http.Request)
LoadSession(*http.Request) (*SessionState, error)
SaveSession(http.ResponseWriter, *http.Request, *SessionState) error
}
// CookieStore represents all the cookie related configurations
type CookieStore struct {
Name string
CSRFCookieName string
CookieExpire time.Duration
CookieRefresh time.Duration
CookieSecure bool
CookieHTTPOnly bool
CookieDomain string
CookieCipher aead.Cipher
SessionLifetimeTTL time.Duration
}
// CreateMiscreantCookieCipher creates a new miscreant cipher with the cookie secret
func CreateMiscreantCookieCipher(cookieSecret []byte) func(s *CookieStore) error {
return func(s *CookieStore) error {
cipher, err := aead.NewMiscreantCipher(cookieSecret)
if err != nil {
return fmt.Errorf("miscreant cookie-secret error: %s", err.Error())
}
s.CookieCipher = cipher
return nil
}
}
// NewCookieStore returns a new session with ciphers for each of the cookie secrets
func NewCookieStore(cookieName string, optFuncs ...func(*CookieStore) error) (*CookieStore, error) {
c := &CookieStore{
Name: cookieName,
CookieSecure: true,
CookieHTTPOnly: true,
CookieExpire: 168 * time.Hour,
CSRFCookieName: fmt.Sprintf("%v_%v", cookieName, "csrf"),
}
for _, f := range optFuncs {
err := f(c)
if err != nil {
return nil, err
}
}
domain := c.CookieDomain
if domain == "" {
domain = "<default>"
}
return c, nil
}
func (s *CookieStore) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
logger := log.NewLogEntry()
domain := req.Host
if h, _, err := net.SplitHostPort(domain); err == nil {
domain = h
}
if s.CookieDomain != "" {
if !strings.HasSuffix(domain, s.CookieDomain) {
logger.WithRequestHost(domain).WithCookieDomain(s.CookieDomain).Warn("Warning: Using configured cookie domain.")
}
domain = s.CookieDomain
}
return &http.Cookie{
Name: name,
Value: value,
Path: "/",
Domain: domain,
HttpOnly: s.CookieHTTPOnly,
Secure: s.CookieSecure,
Expires: now.Add(expiration),
}
}
// makeSessionCookie constructs a session cookie given the request, an expiration time and the current time.
func (s *CookieStore) makeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
return s.makeCookie(req, s.Name, value, expiration, now)
}
// makeCSRFCookie creates a CSRF cookie given the request, an expiration time, and the current time.
func (s *CookieStore) makeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
return s.makeCookie(req, s.CSRFCookieName, value, expiration, now)
}
// ClearCSRF clears the CSRF cookie from the request
func (s *CookieStore) ClearCSRF(rw http.ResponseWriter, req *http.Request) {
http.SetCookie(rw, s.makeCSRFCookie(req, "", time.Hour*-1, time.Now()))
}
// SetCSRF sets the CSRFCookie creates a CSRF cookie in a given request
func (s *CookieStore) SetCSRF(rw http.ResponseWriter, req *http.Request, val string) {
http.SetCookie(rw, s.makeCSRFCookie(req, val, s.CookieExpire, time.Now()))
}
// GetCSRF gets the CSRFCookie creates a CSRF cookie in a given request
func (s *CookieStore) GetCSRF(req *http.Request) (*http.Cookie, error) {
return req.Cookie(s.CSRFCookieName)
}
// ClearSession clears the session cookie from a request
func (s *CookieStore) ClearSession(rw http.ResponseWriter, req *http.Request) {
http.SetCookie(rw, s.makeSessionCookie(req, "", time.Hour*-1, time.Now()))
}
func (s *CookieStore) setSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
http.SetCookie(rw, s.makeSessionCookie(req, val, s.CookieExpire, time.Now()))
}
// LoadSession returns a SessionState from the cookie in the request.
func (s *CookieStore) LoadSession(req *http.Request) (*SessionState, error) {
logger := log.NewLogEntry()
c, err := req.Cookie(s.Name)
if err != nil {
// always http.ErrNoCookie
return nil, err
}
session, err := UnmarshalSession(c.Value, s.CookieCipher)
if err != nil {
logger.WithRequestHost(req.Host).WithError(err).Error("error unmarshaling session")
return nil, ErrInvalidSession
}
return session, nil
}
// SaveSession saves a session state to a request sessions.
func (s *CookieStore) SaveSession(rw http.ResponseWriter, req *http.Request, sessionState *SessionState) error {
value, err := MarshalSession(sessionState, s.CookieCipher)
if err != nil {
return err
}
s.setSessionCookie(rw, req, value)
return nil
}