(ctx context.Context, w http.ResponseWriter, r *http.Request)
| 212 | } |
| 213 | |
| 214 | func (a *API) PKCE(ctx context.Context, w http.ResponseWriter, r *http.Request) error { |
| 215 | db := a.db.WithContext(ctx) |
| 216 | config := a.config |
| 217 | var grantParams models.GrantParams |
| 218 | |
| 219 | // There is a slight problem with this as it will pick-up the |
| 220 | // User-Agent and IP addresses from the server if used on the server |
| 221 | // side. Currently there's no mechanism to distinguish, but the server |
| 222 | // can be told to at least propagate the User-Agent header. |
| 223 | grantParams.FillGrantParams(r) |
| 224 | |
| 225 | params := &PKCEGrantParams{} |
| 226 | if err := retrieveRequestParams(r, params); err != nil { |
| 227 | return err |
| 228 | } |
| 229 | |
| 230 | if params.AuthCode == "" || params.CodeVerifier == "" { |
| 231 | return apierrors.NewBadRequestError(apierrors.ErrorCodeValidationFailed, "invalid request: both auth code and code verifier should be non-empty") |
| 232 | } |
| 233 | |
| 234 | flowState, err := models.FindFlowStateByAuthCode(db, params.AuthCode) |
| 235 | // Sanity check in case user ID was not set properly |
| 236 | if models.IsNotFoundError(err) || flowState.UserID == nil { |
| 237 | return apierrors.NewNotFoundError(apierrors.ErrorCodeFlowStateNotFound, "invalid flow state, no valid flow state found") |
| 238 | } else if err != nil { |
| 239 | return err |
| 240 | } |
| 241 | if flowState.IsExpired(a.config.External.FlowStateExpiryDuration) { |
| 242 | return apierrors.NewUnprocessableEntityError(apierrors.ErrorCodeFlowStateExpired, "invalid flow state, flow state has expired") |
| 243 | } |
| 244 | |
| 245 | user, err := models.FindUserByID(db, *flowState.UserID) |
| 246 | if err != nil { |
| 247 | return err |
| 248 | } |
| 249 | if err := flowState.VerifyPKCE(params.CodeVerifier); err != nil { |
| 250 | return apierrors.NewBadRequestError(apierrors.ErrorCodeBadCodeVerifier, "%s", err.Error()) |
| 251 | } |
| 252 | |
| 253 | var token *AccessTokenResponse |
| 254 | err = db.Transaction(func(tx *storage.Connection) error { |
| 255 | var terr error |
| 256 | authMethod, err := models.ParseAuthenticationMethod(flowState.AuthenticationMethod) |
| 257 | if err != nil { |
| 258 | return err |
| 259 | } |
| 260 | if terr := models.NewAuditLogEntry(config.AuditLog, r, tx, user, models.LoginAction, "", map[string]interface{}{ |
| 261 | "provider_type": flowState.ProviderType, |
| 262 | }); terr != nil { |
| 263 | return terr |
| 264 | } |
| 265 | token, terr = a.tokenService.IssueRefreshToken(r, w.Header(), tx, user, authMethod, grantParams) |
| 266 | if terr != nil { |
| 267 | // error type is already handled in issueRefreshToken |
| 268 | return terr |
| 269 | } |
| 270 | token.ProviderAccessToken = flowState.ProviderAccessToken |
| 271 | // Because not all providers give out a refresh token |
nothing calls this directly
no test coverage detected