auth

Paddy 2015-03-18 Parent:d30a3a12d387 Child:762953f6a7f2

145:e660a38fa936 Go to Latest

auth/token.go

Implement UpdateProfileHandler. Implement a handler that will allow users to update their Profiles through the API.

History
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 []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 revokeToken(token string, refresh bool) error
57 getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
58 }
60 func (m *memstore) getToken(token string, refresh bool) (Token, error) {
61 if refresh {
62 t, err := m.lookupTokenByRefresh(token)
63 if err != nil {
64 return Token{}, err
65 }
66 token = t
67 }
68 m.tokenLock.RLock()
69 defer m.tokenLock.RUnlock()
70 result, ok := m.tokens[token]
71 if !ok {
72 return Token{}, ErrTokenNotFound
73 }
74 return result, nil
75 }
77 func (m *memstore) saveToken(token Token) error {
78 m.tokenLock.Lock()
79 defer m.tokenLock.Unlock()
80 _, ok := m.tokens[token.AccessToken]
81 if ok {
82 return ErrTokenAlreadyExists
83 }
84 m.tokens[token.AccessToken] = token
85 if token.RefreshToken != "" {
86 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
87 }
88 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
89 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
90 } else {
91 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
92 }
93 return nil
94 }
96 func (m *memstore) revokeToken(token string, refresh bool) error {
97 if refresh {
98 t, err := m.lookupTokenByRefresh(token)
99 if err != nil {
100 return err
101 }
102 token = t
103 }
104 m.tokenLock.Lock()
105 defer m.tokenLock.Unlock()
106 t, ok := m.tokens[token]
107 if !ok {
108 return ErrTokenNotFound
109 }
110 if refresh {
111 t.RefreshRevoked = true
112 } else {
113 t.Revoked = true
114 }
115 m.tokens[token] = t
116 return nil
117 }
119 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
120 ids, err := m.lookupTokensByProfileID(profileID.String())
121 if err != nil {
122 return []Token{}, err
123 }
124 if len(ids) > num+offset {
125 ids = ids[offset : num+offset]
126 } else if len(ids) > offset {
127 ids = ids[offset:]
128 } else {
129 return []Token{}, nil
130 }
131 tokens := []Token{}
132 for _, id := range ids {
133 token, err := m.getToken(id, false)
134 if err != nil {
135 return []Token{}, err
136 }
137 tokens = append(tokens, token)
138 }
139 return tokens, nil
140 }
142 func refreshTokenValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes []string, profileID uuid.ID, valid bool) {
143 enc := json.NewEncoder(w)
144 refresh := r.PostFormValue("refresh_token")
145 if refresh == "" {
146 w.WriteHeader(http.StatusBadRequest)
147 renderJSONError(enc, "invalid_request")
148 return
149 }
150 token, err := context.GetToken(refresh, true)
151 if err != nil {
152 if err == ErrTokenNotFound {
153 w.WriteHeader(http.StatusBadRequest)
154 renderJSONError(enc, "invalid_grant")
155 return
156 }
157 log.Println("Error exchanging refresh token:", err)
158 w.WriteHeader(http.StatusInternalServerError)
159 renderJSONError(enc, "server_error")
160 return
161 }
162 clientID, _, ok := getClientAuth(w, r, true)
163 if !ok {
164 return
165 }
166 if !token.ClientID.Equal(clientID) {
167 w.WriteHeader(http.StatusBadRequest)
168 renderJSONError(enc, "invalid_grant")
169 return
170 }
171 if token.RefreshRevoked {
172 w.WriteHeader(http.StatusBadRequest)
173 renderJSONError(enc, "invalid_grant")
174 return
175 }
176 return token.Scopes, token.ProfileID, true
177 }
179 func refreshTokenInvalidate(r *http.Request, context Context) error {
180 refresh := r.PostFormValue("refresh_token")
181 if refresh == "" {
182 return ErrTokenNotFound
183 }
184 return context.RevokeToken(refresh, true)
185 }
187 func refreshTokenAuditString(r *http.Request) string {
188 return "refresh_token:" + r.PostFormValue("refresh_token")
189 }
191 // BUG(paddy): We need to implement a handler for revoking a token.
192 // BUG(paddy): We need to implement a handler for listing active tokens.