forked from zerodha/simplesessions
-
Notifications
You must be signed in to change notification settings - Fork 0
/
session.go
337 lines (278 loc) · 9.16 KB
/
session.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
package simplesessions
import (
"crypto/rand"
"errors"
"net/http"
"time"
"unicode"
)
// Session is utility for get, set or clear session.
type Session struct {
// Map to store session data which can be loaded using `Load` method.
// Get session method check if the field is available here before getting from store directly.
values map[string]interface{}
// Session manager.
manager *Manager
// Current http cookie. This is passed down to `SetCookie` callback.
cookie *http.Cookie
// HTTP reader and writer interfaces which are passed on to
// `GetCookie`` and `SetCookie`` callback respectively.
reader interface{}
writer interface{}
// Track if session is set in store or not
// used to throw and error is autoSet is not enabled and user
// explicitly didn't create new session in store.
isSet bool
}
var (
// ErrInvalidSession is raised when session is tried to access before setting it or its not set in store.
// Handle this and create new session.
ErrInvalidSession = errors.New("invalid session")
// ErrFieldNotFound is raised when given key is not found in store
ErrFieldNotFound = errors.New("session field not found in store")
// ErrAssertType is raised when type assertion fails
ErrAssertType = errors.New("invalid type assertion")
// Dictionary for generating random string
dictionary = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
)
// NewSession creates a new session. Reads cookie info from `GetCookie`` callback
// and validate the session with current store. If cookie not set then it creates
// new session and calls `SetCookie`` callback. If `DisableAutoSet` is set then it
// skips new session creation and should be manually done using `Create` method.
// If a cookie is found but its invalid in store then `ErrInvalidSession` error is returned.
func NewSession(m *Manager, r, w interface{}) (*Session, error) {
var (
err error
sess = &Session{
manager: m,
reader: r,
writer: w,
values: make(map[string]interface{}),
}
)
// Get existing http session cookie
sess.cookie, err = m.getCookieCb(m.opts.CookieName, r)
// Create new session
if err == http.ErrNoCookie {
// Skip creating new cookie in store. User has to manually create before doing Get or Set.
if m.opts.DisableAutoSet {
return sess, nil
}
// Create new cookie in store and write to front
// Store also calls `WriteCookie`` to write to http interface
cv, err := m.store.Create(sess)
if err != nil {
return nil, err
}
// Write cookie
if err := sess.WriteCookie(cv); err != nil {
return nil, err
}
// Set isSet flag
sess.isSet = true
} else if err != nil {
return nil, err
}
if isValid, err := m.store.IsValid(sess, sess.cookie.Value); err != nil {
return nil, err
} else if !isValid {
return nil, ErrInvalidSession
}
// Set isSet flag
sess.isSet = true
return sess, nil
}
// GenerateRandomString is a utility method which can be used by store to
// generate cryptographically random alphanumeric string of length n.
func (s *Session) GenerateRandomString(n int) (string, error) {
var bytes = make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
for k, v := range bytes {
bytes[k] = dictionary[v%byte(len(dictionary))]
}
return string(bytes), nil
}
// IsValidRandomString validates the random string generated by `GenerateRandomString` method.
func (s *Session) IsValidRandomString(val string) bool {
return s.isAlphaNum(val)
}
// isAlphaNum checks if the provided string is Alphanumeric
func (s *Session) isAlphaNum(val string) bool {
for _, r := range val {
if !unicode.IsDigit(r) && !unicode.IsLetter(r) {
return false
}
}
return true
}
// WriteCookie updates the cookie and calls `SetCookie` callback.
// This method can also be used by store to update cookie whenever the cookie value changes.
func (s *Session) WriteCookie(cv string) error {
s.cookie = &http.Cookie{
Value: cv,
Name: s.manager.opts.CookieName,
Domain: s.manager.opts.CookieDomain,
Path: s.manager.opts.CookiePath,
Secure: s.manager.opts.IsSecureCookie,
HttpOnly: s.manager.opts.IsHTTPOnlyCookie,
}
// Set cookie expiry
if s.manager.opts.CookieLifetime != 0 {
s.cookie.Expires = time.Now().Add(s.manager.opts.CookieLifetime)
}
// Call `SetCookie` callback to write cookie to response
return s.manager.setCookieCb(s.cookie, s.writer)
}
// clearCookie sets expiry of the cookie to one day before to clear it.
func (s *Session) clearCookie() error {
s.cookie = &http.Cookie{
Name: s.manager.opts.CookieName,
Value: "",
// Set expiry to previous date to clear it from browser
Expires: time.Now().AddDate(0, 0, -1),
}
// Call `SetCookie` callback to write cookie to response
return s.manager.setCookieCb(s.cookie, s.writer)
}
// Create a new session. This is implicit when option `DisableAutoSet` is false
// else session has to be manually created before setting or getting values.
func (s *Session) Create() error {
// Create new cookie in store and write to front.
cv, err := s.manager.store.Create(s)
if err != nil {
return err
}
// Write cookie
if err := s.WriteCookie(cv); err != nil {
return err
}
// Set isSet flag
s.isSet = true
return nil
}
// LoadValues loads the session values in memory.
// Get session field tries to get value from memory before hitting store.
func (s *Session) LoadValues() error {
var err error
s.values, err = s.GetAll()
return err
}
// ResetValues reset the loaded values using `LoadValues` method.ResetValues
// Subsequent Get, GetAll and GetMulti
func (s *Session) ResetValues() {
s.values = make(map[string]interface{})
}
// GetAll gets all the fields in the session.
func (s *Session) GetAll() (map[string]interface{}, error) {
// Check if session is set before accessing it
if !s.isSet {
return nil, ErrInvalidSession
}
// Load value from map if its already loaded
if len(s.values) > 0 {
return s.values, nil
}
return s.manager.store.GetAll(s, s.cookie.Value)
}
// GetMulti gets a map of values for multiple session keys.
func (s *Session) GetMulti(keys ...string) (map[string]interface{}, error) {
// Check if session is set before accessing it
if !s.isSet {
return nil, ErrInvalidSession
}
// Load values from map if its already loaded
if len(s.values) > 0 {
vals := make(map[string]interface{})
for _, k := range keys {
if v, ok := s.values[k]; ok {
vals[k] = v
}
}
return vals, nil
}
return s.manager.store.GetMulti(s, s.cookie.Value, keys...)
}
// Get gets a value for given key in session.
// If session is already loaded using `Load` then returns values from
// existing map instead of getting it from store.
func (s *Session) Get(key string) (interface{}, error) {
// Check if session is set before accessing it
if !s.isSet {
return nil, ErrInvalidSession
}
// Load value from map if its already loaded
if len(s.values) > 0 {
if val, ok := s.values[key]; ok {
return val, nil
}
}
// Get from backend if not found in previous step
return s.manager.store.Get(s, s.cookie.Value, key)
}
// Set sets a value for given key in session. Its up to store to commit
// all previously set values at once or store it on each set.
func (s *Session) Set(key string, val interface{}) error {
// Check if session is set before accessing it
if !s.isSet {
return ErrInvalidSession
}
return s.manager.store.Set(s, s.cookie.Value, key, val)
}
// Commit commits all set to store. Its up to store to commit
// all previously set values at once or store it on each set.
func (s *Session) Commit() error {
// Check if session is set before accessing it
if !s.isSet {
return ErrInvalidSession
}
return s.manager.store.Commit(s, s.cookie.Value)
}
// Delete deletes a field from session.
func (s *Session) Delete(key string) error {
// Check if session is set before accessing it
if !s.isSet {
return ErrInvalidSession
}
return s.manager.store.Delete(s, s.cookie.Value, key)
}
// Clear clears session data from store and clears the cookie
func (s *Session) Clear() error {
// Check if session is set before accessing it
if !s.isSet {
return ErrInvalidSession
}
if err := s.manager.store.Clear(s, s.cookie.Value); err != nil {
return err
}
return s.clearCookie()
}
// Int is a helper to get values as integer
func (s *Session) Int(r interface{}, err error) (int, error) {
return s.manager.store.Int(r, err)
}
// Int64 is a helper to get values as Int64
func (s *Session) Int64(r interface{}, err error) (int64, error) {
return s.manager.store.Int64(r, err)
}
// UInt64 is a helper to get values as UInt64
func (s *Session) UInt64(r interface{}, err error) (uint64, error) {
return s.manager.store.UInt64(r, err)
}
// Float64 is a helper to get values as Float64
func (s *Session) Float64(r interface{}, err error) (float64, error) {
return s.manager.store.Float64(r, err)
}
// String is a helper to get values as String
func (s *Session) String(r interface{}, err error) (string, error) {
return s.manager.store.String(r, err)
}
// Bytes is a helper to get values as Bytes
func (s *Session) Bytes(r interface{}, err error) ([]byte, error) {
return s.manager.store.Bytes(r, err)
}
// Bool is a helper to get values as Bool
func (s *Session) Bool(r interface{}, err error) (bool, error) {
return s.manager.store.Bool(r, err)
}