auth

Paddy 2014-12-13 Parent:61a802849b51 Child:e81407f7ecb1

91:c1b3a36af1a7 Go to Latest

auth/token.go

Implement revoking tokens. Add a revokeToken method to tokenStore, and an implementation for memstore. All it does is set the token's Revoked property to true.

History
paddy@28 1 package auth
paddy@28 2
paddy@28 3 import (
paddy@28 4 "errors"
paddy@28 5 "time"
paddy@28 6
paddy@45 7 "code.secondbit.org/uuid"
paddy@28 8 )
paddy@28 9
paddy@69 10 const (
paddy@88 11 defaultTokenExpiration = 3600 // one hour
paddy@88 12 defaultRefreshTokenExpiration = 86400 // one day
paddy@69 13 )
paddy@69 14
paddy@28 15 var (
paddy@57 16 // ErrNoTokenStore is returned when a Context tries to act on a tokenStore without setting one first.
paddy@57 17 ErrNoTokenStore = errors.New("no tokenStore was specified for the Context")
paddy@57 18 // ErrTokenNotFound is returned when a Token is requested but not found in a tokenStore.
paddy@57 19 ErrTokenNotFound = errors.New("token not found in tokenStore")
paddy@57 20 // ErrTokenAlreadyExists is returned when a Token is added to a tokenStore, but another Token with
paddy@57 21 // the same AccessToken property already exists in the tokenStore.
paddy@57 22 ErrTokenAlreadyExists = errors.New("token already exists in tokenStore")
paddy@28 23 )
paddy@28 24
paddy@57 25 // Token represents an access and/or refresh token that the Client can use to access user data
paddy@57 26 // or obtain a new access token.
paddy@28 27 type Token struct {
paddy@88 28 AccessToken string
paddy@88 29 RefreshToken string
paddy@88 30 Created time.Time
paddy@88 31 CreatedFrom string
paddy@88 32 ExpiresIn int32
paddy@88 33 RefreshExpiresIn int32
paddy@88 34 TokenType string
paddy@88 35 Scope string
paddy@88 36 ProfileID uuid.ID
paddy@88 37 Revoked bool
paddy@28 38 }
paddy@28 39
paddy@57 40 type tokenStore interface {
paddy@88 41 // BUG(paddy): need to be able to revoke tokens and refresh tokens
paddy@57 42 getToken(token string, refresh bool) (Token, error)
paddy@57 43 saveToken(token Token) error
paddy@57 44 removeToken(token string) error
paddy@91 45 revokeToken(token string, refresh bool) error
paddy@57 46 getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
paddy@28 47 }
paddy@28 48
paddy@57 49 func (m *memstore) getToken(token string, refresh bool) (Token, error) {
paddy@28 50 if refresh {
paddy@28 51 t, err := m.lookupTokenByRefresh(token)
paddy@28 52 if err != nil {
paddy@28 53 return Token{}, err
paddy@28 54 }
paddy@28 55 token = t
paddy@28 56 }
paddy@28 57 m.tokenLock.RLock()
paddy@28 58 defer m.tokenLock.RUnlock()
paddy@28 59 result, ok := m.tokens[token]
paddy@28 60 if !ok {
paddy@28 61 return Token{}, ErrTokenNotFound
paddy@28 62 }
paddy@28 63 return result, nil
paddy@28 64 }
paddy@28 65
paddy@57 66 func (m *memstore) saveToken(token Token) error {
paddy@28 67 m.tokenLock.Lock()
paddy@28 68 defer m.tokenLock.Unlock()
paddy@28 69 _, ok := m.tokens[token.AccessToken]
paddy@28 70 if ok {
paddy@28 71 return ErrTokenAlreadyExists
paddy@28 72 }
paddy@28 73 m.tokens[token.AccessToken] = token
paddy@28 74 if token.RefreshToken != "" {
paddy@28 75 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
paddy@28 76 }
paddy@28 77 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
paddy@28 78 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
paddy@28 79 } else {
paddy@28 80 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
paddy@28 81 }
paddy@28 82 return nil
paddy@28 83 }
paddy@28 84
paddy@57 85 func (m *memstore) removeToken(token string) error {
paddy@28 86 m.tokenLock.Lock()
paddy@28 87 defer m.tokenLock.Unlock()
paddy@28 88 t, ok := m.tokens[token]
paddy@28 89 if !ok {
paddy@28 90 return ErrTokenNotFound
paddy@28 91 }
paddy@28 92 delete(m.tokens, token)
paddy@28 93 if t.RefreshToken != "" {
paddy@28 94 delete(m.refreshTokenLookup, t.RefreshToken)
paddy@28 95 }
paddy@28 96 pos := -1
paddy@28 97 for p, item := range m.profileTokenLookup[t.ProfileID.String()] {
paddy@28 98 if item == token {
paddy@28 99 pos = p
paddy@28 100 break
paddy@28 101 }
paddy@28 102 }
paddy@28 103 if pos >= 0 {
paddy@28 104 m.profileTokenLookup[t.ProfileID.String()] = append(m.profileTokenLookup[t.ProfileID.String()][:pos], m.profileTokenLookup[t.ProfileID.String()][pos+1:]...)
paddy@28 105 }
paddy@28 106 return nil
paddy@28 107 }
paddy@28 108
paddy@91 109 func (m *memstore) revokeToken(token string, refresh bool) error {
paddy@91 110 if refresh {
paddy@91 111 t, err := m.lookupTokenByRefresh(token)
paddy@91 112 if err != nil {
paddy@91 113 return err
paddy@91 114 }
paddy@91 115 token = t
paddy@91 116 }
paddy@91 117 m.tokenLock.Lock()
paddy@91 118 defer m.tokenLock.Unlock()
paddy@91 119 t, ok := m.tokens[token]
paddy@91 120 if !ok {
paddy@91 121 return ErrTokenNotFound
paddy@91 122 }
paddy@91 123 t.Revoked = true
paddy@91 124 m.tokens[token] = t
paddy@91 125 return nil
paddy@91 126 }
paddy@91 127
paddy@57 128 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
paddy@28 129 ids, err := m.lookupTokensByProfileID(profileID.String())
paddy@28 130 if err != nil {
paddy@28 131 return []Token{}, err
paddy@28 132 }
paddy@28 133 if len(ids) > num+offset {
paddy@28 134 ids = ids[offset : num+offset]
paddy@28 135 } else if len(ids) > offset {
paddy@28 136 ids = ids[offset:]
paddy@28 137 } else {
paddy@28 138 return []Token{}, nil
paddy@28 139 }
paddy@28 140 tokens := []Token{}
paddy@28 141 for _, id := range ids {
paddy@57 142 token, err := m.getToken(id, false)
paddy@28 143 if err != nil {
paddy@28 144 return []Token{}, err
paddy@28 145 }
paddy@28 146 tokens = append(tokens, token)
paddy@28 147 }
paddy@28 148 return tokens, nil
paddy@28 149 }