auth

Paddy 2015-05-11 Parent:cf1aef6eb81f Child:0ff23f3a4ede

166:c45b946abe78 Go to Latest

auth/token.go

Implement a GetProfileHandler. Create a Handler that will allow us to return details about a Profile. Right now, you only get a single Profile at a time, which is problematic, because it will lead to N+1 requests. But we have no reason to retrieve anyone _else_'s Profile, so it's not like you need to be fetching any Profile other than your own. Also, this requires a Token issued for the Profile in question, which means you're limited to one Profile per request, anyways. Future avenues for exploration may be an admin Token granting access to many Profiles, the unspecified service flow for accessing the API, or simply accepting that name, join date, last active date, and ID are "public information".

History
paddy@28 1 package auth
paddy@28 2
paddy@28 3 import (
paddy@123 4 "encoding/json"
paddy@28 5 "errors"
paddy@123 6 "log"
paddy@123 7 "net/http"
paddy@28 8 "time"
paddy@28 9
paddy@107 10 "code.secondbit.org/uuid.hg"
paddy@28 11 )
paddy@28 12
paddy@69 13 const (
paddy@125 14 defaultTokenExpiration = 3600 // one hour
paddy@69 15 )
paddy@69 16
paddy@123 17 func init() {
paddy@123 18 RegisterGrantType("refresh_token", GrantType{
paddy@123 19 Validate: refreshTokenValidate,
paddy@123 20 Invalidate: refreshTokenInvalidate,
paddy@123 21 IssuesRefresh: true,
paddy@123 22 ReturnToken: RenderJSONToken,
paddy@124 23 AuditString: refreshTokenAuditString,
paddy@123 24 })
paddy@123 25 }
paddy@123 26
paddy@28 27 var (
paddy@57 28 // ErrNoTokenStore is returned when a Context tries to act on a tokenStore without setting one first.
paddy@57 29 ErrNoTokenStore = errors.New("no tokenStore was specified for the Context")
paddy@57 30 // ErrTokenNotFound is returned when a Token is requested but not found in a tokenStore.
paddy@57 31 ErrTokenNotFound = errors.New("token not found in tokenStore")
paddy@57 32 // ErrTokenAlreadyExists is returned when a Token is added to a tokenStore, but another Token with
paddy@57 33 // the same AccessToken property already exists in the tokenStore.
paddy@57 34 ErrTokenAlreadyExists = errors.New("token already exists in tokenStore")
paddy@28 35 )
paddy@28 36
paddy@57 37 // Token represents an access and/or refresh token that the Client can use to access user data
paddy@57 38 // or obtain a new access token.
paddy@28 39 type Token struct {
paddy@125 40 AccessToken string
paddy@125 41 RefreshToken string
paddy@125 42 Created time.Time
paddy@125 43 CreatedFrom string
paddy@125 44 ExpiresIn int32
paddy@125 45 TokenType string
paddy@163 46 Scopes Scopes
paddy@125 47 ProfileID uuid.ID
paddy@125 48 ClientID uuid.ID
paddy@125 49 Revoked bool
paddy@125 50 RefreshRevoked bool
paddy@28 51 }
paddy@28 52
paddy@57 53 type tokenStore interface {
paddy@57 54 getToken(token string, refresh bool) (Token, error)
paddy@57 55 saveToken(token Token) error
paddy@91 56 revokeToken(token string, refresh bool) error
paddy@57 57 getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
paddy@162 58 revokeTokensByProfileID(profileID uuid.ID) error
paddy@164 59 revokeTokensByClientID(clientID uuid.ID) error
paddy@28 60 }
paddy@28 61
paddy@57 62 func (m *memstore) getToken(token string, refresh bool) (Token, error) {
paddy@28 63 if refresh {
paddy@28 64 t, err := m.lookupTokenByRefresh(token)
paddy@28 65 if err != nil {
paddy@28 66 return Token{}, err
paddy@28 67 }
paddy@28 68 token = t
paddy@28 69 }
paddy@28 70 m.tokenLock.RLock()
paddy@28 71 defer m.tokenLock.RUnlock()
paddy@28 72 result, ok := m.tokens[token]
paddy@28 73 if !ok {
paddy@28 74 return Token{}, ErrTokenNotFound
paddy@28 75 }
paddy@28 76 return result, nil
paddy@28 77 }
paddy@28 78
paddy@57 79 func (m *memstore) saveToken(token Token) error {
paddy@28 80 m.tokenLock.Lock()
paddy@28 81 defer m.tokenLock.Unlock()
paddy@28 82 _, ok := m.tokens[token.AccessToken]
paddy@28 83 if ok {
paddy@28 84 return ErrTokenAlreadyExists
paddy@28 85 }
paddy@28 86 m.tokens[token.AccessToken] = token
paddy@28 87 if token.RefreshToken != "" {
paddy@28 88 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
paddy@28 89 }
paddy@28 90 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
paddy@28 91 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
paddy@28 92 } else {
paddy@28 93 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
paddy@28 94 }
paddy@28 95 return nil
paddy@28 96 }
paddy@28 97
paddy@91 98 func (m *memstore) revokeToken(token string, refresh bool) error {
paddy@91 99 if refresh {
paddy@91 100 t, err := m.lookupTokenByRefresh(token)
paddy@91 101 if err != nil {
paddy@91 102 return err
paddy@91 103 }
paddy@91 104 token = t
paddy@91 105 }
paddy@91 106 m.tokenLock.Lock()
paddy@91 107 defer m.tokenLock.Unlock()
paddy@91 108 t, ok := m.tokens[token]
paddy@91 109 if !ok {
paddy@91 110 return ErrTokenNotFound
paddy@91 111 }
paddy@123 112 if refresh {
paddy@123 113 t.RefreshRevoked = true
paddy@123 114 } else {
paddy@123 115 t.Revoked = true
paddy@123 116 }
paddy@91 117 m.tokens[token] = t
paddy@91 118 return nil
paddy@91 119 }
paddy@91 120
paddy@162 121 func (m *memstore) revokeTokensByProfileID(profileID uuid.ID) error {
paddy@162 122 ids, err := m.lookupTokensByProfileID(profileID.String())
paddy@162 123 if err != nil {
paddy@162 124 return err
paddy@162 125 }
paddy@162 126 if len(ids) < 1 {
paddy@162 127 return ErrProfileNotFound
paddy@162 128 }
paddy@162 129 m.tokenLock.Lock()
paddy@162 130 defer m.tokenLock.Unlock()
paddy@162 131 for _, id := range ids {
paddy@164 132 token := m.tokens[id]
paddy@164 133 token.Revoked = true
paddy@164 134 token.RefreshRevoked = true
paddy@164 135 m.tokens[id] = token
paddy@164 136 }
paddy@164 137 return nil
paddy@164 138 }
paddy@164 139
paddy@164 140 func (m *memstore) revokeTokensByClientID(clientID uuid.ID) error {
paddy@164 141 m.tokenLock.Lock()
paddy@164 142 defer m.tokenLock.Unlock()
paddy@164 143 for id, token := range m.tokens {
paddy@164 144 if !token.ClientID.Equal(clientID) {
paddy@164 145 continue
paddy@164 146 }
paddy@164 147 token.Revoked = true
paddy@164 148 token.RefreshRevoked = true
paddy@164 149 m.tokens[id] = token
paddy@162 150 }
paddy@162 151 return nil
paddy@162 152 }
paddy@162 153
paddy@57 154 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
paddy@28 155 ids, err := m.lookupTokensByProfileID(profileID.String())
paddy@28 156 if err != nil {
paddy@28 157 return []Token{}, err
paddy@28 158 }
paddy@28 159 if len(ids) > num+offset {
paddy@28 160 ids = ids[offset : num+offset]
paddy@28 161 } else if len(ids) > offset {
paddy@28 162 ids = ids[offset:]
paddy@28 163 } else {
paddy@28 164 return []Token{}, nil
paddy@28 165 }
paddy@28 166 tokens := []Token{}
paddy@28 167 for _, id := range ids {
paddy@57 168 token, err := m.getToken(id, false)
paddy@28 169 if err != nil {
paddy@28 170 return []Token{}, err
paddy@28 171 }
paddy@28 172 tokens = append(tokens, token)
paddy@28 173 }
paddy@28 174 return tokens, nil
paddy@28 175 }
paddy@123 176
paddy@163 177 func refreshTokenValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
paddy@123 178 enc := json.NewEncoder(w)
paddy@123 179 refresh := r.PostFormValue("refresh_token")
paddy@123 180 if refresh == "" {
paddy@123 181 w.WriteHeader(http.StatusBadRequest)
paddy@123 182 renderJSONError(enc, "invalid_request")
paddy@123 183 return
paddy@123 184 }
paddy@123 185 token, err := context.GetToken(refresh, true)
paddy@123 186 if err != nil {
paddy@123 187 if err == ErrTokenNotFound {
paddy@123 188 w.WriteHeader(http.StatusBadRequest)
paddy@123 189 renderJSONError(enc, "invalid_grant")
paddy@123 190 return
paddy@123 191 }
paddy@123 192 log.Println("Error exchanging refresh token:", err)
paddy@123 193 w.WriteHeader(http.StatusInternalServerError)
paddy@123 194 renderJSONError(enc, "server_error")
paddy@123 195 return
paddy@123 196 }
paddy@123 197 clientID, _, ok := getClientAuth(w, r, true)
paddy@123 198 if !ok {
paddy@123 199 return
paddy@123 200 }
paddy@123 201 if !token.ClientID.Equal(clientID) {
paddy@123 202 w.WriteHeader(http.StatusBadRequest)
paddy@123 203 renderJSONError(enc, "invalid_grant")
paddy@123 204 return
paddy@123 205 }
paddy@123 206 if token.RefreshRevoked {
paddy@123 207 w.WriteHeader(http.StatusBadRequest)
paddy@123 208 renderJSONError(enc, "invalid_grant")
paddy@123 209 return
paddy@123 210 }
paddy@135 211 return token.Scopes, token.ProfileID, true
paddy@123 212 }
paddy@123 213
paddy@123 214 func refreshTokenInvalidate(r *http.Request, context Context) error {
paddy@123 215 refresh := r.PostFormValue("refresh_token")
paddy@123 216 if refresh == "" {
paddy@123 217 return ErrTokenNotFound
paddy@123 218 }
paddy@123 219 return context.RevokeToken(refresh, true)
paddy@123 220 }
paddy@124 221
paddy@124 222 func refreshTokenAuditString(r *http.Request) string {
paddy@124 223 return "refresh_token:" + r.PostFormValue("refresh_token")
paddy@124 224 }
paddy@128 225
paddy@128 226 // BUG(paddy): We need to implement a handler for revoking a token.
paddy@128 227 // BUG(paddy): We need to implement a handler for listing active tokens.