/
auth.go
234 lines (201 loc) · 6.1 KB
/
auth.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
package auth
import (
"errors"
"strconv"
"time"
"github.com/tinode/chat/server/store/types"
)
// Level is the type for authentication levels.
type Level int
// Authentication levels
const (
// LevelNone is undefined/not authenticated
LevelNone Level = iota * 10
// LevelAnon is anonymous user/light authentication
LevelAnon
// LevelAuth is fully authenticated user
LevelAuth
// LevelRoot is a superuser (currently unused)
LevelRoot
)
// String implements Stringer interface: gets human-readable name for a numeric authentication level.
func (a Level) String() string {
s, err := a.MarshalText()
if err != nil {
return "unkn"
}
return string(s)
}
// ParseAuthLevel parses authentication level from a string.
func ParseAuthLevel(name string) Level {
switch name {
case "anon", "ANON":
return LevelAnon
case "auth", "AUTH":
return LevelAuth
case "root", "ROOT":
return LevelRoot
default:
return LevelNone
}
}
// MarshalText converts Level to a slice of bytes with the name of the level.
func (a Level) MarshalText() ([]byte, error) {
switch a {
case LevelNone:
return []byte(""), nil
case LevelAnon:
return []byte("anon"), nil
case LevelAuth:
return []byte("auth"), nil
case LevelRoot:
return []byte("root"), nil
default:
return nil, errors.New("auth.Level: invalid level value")
}
}
// UnmarshalText parses authentication level from a string.
func (a *Level) UnmarshalText(b []byte) error {
switch string(b) {
case "":
*a = LevelNone
return nil
case "anon", "ANON":
*a = LevelAnon
return nil
case "auth", "AUTH":
*a = LevelAuth
return nil
case "root", "ROOT":
*a = LevelRoot
return nil
default:
return errors.New("auth.Level: unrecognized")
}
}
// MarshalJSON converts Level to a quoted string.
func (a Level) MarshalJSON() ([]byte, error) {
res, err := a.MarshalText()
if err != nil {
return nil, err
}
return append(append([]byte{'"'}, res...), '"'), nil
}
// UnmarshalJSON reads Level from a quoted string.
func (a *Level) UnmarshalJSON(b []byte) error {
if b[0] != '"' || b[len(b)-1] != '"' {
return errors.New("syntax error")
}
return a.UnmarshalText(b[1 : len(b)-1])
}
// Feature is a bitmap of authenticated features, such as validated/not validated.
type Feature uint16
const (
// FeatureValidated bit is set if user's credentials are already validated.
FeatureValidated Feature = 1 << iota
// FeatureNoLogin is set if the token should not be used to permanently authenticate a session.
FeatureNoLogin
)
// MarshalText converts AccessMode to ASCII byte slice.
func (f Feature) MarshalText() ([]byte, error) {
var res = []byte{}
for i, chr := range []byte{'V', 'L'} {
if (f & (1 << uint(i))) != 0 {
res = append(res, chr)
}
}
return res, nil
}
// UnmarshalText parses access mode string as byte slice.
// Does not change the mode if the string is empty or invalid.
func (f *Feature) UnmarshalText(b []byte) error {
var f0 int
var err error
if len(b) > 0 {
if b[0] >= '0' && b[0] <= '9' {
f0, err = strconv.Atoi(string(b))
} else {
Loop:
for i := 0; i < len(b); i++ {
switch b[i] {
case 'V', 'v':
f0 |= int(FeatureValidated)
case 'L', 'l':
f0 |= int(FeatureNoLogin)
default:
err = errors.New("Feature: invalid character '" + string(b[i]) + "'")
break Loop
}
}
}
}
*f = Feature(f0)
return err
}
// String returns string representation of Feature.
func (f Feature) String() string {
res, err := f.MarshalText()
if err != nil {
return ""
}
return string(res)
}
// MarshalJSON converts AccessMode to a quoted string.
func (f Feature) MarshalJSON() ([]byte, error) {
res, err := f.MarshalText()
if err != nil {
return nil, err
}
return append(append([]byte{'"'}, res...), '"'), nil
}
// UnmarshalJSON reads AccessMode from a quoted string.
func (f *Feature) UnmarshalJSON(b []byte) error {
if b[0] != '"' || b[len(b)-1] != '"' {
return errors.New("syntax error")
}
return f.UnmarshalText(b[1 : len(b)-1])
}
// Rec is an authentication record.
type Rec struct {
// User ID
Uid types.Uid `json:"uid,omitempty"`
// Authentication level
AuthLevel Level `json:"authlvl,omitempty"`
// Lifetime of this record
Lifetime time.Duration `json:"lifetime,omitempty"`
// Bitmap of features. Currently 'validated'/'not validated' only.
Features Feature `json:"features,omitempty"`
// Tags generated by this authentication record.
Tags []string `json:"tags,omitempty"`
// Authenticator may request the server to create a new account.
// These are the account parameters which can be used for creating the account.
DefAcs *types.DefaultAccess `json:"defacs,omitempty"`
Public interface{} `json:"public,omitempty"`
Private interface{} `json:"private,omitempty"`
}
// AuthHandler is the interface which auth providers must implement.
type AuthHandler interface {
// Init initializes the handler taking config string and logical name as parameters.
Init(jsonconf, name string) error
// AddRecord adds persistent authentication record to the database.
// Returns: updated auth record, error
AddRecord(rec *Rec, secret []byte) (*Rec, error)
// UpdateRecord updates existing record with new credentials.
// Returns updated auth record, error.
UpdateRecord(rec *Rec, secret []byte) (*Rec, error)
// Authenticate: given a user-provided authentication secret (such as "login:password"), either
// return user's record (ID, time when the secret expires, etc), or issue a challenge to
// continue the authentication process to the next step, or return an error code.
// store.Users.GetAuthRecord("scheme", "unique")
// Returns: user auth record, challenge, error.
Authenticate(secret []byte) (*Rec, []byte, error)
// IsUnique verifies if the provided secret can be considered unique by the auth scheme
// E.g. if login is unique.
IsUnique(secret []byte) (bool, error)
// GenSecret generates a new secret, if appropriate.
GenSecret(rec *Rec) ([]byte, time.Time, error)
// DelRecords deletes (or disables) all authentication records for the given user.
DelRecords(uid types.Uid) error
// RestrictedTags returns the tag namespaces which are restricted by this authenticator.
RestrictedTags() ([]string, error)
}