auth

Paddy 2015-12-14 Parent:b7e685839a1b

182:cd5f07f9811b Go to Latest

auth/token.go

Update nsq import path. go-nsq has moved to nsqio/go-nsq, so we need to update the import path appropriately.

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