auth

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

171:807d20a0b197 Go to Latest

auth/token.go

Add login verification to Config. Keep track of how we're going to verify logins using the Config struct.

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