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