You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

密码重置后新密码无法登录:技术问题排查求助

问题描述

我正在实现用户忘记密码的重置功能,流程如下:

  • 向/user/forget-password发送账户邮箱,获取6位验证码
  • 向/user/reset-password/:userId发送重置验证码+新密码,收到成功提示:"Password updated successfully"
  • 使用邮箱和新密码登录

执行到第3步时,出现错误提示:"message":"Invalid password."

MongoDB集合中可见哈希密码已变更,但新密码仍无法登录。

相关代码

路由定义

router.post("/login", authController.signIn);

router.post("/forget-password", authController.forgetPassword);
router.post("/reset-password/:userId", authController.resetPasword);

控制器代码

module.exports.signIn = async (req, res) => {
  const { email, password } = req.body;
  const user = await UserModel.findOne({ email });

  if (!user) {
    return res.status(400).json({
      message: "User not found.",
    });
  }


  const isValidPassword = await bcrypt.compare(password, user.password);

  if (!isValidPassword) {
    return res.status(400).json({
      message: "Invalid password.",
      error: res.error
    });
  }

  const token = jwt.sign({ id: user._id }, process.env.SECRET_KEY, {
    expiresIn: "2h",
  });

  res.status(200).json({
    message: "User logged in.",
    token,
    user: {
      id: user._id,
      email: user.email,
      firstName: user.firstName,
      lastName: user.lastName,
    },
  });
};

module.exports.forgetPassword = async (req, res) => {
  const { email } = req.body;
  const resetCode = Math.floor(100000 + Math.random() * 900000).toString();

  try {
    const user = await UserModel.findOneAndUpdate({ email }, { resetCode });

    if (!user) return res.status(404).json({ message: "User not found" });

    res.json({ message: "Reset code generated", userId: user._id });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Server error" });
  }
};

module.exports.resetPasword = async (req, res) => {
  const { userId } = req.params;
  const { resetCode, newPassword } = req.body;

  try {
    const user = await UserModel.findById(userId);

    if (!user) return res.status(404).json({ message: "User not found" });

    if (user.resetCode !== resetCode) return res.status(400).json({ message: "Invalid reset code" });

    const salt = await bcrypt.genSalt(10);
    const newHashedPassword = await bcrypt.hash(newPassword, salt);

    user.password = newHashedPassword;
    user.resetCode = null;

    await user.save();

    res.json({ message: "Password updated successfully" });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Server error" });
  }
};

UserSchema定义

const UserSchema = new mongoose.Schema({
  email: {
    type: String,
    required: [true, "Provide an email."],
    unique: true,
    match: [
      /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/,
      "Please, provide a valid email.",
    ],
  },
  password: {
    type: String,
    required: [true, "Password is required."],
  },
  firstname: {
    type: String,
    required: [true, "Firstname is required."],
  },
  lastname: {
    type: String,
    required: [true, "Lastname is required."],
  },
  resetCode: {
    type: String,
    default: null,
  }
});

UserSchema.pre("save", async function (next) {
  const user = this;
  const salt = await bcrypt.genSalt(10);
  const hash = await bcrypt.hash(user.password, salt);
  user.password = hash;
  next();
});

UserSchema.methods.isValidPassword = async function (password) {
  const user = this;
  const compare = await bcrypt.compare(password, user.password);

  return compare;
};

module.exports = mongoose.model("User", UserSchema);
问题原因与解决方案

核心问题

pre("save")钩子会在每次调用user.save()时重新哈希已经哈希过的密码。重置密码流程中:

  1. 你手动将newPassword哈希为newHashedPassword并赋值给user.password
  2. 调用user.save()时,pre("save")钩子再次对这个已哈希的字符串进行二次哈希
  3. 登录时用原始新密码和二次哈希后的密码比对,自然不匹配

解决方案

修改pre("save")钩子,仅当密码字段被修改时才执行哈希操作,避免重复哈希:

UserSchema.pre("save", async function (next) {
  const user = this;
  // 只有password字段被修改时才重新哈希
  if (!user.isModified("password")) return next();
  
  const salt = await bcrypt.genSalt(10);
  const hash = await bcrypt.hash(user.password, salt);
  user.password = hash;
  next();
});

额外优化建议

  1. 重置密码时可直接使用findByIdAndUpdate,减少数据库操作次数(可选):
module.exports.resetPasword = async (req, res) => {
  const { userId } = req.params;
  const { resetCode, newPassword } = req.body;

  try {
    const salt = await bcrypt.genSalt(10);
    const newHashedPassword = await bcrypt.hash(newPassword, salt);

    const user = await UserModel.findOneAndUpdate(
      { _id: userId, resetCode },
      { password: newHashedPassword, resetCode: null },
      { new: true }
    );

    if (!user) return res.status(400).json({ message: "Invalid reset code or user not found" });

    res.json({ message: "Password updated successfully" });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Server error" });
  }
};
  1. 登录时使用模型定义的isValidPassword方法,保持代码一致性:
// 在signIn控制器中替换原密码验证逻辑
const isValidPassword = await user.isValidPassword(password);

内容的提问来源于stack exchange,提问作者Johan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 15:27:49