-
Notifications
You must be signed in to change notification settings - Fork 2
/
backend.go
323 lines (284 loc) · 9.31 KB
/
backend.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
package auth
import (
"fmt"
"strconv"
"time"
"github.com/pkg/errors"
)
var errEmailVerifyHashExists = errors.New("DB: Email verify hash already exists")
var errInvalidEmailVerifyHash = errors.New("DB: Invalid verify code")
var errInvalidRenewTimeUTC = errors.New("DB: Invalid RenewTimeUTC")
var errInvalidSessionHash = errors.New("DB: Invalid SessionHash")
var errRememberMeSelectorExists = errors.New("DB: RememberMe selector already exists")
var errUserNotFound = errors.New("DB: User not found")
var errLoginNotFound = errors.New("DB: Login not found")
var errInvalidCredentials = errors.New("DB: Invalid Credentials")
var errSessionNotFound = errors.New("DB: Session not found")
var errSessionAlreadyExists = errors.New("DB: Session already exists")
var errRememberMeNotFound = errors.New("DB: RememberMe not found")
var errRememberMeNeedsRenew = errors.New("DB: RememberMe needs to be renewed")
var errRememberMeExpired = errors.New("DB: RememberMe is expired")
var errUserAlreadyExists = errors.New("DB: User already exists")
// Backender interface contains all the methods needed to read and write users, sessions and logins
type Backender interface {
userBackender
sessionBackender
backendCloser
Clone() Backender
}
type backendCloser interface {
Close() error
}
// UserBackender interface holds methods for user management
type UserBackender interface {
userBackender
backendCloser
}
type userBackender interface {
AddVerifiedUser(email string, info map[string]interface{}) (string, error)
AddUserFull(email, password string, info map[string]interface{}) (*User, error)
GetUser(email string) (*User, error)
UpdateUser(userID, password string, info map[string]interface{}) error
UpdateInfo(userID string, info map[string]interface{}) error
UpdatePassword(userID, newPassword string) error
VerifyEmail(email string) error
Login(email, password string) error
LoginAndGetUser(email, password string) (*User, error)
AddSecondaryEmail(userID, secondaryEmail string) error
UpdatePrimaryEmail(userID, newPrimaryEmail string) error
}
// SessionBackender interface holds methods for session management
type SessionBackender interface {
sessionBackender
backendCloser
}
type sessionBackender interface {
CreateEmailSession(userID, email string, info map[string]interface{}, emailVerifyHash, csrfToken string) error
GetEmailSession(verifyHash string) (*emailSession, error)
UpdateEmailSession(verifyHash string, userID string) error
DeleteEmailSession(verifyHash string) error
CreateSession(userID, email string, info map[string]interface{}, sessionHash, csrfToken string, sessionRenewTimeUTC, sessionExpireTimeUTC time.Time) (*LoginSession, error)
GetSession(sessionHash string) (*LoginSession, error)
UpdateSession(sessionHash string, renewTimeUTC, expireTimeUTC time.Time) error
DeleteSession(sessionHash string) error
InvalidateSessions(email string) error
DeleteSessions(email string) error
CreateRememberMe(userID, email string, rememberMeSelector, rememberMeTokenHash string, renewTimeUTC, expireTimeUTC time.Time) (*rememberMeSession, error)
GetRememberMe(selector string) (*rememberMeSession, error)
UpdateRememberMe(selector string, renewTimeUTC time.Time) error
DeleteRememberMe(selector string) error
DeleteRememberMes(email string) error
}
type emailSession struct {
UserID string `bson:"userID" json:"userID"`
Email string `bson:"email" json:"email"`
Info map[string]interface{} `bson:"info" json:"info"`
EmailVerifyHash string `bson:"_id" json:"emailVerifyHash"`
CSRFToken string `bson:"csrfToken" json:"csrfToken"`
}
type user struct {
UserID string
PrimaryEmail string
PasswordHash string
IsEmailVerified bool
Info map[string]interface{}
LockoutEndTimeUTC *time.Time
AccessFailedCount int
}
// User is the struct which holds user information
type User struct {
UserID string `json:"userID"`
Email string `json:"email"`
IsEmailVerified bool `json:"isEmailVerified"`
Info map[string]interface{} `json:"info"`
}
// LoginSession is the struct which holds session information
type LoginSession struct {
UserID string `bson:"userID" json:"userID"`
Email string `bson:"email" json:"email"`
Info map[string]interface{} `bson:"info" json:"info"`
SessionHash string `bson:"_id" json:"sessionHash"`
CSRFToken string `bson:"csrfToken" json:"csrfToken"`
RenewTimeUTC time.Time `bson:"renewTimeUTC" json:"renewTimeUTC"`
ExpireTimeUTC time.Time `bson:"expireTimeUTC" json:"expireTimeUTC"`
}
// GetInfo will return the named info as an interface{}
func (l *LoginSession) GetInfo(name string) interface{} {
if l == nil {
return nil
}
return GetInfo(l.Info, name)
}
// GetInfoString will return the named info as a string
func (l *LoginSession) GetInfoString(name string) string {
if l == nil {
return ""
}
return GetInfoString(l.Info, name)
}
// GetInfoStrings will return the named info as an array of strings
func (l *LoginSession) GetInfoStrings(name string) []string {
if l == nil {
return nil
}
return GetInfoStrings(l.Info, name)
}
// GetInfoInts will return the named info as an array of integers
func (l *LoginSession) GetInfoInts(name string) []int {
if l == nil {
return nil
}
return GetInfoInts(l.Info, name)
}
// GetInfo will return the named info as an interface{}
func (u *User) GetInfo(name string) interface{} {
if u == nil {
return nil
}
return GetInfo(u.Info, name)
}
// GetInfoString will return the named info as a string
func (u *User) GetInfoString(name string) string {
if u == nil {
return ""
}
return GetInfoString(u.Info, name)
}
// GetInfoStrings will return the named info as an array of strings
func (u *User) GetInfoStrings(name string) []string {
if u == nil {
return nil
}
return GetInfoStrings(u.Info, name)
}
// GetInfoInts will return the named info as an array of integers
func (u *User) GetInfoInts(name string) []int {
if u == nil {
return nil
}
return GetInfoInts(u.Info, name)
}
// GetInfo will return the named info as an interface{}
func GetInfo(info map[string]interface{}, name string) interface{} {
if info == nil {
return nil
}
return info[name]
}
// GetInfoString will return the named info as a string
func GetInfoString(info map[string]interface{}, name string) string {
v := GetInfo(info, name)
if v == nil {
return ""
}
if i, ok := v.(string); ok {
return i
}
return fmt.Sprint(v)
}
// GetInfoStrings will return the named info as an array of strings
func GetInfoStrings(info map[string]interface{}, name string) []string {
i := GetInfo(info, name)
switch v := i.(type) {
case []string:
return v
case []interface{}:
strArr := make([]string, len(v))
for i, str := range v {
if s, ok := str.(string); ok {
strArr[i] = s
} else {
strArr[i] = fmt.Sprint(str)
}
}
return strArr
}
return nil
}
// GetInfoInts will return the named info as an array of integers
func GetInfoInts(info map[string]interface{}, name string) []int {
i := GetInfo(info, name)
switch v := i.(type) {
case []int:
return v
case []interface{}:
var intArr []int
for _, str := range v {
if s, ok := str.(int); ok {
intArr = append(intArr, s)
} else {
toInt, err := strconv.Atoi(fmt.Sprint(str))
if err != nil {
continue
}
intArr = append(intArr, toInt)
}
}
return intArr
}
return nil
}
type rememberMeSession struct {
UserID string `bson:"userID" json:"userID"`
Email string `bson:"email" json:"email"`
Selector string `bson:"_id" json:"selector"`
TokenHash string `bson:"tokenHash" json:"tokenHash"`
RenewTimeUTC time.Time `bson:"renewTimeUTC" json:"renewTimeUTC"`
ExpireTimeUTC time.Time `bson:"expireTimeUTC" json:"expireTimeUTC"`
}
type loginProvider struct {
LoginProviderID int
Name string
OAuthClientID string
OAuthClientSecret string
OAuthURL string
}
// AuthError struct holds detailed auth error info
type AuthError struct {
message string
innerError error
shouldLog bool
error
}
func newLoggedError(message string, innerError error) *AuthError {
return &AuthError{message: message, innerError: innerError, shouldLog: true}
}
func newAuthError(message string, innerError error) *AuthError {
return &AuthError{message: message, innerError: innerError}
}
func (a *AuthError) Error() string {
return a.message
}
func (a *AuthError) Trace() string {
trace := a.message + "\n"
indent := " "
inner := a.innerError
for inner != nil {
trace += indent + inner.Error() + "\n"
e, ok := inner.(*AuthError)
if !ok {
break
}
indent += " "
inner = e.innerError
}
return trace
}
type backend struct {
UserBackender
SessionBackender
backendCloser
}
// NewBackend returns a Backender from a UserBackender, LoginBackender and SessionBackender
func NewBackend(u UserBackender, s SessionBackender) Backender {
return &backend{UserBackender: u, SessionBackender: s}
}
func (b *backend) Clone() Backender {
return b
}
func (b *backend) Close() error {
if err := b.SessionBackender.Close(); err != nil {
return err
}
return b.UserBackender.Close()
}