auth

Paddy 2015-05-17 Parent:73e12d5a1124

172:8ecb60d29b0d Go to Latest

auth/scope.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@134 1 package auth
paddy@134 2
paddy@134 3 import (
paddy@134 4 "errors"
paddy@134 5 "sort"
paddy@134 6 )
paddy@134 7
paddy@134 8 var (
paddy@153 9 ErrNoScopeStore = errors.New("scopeStore not set in Context")
paddy@153 10 ErrScopeNotFound = errors.New("scope not found")
paddy@153 11 ErrScopeAlreadyExists = errors.New("scope already exists")
paddy@134 12 )
paddy@134 13
paddy@134 14 // Scope represents a limit on the access that a grant provides.
paddy@134 15 type Scope struct {
paddy@134 16 ID string
paddy@134 17 Name string
paddy@134 18 Description string
paddy@134 19 }
paddy@134 20
paddy@134 21 func (s *Scope) ApplyChange(change ScopeChange) {
paddy@134 22 if change.Name != nil {
paddy@134 23 s.Name = *change.Name
paddy@134 24 }
paddy@134 25 if change.Description != nil {
paddy@134 26 s.Description = *change.Description
paddy@134 27 }
paddy@134 28 }
paddy@134 29
paddy@163 30 type Scopes []Scope
paddy@134 31
paddy@163 32 func (s Scopes) Len() int {
paddy@134 33 return len(s)
paddy@134 34 }
paddy@134 35
paddy@163 36 func (s Scopes) Swap(i, j int) {
paddy@134 37 s[i], s[j] = s[j], s[i]
paddy@134 38 }
paddy@134 39
paddy@163 40 func (s Scopes) Less(i, j int) bool {
paddy@134 41 return s[i].ID < s[j].ID
paddy@134 42 }
paddy@134 43
paddy@163 44 func (s Scopes) Strings() []string {
paddy@163 45 res := make([]string, len(s))
paddy@163 46 for pos, scope := range s {
paddy@163 47 res[pos] = scope.ID
paddy@163 48 }
paddy@163 49 return res
paddy@163 50 }
paddy@163 51
paddy@163 52 func stringsToScopes(s []string) Scopes {
paddy@163 53 res := make(Scopes, len(s))
paddy@163 54 for pos, scope := range s {
paddy@163 55 res[pos] = Scope{ID: scope}
paddy@163 56 }
paddy@163 57 return res
paddy@163 58 }
paddy@163 59
paddy@134 60 // ScopeChange represents a change to a Scope.
paddy@134 61 type ScopeChange struct {
paddy@134 62 Name *string
paddy@134 63 Description *string
paddy@134 64 }
paddy@134 65
paddy@152 66 func (s ScopeChange) Empty() bool {
paddy@152 67 return s.Name == nil && s.Description == nil
paddy@152 68 }
paddy@152 69
paddy@134 70 type scopeStore interface {
paddy@134 71 createScopes(scopes []Scope) error
paddy@134 72 getScopes(ids []string) ([]Scope, error)
paddy@152 73 updateScope(id string, change ScopeChange) error
paddy@134 74 removeScopes(ids []string) error
paddy@134 75 listScopes() ([]Scope, error)
paddy@134 76 }
paddy@134 77
paddy@134 78 func (m *memstore) createScopes(scopes []Scope) error {
paddy@134 79 m.scopeLock.Lock()
paddy@134 80 defer m.scopeLock.Unlock()
paddy@134 81
paddy@153 82 for _, scope := range scopes {
paddy@134 83 if _, ok := m.scopes[scope.ID]; ok {
paddy@153 84 return ErrScopeAlreadyExists
paddy@134 85 }
paddy@134 86 }
paddy@134 87 for _, scope := range scopes {
paddy@134 88 m.scopes[scope.ID] = scope
paddy@134 89 }
paddy@134 90 return nil
paddy@134 91 }
paddy@134 92
paddy@134 93 func (m *memstore) getScopes(ids []string) ([]Scope, error) {
paddy@134 94 m.scopeLock.RLock()
paddy@134 95 defer m.scopeLock.RUnlock()
paddy@134 96
paddy@134 97 scopes := []Scope{}
paddy@153 98 for _, id := range ids {
paddy@134 99 scope, ok := m.scopes[id]
paddy@134 100 if !ok {
paddy@153 101 continue
paddy@134 102 }
paddy@134 103 scopes = append(scopes, scope)
paddy@134 104 }
paddy@163 105 sorted := Scopes(scopes)
paddy@134 106 sort.Sort(sorted)
paddy@134 107 scopes = sorted
paddy@134 108 return scopes, nil
paddy@134 109 }
paddy@134 110
paddy@152 111 func (m *memstore) updateScope(id string, change ScopeChange) error {
paddy@134 112 m.scopeLock.Lock()
paddy@134 113 defer m.scopeLock.Unlock()
paddy@134 114
paddy@152 115 scope, ok := m.scopes[id]
paddy@152 116 if !ok {
paddy@153 117 return ErrScopeNotFound
paddy@134 118 }
paddy@152 119 scope.ApplyChange(change)
paddy@152 120 m.scopes[id] = scope
paddy@152 121 return nil
paddy@134 122 }
paddy@134 123
paddy@134 124 func (m *memstore) removeScopes(ids []string) error {
paddy@134 125 m.scopeLock.Lock()
paddy@134 126 defer m.scopeLock.Unlock()
paddy@134 127
paddy@153 128 for _, id := range ids {
paddy@134 129 if _, ok := m.scopes[id]; !ok {
paddy@153 130 return ErrScopeNotFound
paddy@134 131 }
paddy@134 132 }
paddy@134 133 for _, id := range ids {
paddy@134 134 delete(m.scopes, id)
paddy@134 135 }
paddy@134 136 return nil
paddy@134 137 }
paddy@134 138
paddy@134 139 func (m *memstore) listScopes() ([]Scope, error) {
paddy@134 140 m.scopeLock.RLock()
paddy@134 141 defer m.scopeLock.RUnlock()
paddy@134 142
paddy@134 143 scopes := []Scope{}
paddy@134 144 for _, scope := range m.scopes {
paddy@134 145 scopes = append(scopes, scope)
paddy@134 146 }
paddy@163 147 sorted := Scopes(scopes)
paddy@134 148 sort.Sort(sorted)
paddy@134 149 scopes = sorted
paddy@134 150 return scopes, nil
paddy@134 151 }