auth
auth/authcode.go
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.
| paddy@26 | 1 package auth |
| paddy@26 | 2 |
| paddy@26 | 3 import ( |
| paddy@84 | 4 "encoding/json" |
| paddy@29 | 5 "errors" |
| paddy@84 | 6 "net/http" |
| paddy@26 | 7 "time" |
| paddy@26 | 8 |
| paddy@107 | 9 "code.secondbit.org/uuid.hg" |
| paddy@26 | 10 ) |
| paddy@26 | 11 |
| paddy@84 | 12 func init() { |
| paddy@84 | 13 RegisterGrantType("authorization_code", GrantType{ |
| paddy@84 | 14 Validate: authCodeGrantValidate, |
| paddy@94 | 15 Invalidate: authCodeGrantInvalidate, |
| paddy@84 | 16 IssuesRefresh: true, |
| paddy@85 | 17 ReturnToken: RenderJSONToken, |
| paddy@123 | 18 AllowsPublic: true, |
| paddy@84 | 19 }) |
| paddy@84 | 20 } |
| paddy@84 | 21 |
| paddy@29 | 22 var ( |
| paddy@87 | 23 // ErrNoAuthorizationCodeStore is returned when a Context tries to act on a authorizationCodeStore without setting one first. |
| paddy@87 | 24 ErrNoAuthorizationCodeStore = errors.New("no authorizationCodeStore was specified for the Context") |
| paddy@87 | 25 // ErrAuthorizationCodeNotFound is returned when an AuthorizationCode is requested but not found in the authorizationCodeStore. |
| paddy@87 | 26 ErrAuthorizationCodeNotFound = errors.New("authorization code not found in authorizationCodeStore") |
| paddy@87 | 27 // ErrAuthorizationCodeAlreadyExists is returned when an AuthorizationCode is added to a authorizationCodeStore, but another AuthorizationCode with the |
| paddy@87 | 28 // same Code already exists in the authorizationCodeStore. |
| paddy@87 | 29 ErrAuthorizationCodeAlreadyExists = errors.New("authorization code already exists in authorizationCodeStore") |
| paddy@29 | 30 ) |
| paddy@29 | 31 |
| paddy@87 | 32 // AuthorizationCode represents an authorization grant made by a user to a Client, to |
| paddy@57 | 33 // access user data within a defined Scope for a limited amount of time. |
| paddy@87 | 34 type AuthorizationCode struct { |
| paddy@26 | 35 Code string |
| paddy@26 | 36 Created time.Time |
| paddy@26 | 37 ExpiresIn int32 |
| paddy@26 | 38 ClientID uuid.ID |
| paddy@26 | 39 Scope string |
| paddy@26 | 40 RedirectURI string |
| paddy@26 | 41 State string |
| paddy@69 | 42 ProfileID uuid.ID |
| paddy@94 | 43 Used bool |
| paddy@26 | 44 } |
| paddy@26 | 45 |
| paddy@87 | 46 type authorizationCodeStore interface { |
| paddy@87 | 47 getAuthorizationCode(code string) (AuthorizationCode, error) |
| paddy@87 | 48 saveAuthorizationCode(authCode AuthorizationCode) error |
| paddy@87 | 49 deleteAuthorizationCode(code string) error |
| paddy@94 | 50 useAuthorizationCode(code string) error |
| paddy@26 | 51 } |
| paddy@29 | 52 |
| paddy@87 | 53 func (m *memstore) getAuthorizationCode(code string) (AuthorizationCode, error) { |
| paddy@87 | 54 m.authCodeLock.RLock() |
| paddy@87 | 55 defer m.authCodeLock.RUnlock() |
| paddy@87 | 56 authCode, ok := m.authCodes[code] |
| paddy@29 | 57 if !ok { |
| paddy@87 | 58 return AuthorizationCode{}, ErrAuthorizationCodeNotFound |
| paddy@29 | 59 } |
| paddy@87 | 60 return authCode, nil |
| paddy@29 | 61 } |
| paddy@29 | 62 |
| paddy@87 | 63 func (m *memstore) saveAuthorizationCode(authCode AuthorizationCode) error { |
| paddy@87 | 64 m.authCodeLock.Lock() |
| paddy@87 | 65 defer m.authCodeLock.Unlock() |
| paddy@87 | 66 _, ok := m.authCodes[authCode.Code] |
| paddy@29 | 67 if ok { |
| paddy@87 | 68 return ErrAuthorizationCodeAlreadyExists |
| paddy@29 | 69 } |
| paddy@87 | 70 m.authCodes[authCode.Code] = authCode |
| paddy@29 | 71 return nil |
| paddy@29 | 72 } |
| paddy@29 | 73 |
| paddy@87 | 74 func (m *memstore) deleteAuthorizationCode(code string) error { |
| paddy@87 | 75 m.authCodeLock.Lock() |
| paddy@87 | 76 defer m.authCodeLock.Unlock() |
| paddy@87 | 77 _, ok := m.authCodes[code] |
| paddy@29 | 78 if !ok { |
| paddy@87 | 79 return ErrAuthorizationCodeNotFound |
| paddy@29 | 80 } |
| paddy@87 | 81 delete(m.authCodes, code) |
| paddy@29 | 82 return nil |
| paddy@29 | 83 } |
| paddy@84 | 84 |
| paddy@94 | 85 func (m *memstore) useAuthorizationCode(code string) error { |
| paddy@94 | 86 m.authCodeLock.Lock() |
| paddy@94 | 87 defer m.authCodeLock.Unlock() |
| paddy@94 | 88 a, ok := m.authCodes[code] |
| paddy@94 | 89 if !ok { |
| paddy@94 | 90 return ErrAuthorizationCodeNotFound |
| paddy@94 | 91 } |
| paddy@94 | 92 a.Used = true |
| paddy@94 | 93 m.authCodes[code] = a |
| paddy@94 | 94 return nil |
| paddy@94 | 95 } |
| paddy@94 | 96 |
| paddy@84 | 97 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scope string, profileID uuid.ID, valid bool) { |
| paddy@84 | 98 enc := json.NewEncoder(w) |
| paddy@84 | 99 code := r.PostFormValue("code") |
| paddy@84 | 100 if code == "" { |
| paddy@84 | 101 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 102 renderJSONError(enc, "invalid_request") |
| paddy@84 | 103 return |
| paddy@84 | 104 } |
| paddy@123 | 105 clientID, _, ok := getClientAuth(w, r, true) |
| paddy@123 | 106 if !ok { |
| paddy@84 | 107 return |
| paddy@84 | 108 } |
| paddy@87 | 109 authCode, err := context.GetAuthorizationCode(code) |
| paddy@84 | 110 if err != nil { |
| paddy@87 | 111 if err == ErrAuthorizationCodeNotFound { |
| paddy@84 | 112 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 113 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 114 return |
| paddy@84 | 115 } |
| paddy@84 | 116 w.WriteHeader(http.StatusInternalServerError) |
| paddy@84 | 117 renderJSONError(enc, "server_error") |
| paddy@84 | 118 return |
| paddy@84 | 119 } |
| paddy@85 | 120 redirectURI := r.PostFormValue("redirect_uri") |
| paddy@87 | 121 if authCode.RedirectURI != redirectURI { |
| paddy@84 | 122 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 123 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 124 return |
| paddy@84 | 125 } |
| paddy@87 | 126 if !authCode.ClientID.Equal(clientID) { |
| paddy@84 | 127 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 128 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 129 return |
| paddy@84 | 130 } |
| paddy@87 | 131 return authCode.Scope, authCode.ProfileID, true |
| paddy@84 | 132 } |
| paddy@90 | 133 |
| paddy@90 | 134 func authCodeGrantInvalidate(r *http.Request, context Context) error { |
| paddy@94 | 135 code := r.PostFormValue("code") |
| paddy@94 | 136 if code == "" { |
| paddy@94 | 137 return ErrAuthorizationCodeNotFound |
| paddy@94 | 138 } |
| paddy@94 | 139 return context.UseAuthorizationCode(code) |
| paddy@90 | 140 } |