Make client use our auth(n/z) scheme.
Our auth(n/z) scheme can be loosely defined as "encrypted tokens that nginx
transforms into headers" and "scopes for bypassing ACL". Our Go client, which is
what we'll be using to have services communicate with each other, follows this
paradigm now by auto-injecting the headers we'll need to identify ourselves.
This will work behind our firewall, but will be useless for the rest of the
world, which will need to go through the nginx bastion that can strip the
headers and replace them with the headers appropriate to the token attached to
the request.
This did involve setting a static client ID as the client for our
email_verification listener. Ideally, this would cause Client registration
(using that ID) when the listener starts up, ignoring ErrClientAlreadyExists. I
don't want to have to write the code that will allow us to bypass the Client ACL
properly right now, though, so we're just going to have to remember to manually
create that Client. Or not. I don't think it will do any harm (outside the OAuth
flow) to be using a Client ID that doesn't actually point to a Client. I just
think it'd be good for record-keeping purposes.
9 "code.secondbit.org/uuid.hg"
13 profileChangeName = 1 << iota
14 profileChangePassphrase
15 profileChangeIterations
17 profileChangePassphraseScheme
18 profileChangeCompromised
19 profileChangeLockedUntil
20 profileChangePassphraseReset
21 profileChangePassphraseResetCreated
26 if os.Getenv("PG_TEST_DB") != "" {
27 p, err := NewPostgres(os.Getenv("PG_TEST_DB"))
31 profileStores = append(profileStores, &p)
35 var profileStores = []profileStore{NewMemstore()}
37 func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) {
38 if !profile1.ID.Equal(profile2.ID) {
39 return false, "ID", profile1.ID, profile2.ID
41 if profile1.Name != profile2.Name {
42 return false, "name", profile1.Name, profile2.Name
44 if profile1.Passphrase != profile2.Passphrase {
45 return false, "passphrase", profile1.Passphrase, profile2.Passphrase
47 if profile1.Iterations != profile2.Iterations {
48 return false, "iterations", profile1.Iterations, profile2.Iterations
50 if profile1.Salt != profile2.Salt {
51 return false, "salt", profile1.Salt, profile2.Salt
53 if profile1.PassphraseScheme != profile2.PassphraseScheme {
54 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme
56 if profile1.Compromised != profile2.Compromised {
57 return false, "compromised", profile1.Compromised, profile2.Compromised
59 if !profile1.LockedUntil.Equal(profile2.LockedUntil) {
60 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil
62 if profile1.PassphraseReset != profile2.PassphraseReset {
63 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset
65 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) {
66 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated
68 if !profile1.Created.Equal(profile2.Created) {
69 return false, "created", profile1.Created, profile2.Created
71 if !profile1.LastSeen.Equal(profile2.LastSeen) {
72 return false, "last seen", profile1.LastSeen, profile2.LastSeen
74 return true, "", nil, nil
77 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) {
78 if login1.Type != login2.Type {
79 return false, "Type", login1.Type, login2.Type
81 if login1.Value != login2.Value {
82 return false, "Value", login1.Value, login2.Value
84 if !login1.ProfileID.Equal(login2.ProfileID) {
85 return false, "ProfileID", login1.ProfileID, login2.ProfileID
87 if !login1.Created.Equal(login2.Created) {
88 return false, "Created", login1.Created, login2.Created
90 if !login1.LastUsed.Equal(login2.LastUsed) {
91 return false, "LastUsed", login1.LastUsed, login2.LastUsed
93 if login1.Verification != login2.Verification {
94 return false, "Verification", login1.Verification, login2.Verification
96 if login1.Verified != login2.Verified {
97 return false, "Verified", login1.Verified, login2.Verified
99 return true, "", nil, nil
102 func TestProfileStoreSuccess(t *testing.T) {
107 Passphrase: "passphrase",
112 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond),
113 PassphraseReset: "passphrase reset",
114 PassphraseResetCreated: time.Now().Round(time.Millisecond),
115 Created: time.Now().Round(time.Millisecond),
116 LastSeen: time.Now().Round(time.Millisecond),
118 for _, store := range profileStores {
119 context := Context{profiles: store}
120 err := context.SaveProfile(profile)
122 t.Errorf("Error saving profile to %T: %s", store, err)
124 err = context.SaveProfile(profile)
125 if err != ErrProfileAlreadyExists {
126 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
128 retrieved, err := context.GetProfileByID(profile.ID)
130 t.Errorf("Error retrieving profile from %T: %s", store, err)
132 match, field, expectation, result := compareProfiles(profile, retrieved)
134 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
136 err = context.DeleteProfile(profile.ID)
138 t.Errorf("Error removing profile from %T: %s", store, err)
140 retrieved, err = context.GetProfileByID(profile.ID)
141 if err != ErrProfileNotFound {
142 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
147 func TestProfileUpdates(t *testing.T) {
149 variations := 1 << 10
153 Passphrase: "passphrase",
158 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond),
159 PassphraseReset: "passphrase reset",
160 PassphraseResetCreated: time.Now().Round(time.Millisecond),
161 Created: time.Now().Round(time.Millisecond),
162 LastSeen: time.Now().Round(time.Millisecond),
164 for i := 0; i < variations; i++ {
165 var name, passphrase, salt, passphraseReset string
167 var lockedUntil, passphraseResetCreated, lastSeen time.Time
168 var passphraseScheme int
171 profile.ID = uuid.NewID()
172 change := ProfileChange{}
173 expectation := profile
175 if i&profileChangeName != 0 {
176 name = fmt.Sprintf("name-%d", i)
178 expectation.Name = name
180 if i&profileChangePassphrase != 0 {
181 passphrase = fmt.Sprintf("passphrase-%d", i)
182 change.Passphrase = &passphrase
183 expectation.Passphrase = passphrase
185 if i&profileChangeIterations != 0 {
187 change.Iterations = &iterations
188 expectation.Iterations = iterations
190 if i&profileChangeSalt != 0 {
191 salt = fmt.Sprintf("salt-%d", i)
193 expectation.Salt = salt
195 if i&profileChangePassphraseScheme != 0 {
197 change.PassphraseScheme = &passphraseScheme
198 expectation.PassphraseScheme = passphraseScheme
200 if i&profileChangeCompromised != 0 {
201 compromised = i%2 != 0
202 change.Compromised = &compromised
203 expectation.Compromised = compromised
205 if i&profileChangeLockedUntil != 0 {
206 lockedUntil = time.Now().Round(time.Millisecond)
207 change.LockedUntil = &lockedUntil
208 expectation.LockedUntil = lockedUntil
210 if i&profileChangePassphraseReset != 0 {
211 passphraseReset = fmt.Sprintf("passphraseReset-%d", i)
212 change.PassphraseReset = &passphraseReset
213 expectation.PassphraseReset = passphraseReset
215 if i&profileChangePassphraseResetCreated != 0 {
216 passphraseResetCreated = time.Now().Round(time.Millisecond)
217 change.PassphraseResetCreated = &passphraseResetCreated
218 expectation.PassphraseResetCreated = passphraseResetCreated
220 if i&profileChangeLastSeen != 0 {
221 lastSeen = time.Now().Round(time.Millisecond)
222 change.LastSeen = &lastSeen
223 expectation.LastSeen = lastSeen
225 result.ApplyChange(change)
226 match, field, expected, got := compareProfiles(expectation, result)
228 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
230 for _, store := range profileStores {
231 context := Context{profiles: store}
232 err := context.SaveProfile(profile)
234 t.Errorf("Error saving profile in %T: %s", store, err)
236 err = context.UpdateProfile(profile.ID, change)
238 t.Errorf("Error updating profile in %T: %s", store, err)
240 retrieved, err := context.GetProfileByID(profile.ID)
242 t.Errorf("Error getting profile from %T: %s", store, err)
244 match, field, expected, got = compareProfiles(expectation, retrieved)
246 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
252 func TestProfilesUpdates(t *testing.T) {
263 change := BulkProfileChange{
266 for _, store := range profileStores {
267 context := Context{profiles: store}
268 err := context.SaveProfile(profile1)
270 t.Errorf("Error saving profile in %T: %s", store, err)
272 err = context.SaveProfile(profile2)
274 t.Errorf("Error saving profile in %T: %s", store, err)
276 err = context.SaveProfile(profile3)
278 t.Errorf("Error saving profile in %T: %s", store, err)
280 err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
282 t.Errorf("Error updating profile in %T: %s", store, err)
284 profile1.Compromised = truth
285 profile3.Compromised = truth
286 retrieved, err := context.GetProfileByID(profile1.ID)
288 t.Errorf("Error getting profile from %T: %s", store, err)
290 match, field, expected, got := compareProfiles(profile1, retrieved)
292 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
294 retrieved, err = context.GetProfileByID(profile2.ID)
296 t.Errorf("Error getting profile from %T: %s", store, err)
298 match, field, expected, got = compareProfiles(profile2, retrieved)
300 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
302 retrieved, err = context.GetProfileByID(profile3.ID)
304 t.Errorf("Error getting profile from %T: %s", store, err)
306 match, field, expected, got = compareProfiles(profile3, retrieved)
308 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
313 func TestProfileStoreLoginSuccess(t *testing.T) {
318 ProfileID: uuid.NewID(),
319 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond),
320 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond),
322 for _, store := range profileStores {
323 context := Context{profiles: store}
324 err := context.AddLogin(login)
326 t.Errorf("Error adding login to %T: %s", store, err)
328 err = context.AddLogin(login)
329 if err != ErrLoginAlreadyExists {
330 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
332 retrieved, err := context.ListLogins(login.ProfileID, 10, 0)
334 t.Errorf("Error retrieving logins from %T: %s", store, err)
336 if len(retrieved) != 1 {
337 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
339 match, field, expectation, result := compareLogins(login, retrieved[0])
341 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
343 lastUsed := time.Now().Round(time.Millisecond)
344 err = context.RecordLoginUse(login.Value, lastUsed)
346 t.Errorf("Error recording use of login to %T: %s", store, err)
348 login.LastUsed = lastUsed
349 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
351 t.Errorf("Error retrieving logins from %T: %s", store, err)
353 if len(retrieved) != 1 {
354 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
356 match, field, expectation, result = compareLogins(login, retrieved[0])
358 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
360 err = context.RemoveLogin(login.Value, login.ProfileID)
362 t.Errorf("Error removing login from %T: %s", store, err)
364 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
365 if len(retrieved) != 0 {
366 t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved)
368 err = context.RemoveLogin(login.Value, login.ProfileID)
369 if err != ErrLoginNotFound {
370 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
375 func TestProfileStoreLoginRetrieval(t *testing.T) {
380 Passphrase: "passphrase",
385 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond),
386 PassphraseReset: "passphrase reset",
387 PassphraseResetCreated: time.Now().Round(time.Millisecond),
388 Created: time.Now().Round(time.Millisecond),
389 LastSeen: time.Now().Round(time.Millisecond),
394 ProfileID: profile.ID,
395 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond),
396 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond),
398 for _, store := range profileStores {
399 context := Context{profiles: store}
400 err := context.SaveProfile(profile)
402 t.Errorf("Error saving profile in %T: %s", store, err)
404 err = context.AddLogin(login)
406 t.Errorf("Error storing login in %T: %s", store, err)
408 retrieved, err := context.GetProfileByLogin(login.Value)
410 t.Errorf("Error retrieving profile by login from %T: %s", store, err)
412 match, field, expectation, result := compareProfiles(profile, retrieved)
414 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
419 func TestProfileChangeValidation(t *testing.T) {
421 passphraseScheme := 1
422 passphraseReset := "reset"
426 enteredName := "Paddy"
427 okPassphrase := "this is a decent passphrase"
429 lockedUntil := time.Now().Round(time.Millisecond)
430 resetCreated := time.Now().Round(time.Millisecond)
431 lastSeen := time.Now().Round(time.Millisecond)
432 changes := map[*ProfileChange]error{
433 &ProfileChange{}: ErrEmptyChange,
434 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase,
435 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil,
436 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated,
437 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil,
438 &ProfileChange{Salt: &salt}: ErrMissingPassphrase,
439 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil,
440 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase,
441 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil,
442 &ProfileChange{Passphrase: &okPassphrase}: nil,
443 &ProfileChange{Name: &emptyName}: nil,
444 &ProfileChange{Name: &enteredName}: nil,
445 &ProfileChange{Compromised: &compromised}: nil,
446 &ProfileChange{LockedUntil: &lockedUntil}: nil,
447 &ProfileChange{LastSeen: &lastSeen}: nil,
448 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset,
450 for change, expectedErr := range changes {
451 if err := change.Validate(); err != expectedErr {
452 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
457 func TestBulkProfileChangeValidation(t *testing.T) {
460 changes := map[*BulkProfileChange]error{
461 &BulkProfileChange{}: ErrEmptyChange,
462 &BulkProfileChange{Compromised: &compromised}: nil,
464 for change, expectedErr := range changes {
465 if err := change.Validate(); err != expectedErr {
466 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
471 // BUG(paddy): We need to test the validateNewProfileRequest helper.
472 // BUG(paddy): We need to test the CreateProfileHandler.
473 // BUG(paddy): We need to test that deleting works as we expect.