auth

Paddy 2015-05-17 Parent:581c60f8dd23 Child:b7e685839a1b

172:8ecb60d29b0d Go to Latest

auth/token_test.go

Support email verification. The bulk of this commit is auto-modifying files to export variables (mostly our request error types and our response type) so that they can be reused in a Go client for that API. We also implement the beginnings of a Go client for that API, implementing the bare minimum we need for our immediate purposes: the ability to retrieve information about a Login. This, of course, means we need an API endpoint that will return information about a Login, which in turn required us to implement a GetLogin method in our profileStore. Which got in-memory and postgres implementations. That done, we could add the Verification field and Verified field to the Login type, to keep track of whether we've verified the user's ownership of those communication methods (if the Login is, in fact, a communication method). This required us to update sql/postgres_init.sql to account for the new fields we're tracking. It also means that when creating a Login, we had to generate a UUID to use as the Verification field. To make things complete, we needed a verifyLogin method on the profileStore to mark a Login as verified. That, in turn, required an endpoint to control this through the API. While doing so, I lumped things together in an UpdateLogin handler just so we could reuse the endpoint and logic when resending a verification email that may have never reached the user, for whatever reason (the quintessential "send again" button). Finally, we implemented an email_verification listener that will pull email_verification events off NSQ, check for the requisite data integrity, and use mailgun to email out a verification/welcome email.

