auth
auth/token.go
Implement a GetProfileHandler. Create a Handler that will allow us to return details about a Profile. Right now, you only get a single Profile at a time, which is problematic, because it will lead to N+1 requests. But we have no reason to retrieve anyone _else_'s Profile, so it's not like you need to be fetching any Profile other than your own. Also, this requires a Token issued for the Profile in question, which means you're limited to one Profile per request, anyways. Future avenues for exploration may be an admin Token granting access to many Profiles, the unspecified service flow for accessing the API, or simply accepting that name, join date, last active date, and ID are "public information".
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 Scopes Scopes
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 revokeToken(token string, refresh bool) error
57 getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
58 revokeTokensByProfileID(profileID uuid.ID) error
59 revokeTokensByClientID(clientID uuid.ID) error
60 }
62 func (m *memstore) getToken(token string, refresh bool) (Token, error) {
63 if refresh {
64 t, err := m.lookupTokenByRefresh(token)
65 if err != nil {
66 return Token{}, err
67 }
68 token = t
69 }
70 m.tokenLock.RLock()
71 defer m.tokenLock.RUnlock()
72 result, ok := m.tokens[token]
73 if !ok {
74 return Token{}, ErrTokenNotFound
75 }
76 return result, nil
77 }
79 func (m *memstore) saveToken(token Token) error {
80 m.tokenLock.Lock()
81 defer m.tokenLock.Unlock()
82 _, ok := m.tokens[token.AccessToken]
83 if ok {
84 return ErrTokenAlreadyExists
85 }
86 m.tokens[token.AccessToken] = token
87 if token.RefreshToken != "" {
88 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
89 }
90 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
91 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
92 } else {
93 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
94 }
95 return nil
96 }
98 func (m *memstore) revokeToken(token string, refresh bool) error {
99 if refresh {
100 t, err := m.lookupTokenByRefresh(token)
101 if err != nil {
102 return err
103 }
104 token = t
105 }
106 m.tokenLock.Lock()
107 defer m.tokenLock.Unlock()
108 t, ok := m.tokens[token]
109 if !ok {
110 return ErrTokenNotFound
111 }
112 if refresh {
113 t.RefreshRevoked = true
114 } else {
115 t.Revoked = true
116 }
117 m.tokens[token] = t
118 return nil
119 }
121 func (m *memstore) revokeTokensByProfileID(profileID uuid.ID) error {
122 ids, err := m.lookupTokensByProfileID(profileID.String())
123 if err != nil {
124 return err
125 }
126 if len(ids) < 1 {
127 return ErrProfileNotFound
128 }
129 m.tokenLock.Lock()
130 defer m.tokenLock.Unlock()
131 for _, id := range ids {
132 token := m.tokens[id]
133 token.Revoked = true
134 token.RefreshRevoked = true
135 m.tokens[id] = token
136 }
137 return nil
138 }
140 func (m *memstore) revokeTokensByClientID(clientID uuid.ID) error {
141 m.tokenLock.Lock()
142 defer m.tokenLock.Unlock()
143 for id, token := range m.tokens {
144 if !token.ClientID.Equal(clientID) {
145 continue
146 }
147 token.Revoked = true
148 token.RefreshRevoked = true
149 m.tokens[id] = token
150 }
151 return nil
152 }
154 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
155 ids, err := m.lookupTokensByProfileID(profileID.String())
156 if err != nil {
157 return []Token{}, err
158 }
159 if len(ids) > num+offset {
160 ids = ids[offset : num+offset]
161 } else if len(ids) > offset {
162 ids = ids[offset:]
163 } else {
164 return []Token{}, nil
165 }
166 tokens := []Token{}
167 for _, id := range ids {
168 token, err := m.getToken(id, false)
169 if err != nil {
170 return []Token{}, err
171 }
172 tokens = append(tokens, token)
173 }
174 return tokens, nil
175 }
177 func refreshTokenValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) {
178 enc := json.NewEncoder(w)
179 refresh := r.PostFormValue("refresh_token")
180 if refresh == "" {
181 w.WriteHeader(http.StatusBadRequest)
182 renderJSONError(enc, "invalid_request")
183 return
184 }
185 token, err := context.GetToken(refresh, true)
186 if err != nil {
187 if err == ErrTokenNotFound {
188 w.WriteHeader(http.StatusBadRequest)
189 renderJSONError(enc, "invalid_grant")
190 return
191 }
192 log.Println("Error exchanging refresh token:", err)
193 w.WriteHeader(http.StatusInternalServerError)
194 renderJSONError(enc, "server_error")
195 return
196 }
197 clientID, _, ok := getClientAuth(w, r, true)
198 if !ok {
199 return
200 }
201 if !token.ClientID.Equal(clientID) {
202 w.WriteHeader(http.StatusBadRequest)
203 renderJSONError(enc, "invalid_grant")
204 return
205 }
206 if token.RefreshRevoked {
207 w.WriteHeader(http.StatusBadRequest)
208 renderJSONError(enc, "invalid_grant")
209 return
210 }
211 return token.Scopes, token.ProfileID, true
212 }
214 func refreshTokenInvalidate(r *http.Request, context Context) error {
215 refresh := r.PostFormValue("refresh_token")
216 if refresh == "" {
217 return ErrTokenNotFound
218 }
219 return context.RevokeToken(refresh, true)
220 }
222 func refreshTokenAuditString(r *http.Request) string {
223 return "refresh_token:" + r.PostFormValue("refresh_token")
224 }
226 // BUG(paddy): We need to implement a handler for revoking a token.
227 // BUG(paddy): We need to implement a handler for listing active tokens.