Refactor verifyClient, implement refresh tokens.
Refactor verifyClient into verifyClient and getClientAuth. We moved verifyClient
out of each of the GrantType's validation functions and into the access token
endpoint, where it will be called before the GrantType's validation function.
Yay, less code repetition. And seeing as we always want to verify the client,
that seems like a good way to prevent things like 118a69954621 from happening.
This did, however, force us to add an AllowsPublic property to the GrantType, so
the token endpoint knows whether or not a public Client is valid for any given
GrantType.
We also implemented the refresh token grant type, which required adding ClientID
and RefreshRevoked as properties on the Token type. We need ClientID because we
need to constrain refresh tokens to the client that issued them. We also should
probably keep track of which tokens belong to which clients, just as a general
rule of thumb. RefreshRevoked had to be created, next to Revoked, because the
AccessToken could be revoked and the RefreshToken still valid, or vice versa.
Notably, when you issue a new refresh token, the old one is revoked, but the
access token is still valid. It remains to be seen whether this is a good way to
track things or not. The number of duplicated properties lead me to believe our
type is not a great representation of the underlying concepts.
9 "code.secondbit.org/uuid.hg"
13 RegisterGrantType("authorization_code", GrantType{
14 Validate: authCodeGrantValidate,
15 Invalidate: authCodeGrantInvalidate,
17 ReturnToken: RenderJSONToken,
23 // ErrNoAuthorizationCodeStore is returned when a Context tries to act on a authorizationCodeStore without setting one first.
24 ErrNoAuthorizationCodeStore = errors.New("no authorizationCodeStore was specified for the Context")
25 // ErrAuthorizationCodeNotFound is returned when an AuthorizationCode is requested but not found in the authorizationCodeStore.
26 ErrAuthorizationCodeNotFound = errors.New("authorization code not found in authorizationCodeStore")
27 // ErrAuthorizationCodeAlreadyExists is returned when an AuthorizationCode is added to a authorizationCodeStore, but another AuthorizationCode with the
28 // same Code already exists in the authorizationCodeStore.
29 ErrAuthorizationCodeAlreadyExists = errors.New("authorization code already exists in authorizationCodeStore")
32 // AuthorizationCode represents an authorization grant made by a user to a Client, to
33 // access user data within a defined Scope for a limited amount of time.
34 type AuthorizationCode struct {
46 type authorizationCodeStore interface {
47 getAuthorizationCode(code string) (AuthorizationCode, error)
48 saveAuthorizationCode(authCode AuthorizationCode) error
49 deleteAuthorizationCode(code string) error
50 useAuthorizationCode(code string) error
53 func (m *memstore) getAuthorizationCode(code string) (AuthorizationCode, error) {
54 m.authCodeLock.RLock()
55 defer m.authCodeLock.RUnlock()
56 authCode, ok := m.authCodes[code]
58 return AuthorizationCode{}, ErrAuthorizationCodeNotFound
63 func (m *memstore) saveAuthorizationCode(authCode AuthorizationCode) error {
65 defer m.authCodeLock.Unlock()
66 _, ok := m.authCodes[authCode.Code]
68 return ErrAuthorizationCodeAlreadyExists
70 m.authCodes[authCode.Code] = authCode
74 func (m *memstore) deleteAuthorizationCode(code string) error {
76 defer m.authCodeLock.Unlock()
77 _, ok := m.authCodes[code]
79 return ErrAuthorizationCodeNotFound
81 delete(m.authCodes, code)
85 func (m *memstore) useAuthorizationCode(code string) error {
87 defer m.authCodeLock.Unlock()
88 a, ok := m.authCodes[code]
90 return ErrAuthorizationCodeNotFound
97 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) {
98 enc := json.NewEncoder(w)
99 code := r.PostFormValue("code")
101 w.WriteHeader(http.StatusBadRequest)
102 renderJSONError(enc, "invalid_request")
105 clientID, _, ok := getClientAuth(w, r, true)
109 authCode, err := context.GetAuthorizationCode(code)
111 if err == ErrAuthorizationCodeNotFound {
112 w.WriteHeader(http.StatusBadRequest)
113 renderJSONError(enc, "invalid_grant")
116 w.WriteHeader(http.StatusInternalServerError)
117 renderJSONError(enc, "server_error")
120 redirectURI := r.PostFormValue("redirect_uri")
121 if authCode.RedirectURI != redirectURI {
122 w.WriteHeader(http.StatusBadRequest)
123 renderJSONError(enc, "invalid_grant")
126 if !authCode.ClientID.Equal(clientID) {
127 w.WriteHeader(http.StatusBadRequest)
128 renderJSONError(enc, "invalid_grant")
131 return authCode.Scope, authCode.ProfileID, true
134 func authCodeGrantInvalidate(r *http.Request, context Context) error {
135 code := r.PostFormValue("code")
137 return ErrAuthorizationCodeNotFound
139 return context.UseAuthorizationCode(code)