Change a users password with a one-time passcode. @Summary Change password with a one-time passcode @ID change-password-with-a-one-time-passcode @Accept json @Tags Authorization @Param request body codersdk.ChangePasswordWithOneTimePasscodeRequest true "Change password request" @Success 204 @Router
(rw http.ResponseWriter, r *http.Request)
| 334 | // @Success 204 |
| 335 | // @Router /api/v2/users/otp/change-password [post] |
| 336 | func (api *API) postChangePasswordWithOneTimePasscode(rw http.ResponseWriter, r *http.Request) { |
| 337 | var ( |
| 338 | err error |
| 339 | ctx = r.Context() |
| 340 | auditor = api.Auditor.Load() |
| 341 | logger = api.Logger.Named(userAuthLoggerName) |
| 342 | aReq, commitAudit = audit.InitRequest[database.User](rw, &audit.RequestParams{ |
| 343 | Audit: *auditor, |
| 344 | Log: api.Logger, |
| 345 | Request: r, |
| 346 | Action: database.AuditActionWrite, |
| 347 | }) |
| 348 | ) |
| 349 | defer commitAudit() |
| 350 | |
| 351 | if api.DeploymentValues.DisablePasswordAuth { |
| 352 | httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ |
| 353 | Message: "Password authentication is disabled.", |
| 354 | }) |
| 355 | return |
| 356 | } |
| 357 | |
| 358 | var req codersdk.ChangePasswordWithOneTimePasscodeRequest |
| 359 | if !httpapi.Read(ctx, rw, r, &req) { |
| 360 | return |
| 361 | } |
| 362 | |
| 363 | if err := userpassword.Validate(req.Password); err != nil { |
| 364 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 365 | Message: "Invalid password.", |
| 366 | Validations: []codersdk.ValidationError{ |
| 367 | { |
| 368 | Field: "password", |
| 369 | Detail: err.Error(), |
| 370 | }, |
| 371 | }, |
| 372 | }) |
| 373 | return |
| 374 | } |
| 375 | |
| 376 | err = api.Database.InTx(func(tx database.Store) error { |
| 377 | //nolint:gocritic // In order to change a user's password, we need to get the user first - and can only do that in the system auth context. |
| 378 | user, err := tx.GetUserByEmailOrUsername(dbauthz.AsSystemRestricted(ctx), database.GetUserByEmailOrUsernameParams{ |
| 379 | Email: req.Email, |
| 380 | }) |
| 381 | if err != nil && !errors.Is(err, sql.ErrNoRows) { |
| 382 | logger.Error(ctx, "unable to fetch user by email", slog.F("email", req.Email), slog.Error(err)) |
| 383 | return xerrors.Errorf("get user by email: %w", err) |
| 384 | } |
| 385 | // We continue if err == sql.ErrNoRows to help prevent a timing-based attack. |
| 386 | aReq.Old = user |
| 387 | aReq.UserID = user.ID |
| 388 | |
| 389 | equal, err := userpassword.Compare(string(user.HashedOneTimePasscode), req.OneTimePasscode) |
| 390 | if err != nil { |
| 391 | logger.Error(ctx, "unable to compare one-time passcode", slog.Error(err)) |
| 392 | return xerrors.Errorf("compare one-time passcode: %w", err) |
| 393 | } |
nothing calls this directly
no test coverage detected