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