RequestPasswordReset sends a password reset email. Always returns success to avoid leaking email existence.
(ctx context.Context, req *connect.Request[v1pb.RequestPasswordResetRequest])
| 1498 | |
| 1499 | // RequestPasswordReset sends a password reset email. Always returns success to avoid leaking email existence. |
| 1500 | func (s *AuthService) RequestPasswordReset(ctx context.Context, req *connect.Request[v1pb.RequestPasswordResetRequest]) (*connect.Response[emptypb.Empty], error) { |
| 1501 | email := strings.ToLower(strings.TrimSpace(req.Msg.Email)) |
| 1502 | if email == "" { |
| 1503 | return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("email is required")) |
| 1504 | } |
| 1505 | |
| 1506 | // Send synchronously, but swallow errors to avoid email enumeration — a fast silent |
| 1507 | // "success" for unknown emails must be indistinguishable from an SMTP failure for |
| 1508 | // known ones. Errors are logged server-side for operator visibility. |
| 1509 | if err := s.sendEmailVerificationCode( |
| 1510 | ctx, |
| 1511 | req.Msg.Workspace, |
| 1512 | email, |
| 1513 | storepb.EmailVerificationCodePurpose_PASSWORD_RESET, |
| 1514 | "[Bytebase] Reset your password", |
| 1515 | "Hi,\n\nYour password reset code is: %s\n\nThis code expires in %d minutes. If you didn't request this, you can safely ignore this email.\n\n— Bytebase", |
| 1516 | ); err != nil { |
| 1517 | slog.Warn("failed to send password reset email", slog.String("to", email), log.BBError(err)) |
| 1518 | } |
| 1519 | |
| 1520 | return connect.NewResponse(&emptypb.Empty{}), nil |
| 1521 | } |
| 1522 | |
| 1523 | // ResetPassword verifies the 6-digit code and updates the user's password. |
| 1524 | // Also revokes all refresh tokens to force re-login. |
nothing calls this directly
no test coverage detected