auth
2015-12-14
Parent:b7e685839a1b
auth/token.go
Update nsq import path. go-nsq has moved to nsqio/go-nsq, so we need to update the import path appropriately.
1 package auth
3 import (
4 "encoding/json"
5 "errors"
6 "log"
7 "net/http"
8 "strings"
9 "time"
11 "code.secondbit.org/scopes.hg/types"
12 "code.secondbit.org/uuid.hg"
14 "github.com/dgrijalva/jwt-go"
15 )
17 const (
18 defaultTokenExpiration = 900 // fifteen minutes
19 )
21 func init() {
22 RegisterGrantType("refresh_token", GrantType{
23 Validate: refreshTokenValidate,
24 Invalidate: refreshTokenInvalidate,
25 IssuesRefresh: true,
26 ReturnToken: RenderJSONToken,
27 AuditString: refreshTokenAuditString,
28 })
29 }
31 var (
32 // ErrNoTokenStore is returned when a Context tries to act on a tokenStore without setting one first.
33 ErrNoTokenStore = errors.New("no tokenStore was specified for the Context")
34 // ErrTokenNotFound is returned when a Token is requested but not found in a tokenStore.
35 ErrTokenNotFound = errors.New("token not found in tokenStore")
36 // ErrTokenAlreadyExists is returned when a Token is added to a tokenStore, but another Token with
37 // the same AccessToken property already exists in the tokenStore.
38 ErrTokenAlreadyExists = errors.New("token already exists in tokenStore")
39 )
41 // Token represents an access and/or refresh token that the Client can use to access user data
42 // or obtain a new access token.
43 type Token struct {
44 AccessToken string
45 RefreshToken string
46 Created time.Time
47 CreatedFrom string
48 ExpiresIn int32
49 TokenType string
50 Scopes scopeTypes.Scopes
51 ProfileID uuid.ID
52 ClientID uuid.ID
53 Revoked bool
54 }
56 func (t Token) GenerateAccessToken(privateKey []byte) (string, error) {
57 access := jwt.New(jwt.SigningMethodHS256)
58 access.Claims["iss"] = t.ClientID
59 access.Claims["sub"] = t.ProfileID
60 access.Claims["exp"] = t.Created.Add(defaultTokenExpiration * time.Second).Unix()
61 access.Claims["nbf"] = t.Created.Add(-2 * time.Minute).Unix()
62 access.Claims["iat"] = t.Created.Unix()
63 access.Claims["scope"] = strings.Join(t.Scopes.Strings(), " ")
64 return access.SignedString(privateKey)
65 }
67 // BUG(paddy): Now that access tokens are generated and have a meaning, refresh tokens should be the primary key
69 type tokenStore interface {
70 getToken(token string, refresh bool) (Token, error)
71 saveToken(token Token) error
72 revokeToken(token string) error
73 getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error)
74 revokeTokensByProfileID(profileID uuid.ID) error
75 revokeTokensByClientID(clientID uuid.ID) error
76 }
78 func (m *memstore) getToken(token string, refresh bool) (Token, error) {
79 if refresh {
80 t, err := m.lookupTokenByRefresh(token)
81 if err != nil {
82 return Token{}, err
83 }
84 token = t
85 }
86 m.tokenLock.RLock()
87 defer m.tokenLock.RUnlock()
88 result, ok := m.tokens[token]
89 if !ok {
90 return Token{}, ErrTokenNotFound
91 }
92 return result, nil
93 }
95 func (m *memstore) saveToken(token Token) error {
96 m.tokenLock.Lock()
97 defer m.tokenLock.Unlock()
98 _, ok := m.tokens[token.AccessToken]
99 if ok {
100 return ErrTokenAlreadyExists
101 }
102 m.tokens[token.AccessToken] = token
103 if token.RefreshToken != "" {
104 m.refreshTokenLookup[token.RefreshToken] = token.AccessToken
105 }
106 if _, ok = m.profileTokenLookup[token.ProfileID.String()]; ok {
107 m.profileTokenLookup[token.ProfileID.String()] = append(m.profileTokenLookup[token.ProfileID.String()], token.AccessToken)
108 } else {
109 m.profileTokenLookup[token.ProfileID.String()] = []string{token.AccessToken}
110 }
111 return nil
112 }
114 func (m *memstore) revokeToken(token string) error {
115 token, err := m.lookupTokenByRefresh(token)
116 if err != nil {
117 return err
118 }
119 m.tokenLock.Lock()
120 defer m.tokenLock.Unlock()
121 t, ok := m.tokens[token]
122 if !ok {
123 return ErrTokenNotFound
124 }
125 t.Revoked = true
126 m.tokens[token] = t
127 return nil
128 }
130 func (m *memstore) revokeTokensByProfileID(profileID uuid.ID) error {
131 ids, err := m.lookupTokensByProfileID(profileID.String())
132 if err != nil {
133 return err
134 }
135 if len(ids) < 1 {
136 return ErrProfileNotFound
137 }
138 m.tokenLock.Lock()
139 defer m.tokenLock.Unlock()
140 for _, id := range ids {
141 token := m.tokens[id]
142 token.Revoked = true
143 m.tokens[id] = token
144 }
145 return nil
146 }
148 func (m *memstore) revokeTokensByClientID(clientID uuid.ID) error {
149 m.tokenLock.Lock()
150 defer m.tokenLock.Unlock()
151 for id, token := range m.tokens {
152 if !token.ClientID.Equal(clientID) {
153 continue
154 }
155 token.Revoked = true
156 m.tokens[id] = token
157 }
158 return nil
159 }
161 func (m *memstore) getTokensByProfileID(profileID uuid.ID, num, offset int) ([]Token, error) {
162 ids, err := m.lookupTokensByProfileID(profileID.String())
163 if err != nil {
164 return []Token{}, err
165 }
166 if len(ids) > num+offset {
167 ids = ids[offset : num+offset]
168 } else if len(ids) > offset {
169 ids = ids[offset:]
170 } else {
171 return []Token{}, nil
172 }
173 tokens := []Token{}
174 for _, id := range ids {
175 token, err := m.getToken(id, false)
176 if err != nil {
177 return []Token{}, err
178 }
179 tokens = append(tokens, token)
180 }
181 return tokens, nil
182 }
184 func refreshTokenValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes scopeTypes.Scopes, profileID uuid.ID, valid bool) {
185 enc := json.NewEncoder(w)
186 refresh := r.PostFormValue("refresh_token")
187 if refresh == "" {
188 w.WriteHeader(http.StatusBadRequest)
189 renderJSONError(enc, "invalid_request")
190 return
191 }
192 token, err := context.GetToken(refresh, true)
193 if err != nil {
194 if err == ErrTokenNotFound {
195 w.WriteHeader(http.StatusBadRequest)
196 renderJSONError(enc, "invalid_grant")
197 return
198 }
199 log.Println("Error exchanging refresh token:", err)
200 w.WriteHeader(http.StatusInternalServerError)
201 renderJSONError(enc, "server_error")
202 return
203 }
204 clientID, _, ok := getClientAuth(w, r, true)
205 if !ok {
206 return
207 }
208 if !token.ClientID.Equal(clientID) {
209 w.WriteHeader(http.StatusBadRequest)
210 renderJSONError(enc, "invalid_grant")
211 return
212 }
213 if token.Revoked {
214 w.WriteHeader(http.StatusBadRequest)
215 renderJSONError(enc, "invalid_grant")
216 return
217 }
218 return token.Scopes, token.ProfileID, true
219 }
221 func refreshTokenInvalidate(r *http.Request, context Context) error {
222 refresh := r.PostFormValue("refresh_token")
223 if refresh == "" {
224 return ErrTokenNotFound
225 }
226 return context.RevokeToken(refresh)
227 }
229 func refreshTokenAuditString(r *http.Request) string {
230 return "refresh_token:" + r.PostFormValue("refresh_token")
231 }