auth

Paddy 2014-12-13 Parent:42bc3e44f4fe Child:c1b3a36af1a7

88:61a802849b51 Go to Latest

auth/token.go

Add refresh expiration and revoking tokens. Add a property to hold the expiration date for a refresh token. Add a TODO for a tokenStore method to revoke a token.

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@57 45 getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
paddy@28 46 }
paddy@28 47
paddy@57 48 func (m *memstore) getToken(token string, refresh bool) (Token, error) {
paddy@28 49 if refresh {
paddy@28 50 t, err := m.lookupTokenByRefresh(token)
paddy@28 51 if err != nil {
paddy@28 52 return Token{}, err
paddy@28 53 }
paddy@28 54 token = t
paddy@28 55 }
paddy@28 56 m.tokenLock.RLock()
paddy@28 57 defer m.tokenLock.RUnlock()
paddy@28 58 result, ok := m.tokens[token]
paddy@28 59 if !ok {
paddy@28 60 return Token{}, ErrTokenNotFound
paddy@28 61 }
paddy@28 62 return result, nil
paddy@28 63 }
paddy@28 64
paddy@57 65 func (m *memstore) saveToken(token Token) error {
paddy@28 66 m.tokenLock.Lock()
paddy@28 67 defer m.tokenLock.Unlock()
paddy@28 68 _, ok := m.tokens[token.AccessToken]
paddy@28 69 if ok {
paddy@28 70 return ErrTokenAlreadyExists
paddy@28 71 }
paddy@28 72 m.tokens[token.AccessToken] = token
paddy@28 73 if token.RefreshToken != "" {
paddy@28 74 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
paddy@28 75 }
paddy@28 76 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
paddy@28 77 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
paddy@28 78 } else {
paddy@28 79 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
paddy@28 80 }
paddy@28 81 return nil
paddy@28 82 }
paddy@28 83
paddy@57 84 func (m *memstore) removeToken(token string) error {
paddy@28 85 m.tokenLock.Lock()
paddy@28 86 defer m.tokenLock.Unlock()
paddy@28 87 t, ok := m.tokens[token]
paddy@28 88 if !ok {
paddy@28 89 return ErrTokenNotFound
paddy@28 90 }
paddy@28 91 delete(m.tokens, token)
paddy@28 92 if t.RefreshToken != "" {
paddy@28 93 delete(m.refreshTokenLookup, t.RefreshToken)
paddy@28 94 }
paddy@28 95 pos := -1
paddy@28 96 for p, item := range m.profileTokenLookup[t.ProfileID.String()] {
paddy@28 97 if item == token {
paddy@28 98 pos = p
paddy@28 99 break
paddy@28 100 }
paddy@28 101 }
paddy@28 102 if pos >= 0 {
paddy@28 103 m.profileTokenLookup[t.ProfileID.String()] = append(m.profileTokenLookup[t.ProfileID.String()][:pos], m.profileTokenLookup[t.ProfileID.String()][pos+1:]...)
paddy@28 104 }
paddy@28 105 return nil
paddy@28 106 }
paddy@28 107
paddy@57 108 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
paddy@28 109 ids, err := m.lookupTokensByProfileID(profileID.String())
paddy@28 110 if err != nil {
paddy@28 111 return []Token{}, err
paddy@28 112 }
paddy@28 113 if len(ids) > num+offset {
paddy@28 114 ids = ids[offset : num+offset]
paddy@28 115 } else if len(ids) > offset {
paddy@28 116 ids = ids[offset:]
paddy@28 117 } else {
paddy@28 118 return []Token{}, nil
paddy@28 119 }
paddy@28 120 tokens := []Token{}
paddy@28 121 for _, id := range ids {
paddy@57 122 token, err := m.getToken(id, false)
paddy@28 123 if err != nil {
paddy@28 124 return []Token{}, err
paddy@28 125 }
paddy@28 126 tokens = append(tokens, token)
paddy@28 127 }
paddy@28 128 return tokens, nil
paddy@28 129 }