auth
auth/token.go
Remove refresh token expiration, update implicit token. Refresh tokens no longer expire, because they're supposed to be long-lived, and we have no way to communicate to the user exactly how long-lived they are. Instead, they are invalidated after a single use, which should prevent too much abuse. It gives them an effective lifespan of "default token expiration, or until used", which I think is Good Enough. Also updated our implicit token to set the CreatedFrom to "implicit" and the ClientID to the client ID, which is important, I guess. It's really annoying that we have that logic in two different places.
1 package auth
3 import (
4 "encoding/json"
5 "errors"
6 "log"
7 "net/http"
8 "time"
10 "code.secondbit.org/uuid.hg"
11 )
13 const (
14 defaultTokenExpiration = 3600 // one hour
15 )
17 func init() {
18 RegisterGrantType("refresh_token", GrantType{
19 Validate: refreshTokenValidate,
20 Invalidate: refreshTokenInvalidate,
21 IssuesRefresh: true,
22 ReturnToken: RenderJSONToken,
23 AuditString: refreshTokenAuditString,
24 })
25 }
27 var (
28 // ErrNoTokenStore is returned when a Context tries to act on a tokenStore without setting one first.
29 ErrNoTokenStore = errors.New("no tokenStore was specified for the Context")
30 // ErrTokenNotFound is returned when a Token is requested but not found in a tokenStore.
31 ErrTokenNotFound = errors.New("token not found in tokenStore")
32 // ErrTokenAlreadyExists is returned when a Token is added to a tokenStore, but another Token with
33 // the same AccessToken property already exists in the tokenStore.
34 ErrTokenAlreadyExists = errors.New("token already exists in tokenStore")
35 )
37 // Token represents an access and/or refresh token that the Client can use to access user data
38 // or obtain a new access token.
39 type Token struct {
40 AccessToken string
41 RefreshToken string
42 Created time.Time
43 CreatedFrom string
44 ExpiresIn int32
45 TokenType string
46 Scope string
47 ProfileID uuid.ID
48 ClientID uuid.ID
49 Revoked bool
50 RefreshRevoked bool
51 }
53 type tokenStore interface {
54 getToken(token string, refresh bool) (Token, error)
55 saveToken(token Token) error
56 removeToken(token string) error
57 revokeToken(token string, refresh bool) error
58 getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
59 }
61 func (m *memstore) getToken(token string, refresh bool) (Token, error) {
62 if refresh {
63 t, err := m.lookupTokenByRefresh(token)
64 if err != nil {
65 return Token{}, err
66 }
67 token = t
68 }
69 m.tokenLock.RLock()
70 defer m.tokenLock.RUnlock()
71 result, ok := m.tokens[token]
72 if !ok {
73 return Token{}, ErrTokenNotFound
74 }
75 return result, nil
76 }
78 func (m *memstore) saveToken(token Token) error {
79 m.tokenLock.Lock()
80 defer m.tokenLock.Unlock()
81 _, ok := m.tokens[token.AccessToken]
82 if ok {
83 return ErrTokenAlreadyExists
84 }
85 m.tokens[token.AccessToken] = token
86 if token.RefreshToken != "" {
87 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
88 }
89 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
90 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
91 } else {
92 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
93 }
94 return nil
95 }
97 func (m *memstore) removeToken(token string) error {
98 m.tokenLock.Lock()
99 defer m.tokenLock.Unlock()
100 t, ok := m.tokens[token]
101 if !ok {
102 return ErrTokenNotFound
103 }
104 delete(m.tokens, token)
105 if t.RefreshToken != "" {
106 delete(m.refreshTokenLookup, t.RefreshToken)
107 }
108 pos := -1
109 for p, item := range m.profileTokenLookup[t.ProfileID.String()] {
110 if item == token {
111 pos = p
112 break
113 }
114 }
115 if pos >= 0 {
116 m.profileTokenLookup[t.ProfileID.String()] = append(m.profileTokenLookup[t.ProfileID.String()][:pos], m.profileTokenLookup[t.ProfileID.String()][pos+1:]...)
117 }
118 return nil
119 }
121 func (m *memstore) revokeToken(token string, refresh bool) error {
122 if refresh {
123 t, err := m.lookupTokenByRefresh(token)
124 if err != nil {
125 return err
126 }
127 token = t
128 }
129 m.tokenLock.Lock()
130 defer m.tokenLock.Unlock()
131 t, ok := m.tokens[token]
132 if !ok {
133 return ErrTokenNotFound
134 }
135 if refresh {
136 t.RefreshRevoked = true
137 } else {
138 t.Revoked = true
139 }
140 m.tokens[token] = t
141 return nil
142 }
144 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
145 ids, err := m.lookupTokensByProfileID(profileID.String())
146 if err != nil {
147 return []Token{}, err
148 }
149 if len(ids) > num+offset {
150 ids = ids[offset : num+offset]
151 } else if len(ids) > offset {
152 ids = ids[offset:]
153 } else {
154 return []Token{}, nil
155 }
156 tokens := []Token{}
157 for _, id := range ids {
158 token, err := m.getToken(id, false)
159 if err != nil {
160 return []Token{}, err
161 }
162 tokens = append(tokens, token)
163 }
164 return tokens, nil
165 }
167 func refreshTokenValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) {
168 enc := json.NewEncoder(w)
169 refresh := r.PostFormValue("refresh_token")
170 if refresh == "" {
171 w.WriteHeader(http.StatusBadRequest)
172 renderJSONError(enc, "invalid_request")
173 return
174 }
175 token, err := context.GetToken(refresh, true)
176 if err != nil {
177 if err == ErrTokenNotFound {
178 w.WriteHeader(http.StatusBadRequest)
179 renderJSONError(enc, "invalid_grant")
180 return
181 }
182 log.Println("Error exchanging refresh token:", err)
183 w.WriteHeader(http.StatusInternalServerError)
184 renderJSONError(enc, "server_error")
185 return
186 }
187 clientID, _, ok := getClientAuth(w, r, true)
188 if !ok {
189 return
190 }
191 if !token.ClientID.Equal(clientID) {
192 w.WriteHeader(http.StatusBadRequest)
193 renderJSONError(enc, "invalid_grant")
194 return
195 }
196 if token.RefreshRevoked {
197 w.WriteHeader(http.StatusBadRequest)
198 renderJSONError(enc, "invalid_grant")
199 return
200 }
201 return token.Scope, token.ProfileID, true
202 }
204 func refreshTokenInvalidate(r *http.Request, context Context) error {
205 refresh := r.PostFormValue("refresh_token")
206 if refresh == "" {
207 return ErrTokenNotFound
208 }
209 return context.RevokeToken(refresh, true)
210 }
212 func refreshTokenAuditString(r *http.Request) string {
213 return "refresh_token:" + r.PostFormValue("refresh_token")
214 }