auth
auth/token.go
Log ignored errors, grant revocations can return an error. Turn a few TODOs for logging errors into calls to log the actual error. Change the return type of grant revocations for GrantTypes to an error, so they can be logged by the system, not each GrantType. Implement a stub of the revocation function for the authcode GrantType.
| 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 } |