-
Notifications
You must be signed in to change notification settings - Fork 25
/
session-store.go
322 lines (282 loc) · 8.47 KB
/
session-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
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
/*
* Copyright (c) 2022 The AdvantEDGE Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sessions
import (
"errors"
"net/http"
"os"
"strings"
"time"
dkm "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-data-key-mgr"
log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger"
redis "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-redis"
"github.com/rs/xid"
"github.com/gorilla/sessions"
)
const sessionCookie = "authCookie"
const sessionsKey = "sessions:"
const sessionsRedisTable = 0
const SessionDuration = 1200 // 20 minutes
const (
ValSessionID = "sid"
ValUsername = "user"
ValProvider = "provider"
ValSandbox = "sbox"
ValRole = "role"
ValTimestamp = "timestamp"
ValStartTime = "starttime"
)
const (
RoleDefault = "default"
RoleUser = "user"
RoleAdmin = "admin"
)
type Session struct {
ID string
Username string
Provider string
Sandbox string
Role string
Timestamp time.Time
StartTime time.Time
}
type SessionStore struct {
rc *redis.Connector
cs *sessions.CookieStore
baseKey string
}
// NewSessionStore - Create and initialize a Session Store instance
func NewSessionStore(addr string) (ss *SessionStore, err error) {
// Retrieve Sandbox name from environment variable
sessionKey := strings.TrimSpace(os.Getenv("MEEP_SESSION_KEY"))
if sessionKey == "" {
// err = errors.New("variable env variable not set")
// log.Error(err.Error())
// return err
log.Info("No session key provided. Using default key.")
sessionKey = "my-secret-key"
}
// Create new Session Store instance
log.Info("Creating new Session Store")
ss = new(SessionStore)
// Connect to Redis DB
ss.rc, err = redis.NewConnector(addr, sessionsRedisTable)
if err != nil {
log.Error("Failed connection to Session Store redis DB. Error: ", err)
return nil, err
}
log.Info("Connected to Session Store Redis DB")
// Create Cookie store
ss.cs = sessions.NewCookieStore([]byte(sessionKey))
ss.cs.Options = &sessions.Options{
Path: "/",
MaxAge: SessionDuration, // 20 minutes
HttpOnly: true,
}
log.Info("Created Cookie Store")
// Get base store key
ss.baseKey = dkm.GetKeyRootGlobal() + sessionsKey
log.Info("Created Session Store")
return ss, nil
}
// Get - Retrieve session by ID
func (ss *SessionStore) Get(r *http.Request) (s *Session, err error) {
// Get session cookie
sessionCookie, err := ss.cs.Get(r, sessionCookie)
if err != nil {
return nil, err
}
if sessionCookie.IsNew {
err = errors.New("Session not found")
return nil, err
}
// Get session from DB
sessionId := sessionCookie.Values[ValSessionID].(string)
session, err := ss.rc.GetEntry(ss.baseKey + sessionId)
if err != nil {
return nil, err
}
if len(session) == 0 {
err = errors.New("Session not found")
return nil, err
}
s = new(Session)
s.ID = sessionId
s.Username = session[ValUsername]
s.Provider = session[ValProvider]
s.Sandbox = session[ValSandbox]
s.Role = session[ValRole]
s.Timestamp, _ = time.Parse(time.RFC3339, session[ValTimestamp])
s.StartTime, _ = time.Parse(time.RFC3339, session[ValStartTime])
return s, nil
}
// GetCount - Retrieve session count
func (ss *SessionStore) GetCount() (count int) {
_ = ss.rc.ForEachEntry(ss.baseKey+"*", getCountHandler, &count)
return count
}
func getCountHandler(key string, fields map[string]string, userData interface{}) error {
count := userData.(*int)
*count += 1
return nil
}
// GetAll - Retrieve session by name
func (ss *SessionStore) GetAll() (sessionList []*Session, err error) {
// Get all sessions, if any
err = ss.rc.ForEachEntry(ss.baseKey+"*", getSessionEntryHandler, &sessionList)
if err != nil {
return nil, err
}
return sessionList, nil
}
func getSessionEntryHandler(key string, fields map[string]string, userData interface{}) error {
sessionList := userData.(*([]*Session))
// Retrieve session information & add to session list
s := new(Session)
s.ID = fields[ValSessionID]
s.Username = fields[ValUsername]
s.Provider = fields[ValProvider]
s.Sandbox = fields[ValSandbox]
s.Role = fields[ValRole]
s.Timestamp, _ = time.Parse(time.RFC3339, fields[ValTimestamp])
s.StartTime, _ = time.Parse(time.RFC3339, fields[ValStartTime])
*sessionList = append(*sessionList, s)
return nil
}
// GetByName - Retrieve session by name
func (ss *SessionStore) GetByName(provider string, username string) (s *Session, err error) {
// Get existing session, if any
s = new(Session)
s.Username = username
s.Provider = provider
err = ss.rc.ForEachEntry(ss.baseKey+"*", getUserEntryHandler, s)
if err != nil {
return nil, err
}
if s.ID == "" {
err = errors.New("Session not found")
return nil, err
}
return s, nil
}
func getUserEntryHandler(key string, fields map[string]string, userData interface{}) error {
s := userData.(*Session)
// Check if session already found
if s.ID != "" {
return nil
}
// look for matching username
if fields[ValUsername] == s.Username && fields[ValProvider] == s.Provider {
s.ID = fields[ValSessionID]
s.Sandbox = fields[ValSandbox]
s.Role = fields[ValRole]
s.Timestamp, _ = time.Parse(time.RFC3339, fields[ValTimestamp])
s.StartTime, _ = time.Parse(time.RFC3339, fields[ValStartTime])
}
return nil
}
// Set - Create session
func (ss *SessionStore) Set(s *Session, w http.ResponseWriter, r *http.Request) (err error, code int) {
// Get session cookie
sessionCookie, err := ss.cs.Get(r, sessionCookie)
if err != nil {
log.Error(err.Error())
// If error was due to new cookie store keys and new session
// is successfully created, then proceed with login
if sessionCookie == nil {
return err, http.StatusInternalServerError
}
}
// Set session start time on initial request
sessionStartTime := s.StartTime
if sessionStartTime.IsZero() {
sessionStartTime = time.Now()
}
// Update existing session or create new one if not found
sessionId := s.ID
if s.ID == "" {
sessionId = xid.New().String()
}
fields := make(map[string]interface{})
fields[ValSessionID] = sessionId
fields[ValUsername] = s.Username
fields[ValProvider] = s.Provider
fields[ValSandbox] = s.Sandbox
fields[ValRole] = s.Role
fields[ValTimestamp] = time.Now().Format(time.RFC3339)
fields[ValStartTime] = sessionStartTime.Format(time.RFC3339)
err = ss.rc.SetEntry(ss.baseKey+sessionId, fields)
if err != nil {
return err, http.StatusInternalServerError
}
// Update session cookie
sessionCookie.Values[ValSessionID] = sessionId
err = sessionCookie.Save(r, w)
if err != nil {
return err, http.StatusInternalServerError
}
return nil, http.StatusOK
}
// Del - Remove session by cookie
func (ss *SessionStore) Del(w http.ResponseWriter, r *http.Request) (err error, code int) {
// Get session cookie
sessionCookie, err := ss.cs.Get(r, sessionCookie)
if err != nil {
return err, http.StatusInternalServerError
}
if sessionCookie.IsNew {
err = errors.New("Unauthorized")
return err, http.StatusUnauthorized
}
// Get session from cookie & remove from DB
sessionId := sessionCookie.Values[ValSessionID].(string)
err = ss.rc.DelEntry(ss.baseKey + sessionId)
if err != nil {
log.Error("Failed to delete entry for ", sessionId, " with err: ", err.Error())
}
// Delete session cookie
sessionCookie.Values[ValSessionID] = ""
sessionCookie.Options.MaxAge = -1
err = sessionCookie.Save(r, w)
if err != nil {
return err, http.StatusInternalServerError
}
return nil, http.StatusOK
}
// Del - Remove session by ID
func (ss *SessionStore) DelById(sessionId string) error {
// Remove session from DB
err := ss.rc.DelEntry(ss.baseKey + sessionId)
if err != nil {
log.Error("Failed to delete entry for ", sessionId, " with err: ", err.Error())
return err
}
return nil
}
// Refresh - Remove session by ID
func (ss *SessionStore) Refresh(w http.ResponseWriter, r *http.Request) (err error, code int) {
// Get existing session, if any
s, err := ss.Get(r)
if err != nil {
return err, http.StatusUnauthorized
}
// Set session to refresh timestamp and cookie
err, code = ss.Set(s, w, r)
if err != nil {
return err, code
}
return nil, http.StatusOK
}