History
paddy@28 1 package auth
paddy@28 2
paddy@28 3 import (
paddy@155 4 "os"
paddy@28 5 "testing"
paddy@28 6 "time"
paddy@28 7
paddy@107 8 "code.secondbit.org/uuid.hg"
paddy@28 9 )
paddy@28 10
paddy@155 11 func init() {
paddy@155 12 if os.Getenv("PG_TEST_DB") != "" {
paddy@155 13 p, err := NewPostgres(os.Getenv("PG_TEST_DB"))
paddy@155 14 if err != nil {
paddy@155 15 panic(err)
paddy@155 16 }
paddy@155 17 tokenStores = append(tokenStores, &p)
paddy@155 18 }
paddy@155 19 }
paddy@155 20
paddy@57 21 var tokenStores = []tokenStore{NewMemstore()}
paddy@28 22
paddy@35 23 func compareTokens(token1, token2 Token) (success bool, field string, val1, val2 interface{}) {
paddy@35 24 if token1.AccessToken != token2.AccessToken {
paddy@35 25 return false, "access token", token1.AccessToken, token2.AccessToken
paddy@35 26 }
paddy@35 27 if token1.RefreshToken != token2.RefreshToken {
paddy@35 28 return false, "refresh token", token1.RefreshToken, token2.RefreshToken
paddy@35 29 }
paddy@35 30 if !token1.Created.Equal(token2.Created) {
paddy@35 31 return false, "created", token1.Created, token2.Created
paddy@35 32 }
paddy@97 33 if token1.CreatedFrom != token2.CreatedFrom {
paddy@97 34 return false, "created from", token1.CreatedFrom, token2.CreatedFrom
paddy@97 35 }
paddy@35 36 if token1.ExpiresIn != token2.ExpiresIn {
paddy@35 37 return false, "expires in", token1.ExpiresIn, token2.ExpiresIn
paddy@35 38 }
paddy@35 39 if token1.TokenType != token2.TokenType {
paddy@35 40 return false, "token type", token1.TokenType, token2.TokenType
paddy@35 41 }
paddy@135 42 if len(token1.Scopes) != len(token2.Scopes) {
paddy@135 43 return false, "scopes", token1.Scopes, token2.Scopes
paddy@135 44 }
paddy@135 45 for pos, scope := range token1.Scopes {
paddy@135 46 if scope != token2.Scopes[pos] {
paddy@135 47 return false, "scopes", token1.Scopes, token2.Scopes
paddy@135 48 }
paddy@35 49 }
paddy@35 50 if !token1.ProfileID.Equal(token2.ProfileID) {
paddy@35 51 return false, "profile ID", token1.ProfileID, token2.ProfileID
paddy@35 52 }
paddy@97 53 if token1.Revoked != token2.Revoked {
paddy@97 54 return false, "revoked", token1.Revoked, token2.Revoked
paddy@97 55 }
paddy@35 56 return true, "", nil, nil
paddy@35 57 }
paddy@35 58
paddy@28 59 func TestTokenStoreSuccess(t *testing.T) {
paddy@37 60 t.Parallel()
paddy@28 61 token := Token{
paddy@28 62 AccessToken: "access",
paddy@28 63 RefreshToken: "refresh",
paddy@149 64 Created: time.Now().Round(time.Millisecond),
paddy@28 65 ExpiresIn: 3600,
paddy@28 66 TokenType: "bearer",
paddy@163 67 Scopes: stringsToScopes([]string{"scope"}),
paddy@28 68 ProfileID: uuid.NewID(),
paddy@28 69 }
paddy@35 70 for _, store := range tokenStores {
paddy@116 71 context := Context{tokens: store}
paddy@127 72 retrievedAccess, err := context.GetToken(token.AccessToken, false)
paddy@127 73 if err == nil {
paddy@127 74 t.Errorf("Expected ErrTokenNotFound from %T, got %+v", store, retrievedAccess)
paddy@127 75 } else if err != ErrTokenNotFound {
paddy@127 76 t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
paddy@127 77 }
paddy@127 78 retrievedRefresh, err := context.GetToken(token.RefreshToken, true)
paddy@127 79 if err == nil {
paddy@127 80 t.Errorf("Expected ErrTokenNotFound from %T, got %+v", store, retrievedRefresh)
paddy@127 81 } else if err != ErrTokenNotFound {
paddy@127 82 t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
paddy@127 83 }
paddy@168 84 err = context.RevokeToken(token.RefreshToken)
paddy@127 85 if err != ErrTokenNotFound {
paddy@127 86 t.Errorf("Expected ErrTokenNotFound from %T, got %s", store, err)
paddy@127 87 }
paddy@127 88 err = context.SaveToken(token)
paddy@28 89 if err != nil {
paddy@37 90 t.Errorf("Error saving token to %T: %s", store, err)
paddy@37 91 }
paddy@116 92 err = context.SaveToken(token)
paddy@37 93 if err != ErrTokenAlreadyExists {
paddy@37 94 t.Errorf("Expected ErrTokenAlreadyExists from %T, got %s", store, err)
paddy@28 95 }
paddy@127 96 retrievedAccess, err = context.GetToken(token.AccessToken, false)
paddy@28 97 if err != nil {
paddy@35 98 t.Errorf("Error retrieving token from %T: %s", store, err)
paddy@28 99 }
paddy@35 100 success, field, expectation, result := compareTokens(token, retrievedAccess)
paddy@35 101 if !success {
paddy@35 102 t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
paddy@35 103 }
paddy@127 104 retrievedRefresh, err = context.GetToken(token.RefreshToken, true)
paddy@28 105 if err != nil {
paddy@35 106 t.Errorf("Error retrieving refresh token from %T: %s", store, err)
paddy@28 107 }
paddy@35 108 success, field, expectation, result = compareTokens(token, retrievedRefresh)
paddy@35 109 if !success {
paddy@35 110 t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
paddy@35 111 }
paddy@116 112 retrievedProfile, err := context.GetTokensByProfileID(token.ProfileID, 25, 0)
paddy@28 113 if err != nil {
paddy@35 114 t.Errorf("Error retrieving token by profile from %T: %s", store, err)
paddy@28 115 }
paddy@28 116 if len(retrievedProfile) != 1 {
paddy@35 117 t.Errorf("Expected 1 token retrieved by profile ID from %T, got %+v", store, retrievedProfile)
paddy@28 118 }
paddy@35 119 success, field, expectation, result = compareTokens(token, retrievedProfile[0])
paddy@35 120 if !success {
paddy@35 121 t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
paddy@35 122 }
paddy@168 123 err = context.RevokeToken(token.RefreshToken)
paddy@97 124 if err != nil {
paddy@97 125 t.Errorf("Error revoking token in %T: %s", store, err)
paddy@97 126 }
paddy@116 127 retrievedRevoked, err := context.GetToken(token.AccessToken, false)
paddy@97 128 if err != nil {
paddy@97 129 t.Errorf("Error retrieving token from %T: %s", store, err)
paddy@97 130 }
paddy@97 131 token.Revoked = true
paddy@97 132 success, field, expectation, result = compareTokens(token, retrievedRevoked)
paddy@97 133 if !success {
paddy@97 134 t.Errorf("Expected field %s to be %v, but got %v from %T", field, expectation, result, store)
paddy@97 135 }
paddy@28 136 }
paddy@28 137 }
paddy@128 138
paddy@128 139 // BUG(paddy): We need to test the refreshTokenValidate function.
paddy@128 140 // BUG(paddy): We need to test the refreshTokenInvalidate function.