auth
auth/authcode.go
Use an environment variable to set the JWT secret. When setting up the authd server, populate the JWT secret using a JWT_SECRET environment variable. Incidentally, we also included the subscriptions scope, for testing purposes while creating code.secondbit.org/ducky/subscriptions. We now also log the port we're listening on, listen on all interfaces (instead of just 127.0.0.1), and changed the port to 9000 instead of 8080.
| 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@124 | 19 AuditString: authCodeGrantAuditString, |
| paddy@84 | 20 }) |
| paddy@84 | 21 } |
| paddy@84 | 22 |
| paddy@29 | 23 var ( |
| paddy@87 | 24 // ErrNoAuthorizationCodeStore is returned when a Context tries to act on a authorizationCodeStore without setting one first. |
| paddy@87 | 25 ErrNoAuthorizationCodeStore = errors.New("no authorizationCodeStore was specified for the Context") |
| paddy@87 | 26 // ErrAuthorizationCodeNotFound is returned when an AuthorizationCode is requested but not found in the authorizationCodeStore. |
| paddy@87 | 27 ErrAuthorizationCodeNotFound = errors.New("authorization code not found in authorizationCodeStore") |
| paddy@87 | 28 // ErrAuthorizationCodeAlreadyExists is returned when an AuthorizationCode is added to a authorizationCodeStore, but another AuthorizationCode with the |
| paddy@87 | 29 // same Code already exists in the authorizationCodeStore. |
| paddy@87 | 30 ErrAuthorizationCodeAlreadyExists = errors.New("authorization code already exists in authorizationCodeStore") |
| paddy@29 | 31 ) |
| paddy@29 | 32 |
| paddy@87 | 33 // AuthorizationCode represents an authorization grant made by a user to a Client, to |
| paddy@57 | 34 // access user data within a defined Scope for a limited amount of time. |
| paddy@87 | 35 type AuthorizationCode struct { |
| paddy@26 | 36 Code string |
| paddy@26 | 37 Created time.Time |
| paddy@26 | 38 ExpiresIn int32 |
| paddy@26 | 39 ClientID uuid.ID |
| paddy@163 | 40 Scopes Scopes |
| paddy@26 | 41 RedirectURI string |
| paddy@26 | 42 State string |
| paddy@69 | 43 ProfileID uuid.ID |
| paddy@94 | 44 Used bool |
| paddy@26 | 45 } |
| paddy@26 | 46 |
| paddy@87 | 47 type authorizationCodeStore interface { |
| paddy@87 | 48 getAuthorizationCode(code string) (AuthorizationCode, error) |
| paddy@87 | 49 saveAuthorizationCode(authCode AuthorizationCode) error |
| paddy@87 | 50 deleteAuthorizationCode(code string) error |
| paddy@163 | 51 deleteAuthorizationCodesByProfileID(profileID uuid.ID) error |
| paddy@164 | 52 deleteAuthorizationCodesByClientID(clientID uuid.ID) error |
| paddy@94 | 53 useAuthorizationCode(code string) error |
| paddy@26 | 54 } |
| paddy@29 | 55 |
| paddy@87 | 56 func (m *memstore) getAuthorizationCode(code string) (AuthorizationCode, error) { |
| paddy@87 | 57 m.authCodeLock.RLock() |
| paddy@87 | 58 defer m.authCodeLock.RUnlock() |
| paddy@87 | 59 authCode, ok := m.authCodes[code] |
| paddy@29 | 60 if !ok { |
| paddy@87 | 61 return AuthorizationCode{}, ErrAuthorizationCodeNotFound |
| paddy@29 | 62 } |
| paddy@87 | 63 return authCode, nil |
| paddy@29 | 64 } |
| paddy@29 | 65 |
| paddy@87 | 66 func (m *memstore) saveAuthorizationCode(authCode AuthorizationCode) error { |
| paddy@87 | 67 m.authCodeLock.Lock() |
| paddy@87 | 68 defer m.authCodeLock.Unlock() |
| paddy@87 | 69 _, ok := m.authCodes[authCode.Code] |
| paddy@29 | 70 if ok { |
| paddy@87 | 71 return ErrAuthorizationCodeAlreadyExists |
| paddy@29 | 72 } |
| paddy@87 | 73 m.authCodes[authCode.Code] = authCode |
| paddy@29 | 74 return nil |
| paddy@29 | 75 } |
| paddy@29 | 76 |
| paddy@87 | 77 func (m *memstore) deleteAuthorizationCode(code string) error { |
| paddy@87 | 78 m.authCodeLock.Lock() |
| paddy@87 | 79 defer m.authCodeLock.Unlock() |
| paddy@87 | 80 _, ok := m.authCodes[code] |
| paddy@29 | 81 if !ok { |
| paddy@87 | 82 return ErrAuthorizationCodeNotFound |
| paddy@29 | 83 } |
| paddy@87 | 84 delete(m.authCodes, code) |
| paddy@29 | 85 return nil |
| paddy@29 | 86 } |
| paddy@84 | 87 |
| paddy@163 | 88 func (m *memstore) deleteAuthorizationCodesByProfileID(profileID uuid.ID) error { |
| paddy@163 | 89 m.authCodeLock.Lock() |
| paddy@163 | 90 defer m.authCodeLock.Unlock() |
| paddy@163 | 91 var codes []string |
| paddy@163 | 92 for _, code := range m.authCodes { |
| paddy@163 | 93 if code.ProfileID.Equal(profileID) { |
| paddy@163 | 94 codes = append(codes, code.Code) |
| paddy@163 | 95 } |
| paddy@163 | 96 } |
| paddy@163 | 97 if len(codes) < 1 { |
| paddy@163 | 98 return ErrProfileNotFound |
| paddy@163 | 99 } |
| paddy@163 | 100 for _, code := range codes { |
| paddy@163 | 101 delete(m.authCodes, code) |
| paddy@163 | 102 } |
| paddy@163 | 103 return nil |
| paddy@163 | 104 } |
| paddy@163 | 105 |
| paddy@164 | 106 func (m *memstore) deleteAuthorizationCodesByClientID(clientID uuid.ID) error { |
| paddy@164 | 107 m.authCodeLock.Lock() |
| paddy@164 | 108 defer m.authCodeLock.Unlock() |
| paddy@164 | 109 var codes []string |
| paddy@164 | 110 for _, code := range m.authCodes { |
| paddy@164 | 111 if code.ClientID.Equal(clientID) { |
| paddy@164 | 112 codes = append(codes, code.Code) |
| paddy@164 | 113 } |
| paddy@164 | 114 } |
| paddy@164 | 115 if len(codes) < 1 { |
| paddy@164 | 116 return ErrClientNotFound |
| paddy@164 | 117 } |
| paddy@164 | 118 for _, code := range codes { |
| paddy@164 | 119 delete(m.authCodes, code) |
| paddy@164 | 120 } |
| paddy@164 | 121 return nil |
| paddy@164 | 122 } |
| paddy@164 | 123 |
| paddy@94 | 124 func (m *memstore) useAuthorizationCode(code string) error { |
| paddy@94 | 125 m.authCodeLock.Lock() |
| paddy@94 | 126 defer m.authCodeLock.Unlock() |
| paddy@94 | 127 a, ok := m.authCodes[code] |
| paddy@94 | 128 if !ok { |
| paddy@94 | 129 return ErrAuthorizationCodeNotFound |
| paddy@94 | 130 } |
| paddy@94 | 131 a.Used = true |
| paddy@94 | 132 m.authCodes[code] = a |
| paddy@94 | 133 return nil |
| paddy@94 | 134 } |
| paddy@94 | 135 |
| paddy@163 | 136 func authCodeGrantValidate(w http.ResponseWriter, r *http.Request, context Context) (scopes Scopes, profileID uuid.ID, valid bool) { |
| paddy@84 | 137 enc := json.NewEncoder(w) |
| paddy@84 | 138 code := r.PostFormValue("code") |
| paddy@84 | 139 if code == "" { |
| paddy@84 | 140 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 141 renderJSONError(enc, "invalid_request") |
| paddy@84 | 142 return |
| paddy@84 | 143 } |
| paddy@123 | 144 clientID, _, ok := getClientAuth(w, r, true) |
| paddy@123 | 145 if !ok { |
| paddy@84 | 146 return |
| paddy@84 | 147 } |
| paddy@87 | 148 authCode, err := context.GetAuthorizationCode(code) |
| paddy@84 | 149 if err != nil { |
| paddy@87 | 150 if err == ErrAuthorizationCodeNotFound { |
| paddy@84 | 151 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 152 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 153 return |
| paddy@84 | 154 } |
| paddy@84 | 155 w.WriteHeader(http.StatusInternalServerError) |
| paddy@84 | 156 renderJSONError(enc, "server_error") |
| paddy@84 | 157 return |
| paddy@84 | 158 } |
| paddy@85 | 159 redirectURI := r.PostFormValue("redirect_uri") |
| paddy@87 | 160 if authCode.RedirectURI != redirectURI { |
| paddy@84 | 161 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 162 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 163 return |
| paddy@84 | 164 } |
| paddy@87 | 165 if !authCode.ClientID.Equal(clientID) { |
| paddy@84 | 166 w.WriteHeader(http.StatusBadRequest) |
| paddy@84 | 167 renderJSONError(enc, "invalid_grant") |
| paddy@84 | 168 return |
| paddy@84 | 169 } |
| paddy@135 | 170 return authCode.Scopes, authCode.ProfileID, true |
| paddy@84 | 171 } |
| paddy@90 | 172 |
| paddy@90 | 173 func authCodeGrantInvalidate(r *http.Request, context Context) error { |
| paddy@94 | 174 code := r.PostFormValue("code") |
| paddy@94 | 175 if code == "" { |
| paddy@94 | 176 return ErrAuthorizationCodeNotFound |
| paddy@94 | 177 } |
| paddy@94 | 178 return context.UseAuthorizationCode(code) |
| paddy@90 | 179 } |
| paddy@124 | 180 |
| paddy@124 | 181 func authCodeGrantAuditString(r *http.Request) string { |
| paddy@124 | 182 return "authcode:" + r.PostFormValue("code") |
| paddy@124 | 183 } |