auth
auth/token.go
Store salts and passphrases as hex-encoded strings. Update our passphraseScheme.create function signature to return strings. Hex encode our passphrases and salts when encrypthing them so they're easier to store safely. Decode our salt before using it to check candidate passphrases.
| 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@57 | 41 getToken(token string, refresh bool) (Token, error) |
| paddy@57 | 42 saveToken(token Token) error |
| paddy@57 | 43 removeToken(token string) error |
| paddy@91 | 44 revokeToken(token string, refresh bool) 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@91 | 108 func (m *memstore) revokeToken(token string, refresh bool) error { |
| paddy@91 | 109 if refresh { |
| paddy@91 | 110 t, err := m.lookupTokenByRefresh(token) |
| paddy@91 | 111 if err != nil { |
| paddy@91 | 112 return err |
| paddy@91 | 113 } |
| paddy@91 | 114 token = t |
| paddy@91 | 115 } |
| paddy@91 | 116 m.tokenLock.Lock() |
| paddy@91 | 117 defer m.tokenLock.Unlock() |
| paddy@91 | 118 t, ok := m.tokens[token] |
| paddy@91 | 119 if !ok { |
| paddy@91 | 120 return ErrTokenNotFound |
| paddy@91 | 121 } |
| paddy@91 | 122 t.Revoked = true |
| paddy@91 | 123 m.tokens[token] = t |
| paddy@91 | 124 return nil |
| paddy@91 | 125 } |
| paddy@91 | 126 |
| paddy@57 | 127 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) { |
| paddy@28 | 128 ids, err := m.lookupTokensByProfileID(profileID.String()) |
| paddy@28 | 129 if err != nil { |
| paddy@28 | 130 return []Token{}, err |
| paddy@28 | 131 } |
| paddy@28 | 132 if len(ids) > num+offset { |
| paddy@28 | 133 ids = ids[offset : num+offset] |
| paddy@28 | 134 } else if len(ids) > offset { |
| paddy@28 | 135 ids = ids[offset:] |
| paddy@28 | 136 } else { |
| paddy@28 | 137 return []Token{}, nil |
| paddy@28 | 138 } |
| paddy@28 | 139 tokens := []Token{} |
| paddy@28 | 140 for _, id := range ids { |
| paddy@57 | 141 token, err := m.getToken(id, false) |
| paddy@28 | 142 if err != nil { |
| paddy@28 | 143 return []Token{}, err |
| paddy@28 | 144 } |
| paddy@28 | 145 tokens = append(tokens, token) |
| paddy@28 | 146 } |
| paddy@28 | 147 return tokens, nil |
| paddy@28 | 148 } |