loginRequest will process a LoginWithPasswordRequest and return the user if the credentials are correct. If 'false' is returned, the authentication failed and the appropriate error will be written to the ResponseWriter. The user struct is always returned, even if authentication failed. This is to s
(ctx context.Context, rw http.ResponseWriter, req codersdk.LoginWithPasswordRequest)
| 562 | // The user struct is always returned, even if authentication failed. This is |
| 563 | // to support knowing what user attempted to login. |
| 564 | func (api *API) loginRequest(ctx context.Context, rw http.ResponseWriter, req codersdk.LoginWithPasswordRequest) (database.User, rbac.Subject, bool) { |
| 565 | logger := api.Logger.Named(userAuthLoggerName) |
| 566 | |
| 567 | //nolint:gocritic // In order to login, we need to get the user first! |
| 568 | user, err := api.Database.GetUserByEmailOrUsername(dbauthz.AsSystemRestricted(ctx), database.GetUserByEmailOrUsernameParams{ |
| 569 | Email: req.Email, |
| 570 | }) |
| 571 | if err != nil && !xerrors.Is(err, sql.ErrNoRows) { |
| 572 | logger.Error(ctx, "unable to fetch user by email", slog.Error(err)) |
| 573 | httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ |
| 574 | Message: "Internal error.", |
| 575 | }) |
| 576 | return user, rbac.Subject{}, false |
| 577 | } |
| 578 | |
| 579 | // If the user doesn't exist, it will be a default struct. |
| 580 | equal, err := userpassword.Compare(string(user.HashedPassword), req.Password) |
| 581 | if err != nil { |
| 582 | logger.Error(ctx, "unable to compare passwords", slog.Error(err)) |
| 583 | httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ |
| 584 | Message: "Internal error.", |
| 585 | }) |
| 586 | return user, rbac.Subject{}, false |
| 587 | } |
| 588 | |
| 589 | if !equal { |
| 590 | // This message is the same as above to remove ease in detecting whether |
| 591 | // users are registered or not. Attackers still could with a timing attack. |
| 592 | httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ |
| 593 | Message: "Incorrect email or password.", |
| 594 | }) |
| 595 | return user, rbac.Subject{}, false |
| 596 | } |
| 597 | |
| 598 | // If password authentication is disabled and the user does not have the |
| 599 | // owner role, block the request. |
| 600 | if api.DeploymentValues.DisablePasswordAuth { |
| 601 | httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ |
| 602 | Message: "Password authentication is disabled.", |
| 603 | }) |
| 604 | return user, rbac.Subject{}, false |
| 605 | } |
| 606 | |
| 607 | if user.LoginType != database.LoginTypePassword { |
| 608 | httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ |
| 609 | Message: fmt.Sprintf("Incorrect login type, attempting to use %q but user is of login type %q", database.LoginTypePassword, user.LoginType), |
| 610 | }) |
| 611 | return user, rbac.Subject{}, false |
| 612 | } |
| 613 | |
| 614 | user, err = ActivateDormantUser(api.Logger, &api.Auditor, api.Database)(ctx, user) |
| 615 | if err != nil { |
| 616 | httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ |
| 617 | Message: "Internal error.", |
| 618 | Detail: err.Error(), |
| 619 | }) |
| 620 | return user, rbac.Subject{}, false |
| 621 | } |
no test coverage detected