auth
auth/profile.go
Update uuid import path, test for multiple profile updates. Test updating multiple profiles in one request (e.g., when profiles become compromised.) Update the uuid import path to use the new code.secondbit.org/uuid import path.
1 package auth
3 import (
4 "errors"
5 "time"
7 "code.secondbit.org/uuid"
8 )
10 var (
11 ErrProfileAlreadyExists = errors.New("profile already exists in ProfileStore")
12 ErrProfileNotFound = errors.New("profile not found in ProfileStore")
13 ErrLoginAlreadyExists = errors.New("login already exists in ProfileStore")
14 ErrLoginNotFound = errors.New("login not found in ProfileStore")
15 )
17 type Profile struct {
18 ID uuid.ID
19 Name string
20 Passphrase string
21 Iterations int64
22 Salt string
23 PassphraseScheme int
24 Compromised bool
25 LockedUntil time.Time
26 PassphraseReset string
27 PassphraseResetCreated time.Time
28 Created time.Time
29 LastSeen time.Time
30 }
32 func (p *Profile) ApplyChange(change ProfileChange) {
33 if change.Name != nil {
34 p.Name = *change.Name
35 }
36 if change.Passphrase != nil {
37 p.Passphrase = *change.Passphrase
38 }
39 if change.Iterations != nil {
40 p.Iterations = *change.Iterations
41 }
42 if change.Salt != nil {
43 p.Salt = *change.Salt
44 }
45 if change.PassphraseScheme != nil {
46 p.PassphraseScheme = *change.PassphraseScheme
47 }
48 if change.Compromised != nil {
49 p.Compromised = *change.Compromised
50 }
51 if change.LockedUntil != nil {
52 p.LockedUntil = *change.LockedUntil
53 }
54 if change.PassphraseReset != nil {
55 p.PassphraseReset = *change.PassphraseReset
56 }
57 if change.PassphraseResetCreated != nil {
58 p.PassphraseResetCreated = *change.PassphraseResetCreated
59 }
60 if change.LastSeen != nil {
61 p.LastSeen = *change.LastSeen
62 }
63 }
65 func (p *Profile) ApplyBulkChange(change BulkProfileChange) {
66 if change.Compromised != nil {
67 p.Compromised = *change.Compromised
68 }
69 }
71 type ProfileChange struct {
72 Name *string
73 Passphrase *string
74 Iterations *int64
75 Salt *string
76 PassphraseScheme *int
77 Compromised *bool
78 LockedUntil *time.Time
79 PassphraseReset *string
80 PassphraseResetCreated *time.Time
81 LastSeen *time.Time
82 }
84 func (c ProfileChange) Validate() error {
85 // TODO: validate profile changes
86 return nil
87 }
89 type BulkProfileChange struct {
90 Compromised *bool
91 }
93 func (b BulkProfileChange) Validate() error {
94 // TODO: validate bulk profile changs
95 return nil
96 }
98 type Login struct {
99 Type string
100 Value string
101 ProfileID uuid.ID
102 Created time.Time
103 LastUsed time.Time
104 }
106 type ProfileStore interface {
107 GetProfileByID(id uuid.ID) (Profile, error)
108 GetProfileByLogin(loginType, value string) (Profile, error)
109 SaveProfile(profile Profile) error
110 UpdateProfile(id uuid.ID, change ProfileChange) error
111 UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error
112 DeleteProfile(id uuid.ID) error
114 AddLogin(login Login) error
115 RemoveLogin(loginType, value string, profile uuid.ID) error
116 RecordLoginUse(loginType, value string, when time.Time) error
117 ListLogins(profile uuid.ID, num, offset int) ([]Login, error)
118 }
120 func (m *Memstore) GetProfileByID(id uuid.ID) (Profile, error) {
121 m.profileLock.RLock()
122 defer m.profileLock.RUnlock()
123 p, ok := m.profiles[id.String()]
124 if !ok {
125 return Profile{}, ErrProfileNotFound
126 }
127 return p, nil
128 }
130 func (m *Memstore) GetProfileByLogin(loginType, value string) (Profile, error) {
131 m.loginLock.RLock()
132 defer m.loginLock.RUnlock()
133 login, ok := m.logins[loginType+":"+value]
134 if !ok {
135 return Profile{}, ErrLoginNotFound
136 }
137 m.profileLock.RLock()
138 defer m.profileLock.RUnlock()
139 profile, ok := m.profiles[login.ProfileID.String()]
140 if !ok {
141 return Profile{}, ErrProfileNotFound
142 }
143 return profile, nil
144 }
146 func (m *Memstore) SaveProfile(profile Profile) error {
147 m.profileLock.Lock()
148 defer m.profileLock.Unlock()
149 _, ok := m.profiles[profile.ID.String()]
150 if ok {
151 return ErrProfileAlreadyExists
152 }
153 m.profiles[profile.ID.String()] = profile
154 return nil
155 }
157 func (m *Memstore) UpdateProfile(id uuid.ID, change ProfileChange) error {
158 m.profileLock.Lock()
159 defer m.profileLock.Unlock()
160 p, ok := m.profiles[id.String()]
161 if !ok {
162 return ErrProfileNotFound
163 }
164 p.ApplyChange(change)
165 m.profiles[id.String()] = p
166 return nil
167 }
169 func (m *Memstore) UpdateProfiles(ids []uuid.ID, change BulkProfileChange) error {
170 m.profileLock.Lock()
171 defer m.profileLock.Unlock()
172 for id, profile := range m.profiles {
173 for _, i := range ids {
174 if id == i.String() {
175 profile.ApplyBulkChange(change)
176 m.profiles[id] = profile
177 break
178 }
179 }
180 }
181 return nil
182 }
184 func (m *Memstore) DeleteProfile(id uuid.ID) error {
185 m.profileLock.Lock()
186 defer m.profileLock.Unlock()
187 _, ok := m.profiles[id.String()]
188 if !ok {
189 return ErrProfileNotFound
190 }
191 delete(m.profiles, id.String())
192 return nil
193 }
195 func (m *Memstore) AddLogin(login Login) error {
196 m.loginLock.Lock()
197 defer m.loginLock.Unlock()
198 _, ok := m.logins[login.Type+":"+login.Value]
199 if ok {
200 return ErrLoginAlreadyExists
201 }
202 m.logins[login.Type+":"+login.Value] = login
203 m.profileLoginLookup[login.ProfileID.String()] = append(m.profileLoginLookup[login.ProfileID.String()], login.Type+":"+login.Value)
204 return nil
205 }
207 func (m *Memstore) RemoveLogin(loginType, value string, profile uuid.ID) error {
208 m.loginLock.Lock()
209 defer m.loginLock.Unlock()
210 l, ok := m.logins[loginType+":"+value]
211 if !ok {
212 return ErrLoginNotFound
213 }
214 if !l.ProfileID.Equal(profile) {
215 return ErrLoginNotFound
216 }
217 delete(m.logins, loginType+":"+value)
218 pos := -1
219 for p, id := range m.profileLoginLookup[profile.String()] {
220 if id == loginType+":"+value {
221 pos = p
222 break
223 }
224 }
225 if pos >= 0 {
226 m.profileLoginLookup[profile.String()] = append(m.profileLoginLookup[profile.String()][:pos], m.profileLoginLookup[profile.String()][pos+1:]...)
227 }
228 return nil
229 }
231 func (m *Memstore) RecordLoginUse(loginType, value string, when time.Time) error {
232 m.loginLock.Lock()
233 defer m.loginLock.Unlock()
234 l, ok := m.logins[loginType+":"+value]
235 if !ok {
236 return ErrLoginNotFound
237 }
238 l.LastUsed = when
239 m.logins[loginType+":"+value] = l
240 return nil
241 }
243 func (m *Memstore) ListLogins(profile uuid.ID, num, offset int) ([]Login, error) {
244 m.loginLock.RLock()
245 defer m.loginLock.RUnlock()
246 ids, ok := m.profileLoginLookup[profile.String()]
247 if !ok {
248 return []Login{}, nil
249 }
250 if len(ids) > num+offset {
251 ids = ids[offset : num+offset]
252 } else if len(ids) > offset {
253 ids = ids[offset:]
254 } else {
255 return []Login{}, nil
256 }
257 logins := []Login{}
258 for _, id := range ids {
259 login, ok := m.logins[id]
260 if !ok {
261 continue
262 }
263 logins = append(logins, login)
264 }
265 return logins, nil
266 }