auth
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.
| 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 } |