重置密码功能$and查询异常:错误token仍可更新密码
解决重置密码时令牌错误仍能更新密码的问题
我看到你遇到的问题了:即使提供错误的重置令牌,用户密码还是会被更新,这是因为你的代码没有检查updateOne操作是否真的匹配到了目标文档。updateOne方法即使没有找到符合条件的文档,也会resolve Promise,只是返回结果里的matchedCount(或旧版本的n)会是0。另外,你使用bcrypt的回调嵌套方式不仅可读性差,还存在错误处理的漏洞(比如genSalt的错误没有被正确捕获)。
问题核心
你的代码里,执行users.updateOne(...)后直接返回成功响应,但没有验证是否真的有文档被修改。当令牌错误时,criteria无法匹配到任何用户,updateOne不会修改任何数据,但你的代码依然返回“密码重置成功”的提示。
修复方案
我们需要从三个方面调整代码:
- 在
updateOne的回调中检查是否有文档被匹配并修改 - 改用bcrypt的Promise版本替代嵌套回调,提升代码可读性和错误处理能力
- 完善错误分支,区分“令牌无效”和“服务器错误”两种场景
修复后的代码(推荐使用async/await)
// 确保这个逻辑包裹在async函数中(比如你的路由处理函数) try { // 1. 验证用户输入(保留你已有的验证逻辑) // 2. 生成密码哈希 const salt = await bcrypt.genSalt(saltRounds); const hashedPassword = await bcrypt.hash(form.newPassword, salt); // 3. 构建查询条件并执行更新 const criteria = { _id: oid, reset_token: query.token }; const updateResult = await users.updateOne( criteria, { $set: { password: hashedPassword }, $unset: { reset_token: "" } } ); // 4. 检查是否匹配到有效文档 if (updateResult.matchedCount === 0) { return res.status(400).json('Invalid reset token or user ID.'); } // 5. 返回成功响应 return res.status(201).json('Your password has been reset successfully!'); } catch (error) { console.error(error); // 记录错误日志便于排查问题 return res.status(500).json('Sorry something is wrong. Please try again later.'); }
适配原回调风格的修复版本(不推荐,但兼容你的原有代码结构)
// setup find criteria const criteria = { $and: [ {_id: oid}, {reset_token: query.token} ] }; // hashing new password bcrypt.genSalt(saltRounds, (saltError, salt) => { if (saltError) { console.error(saltError); return res.status(500).json('Sorry something is wrong. Please try again later.'); } bcrypt.hash(form.newPassword, salt, (hashError, hash) => { if (hashError) { console.error(hashError); return res.status(500).json('Sorry something is wrong. Please try again later.'); } // update new password into DB and delete reset token users.updateOne(criteria, {$set:{password: hash}, $unset:{reset_token:""}} ) .then(response => { // 关键:检查是否匹配到目标文档 if (response.matchedCount === 0) { return res.status(400).json('Invalid reset token or user ID.'); } return res.status(201).json('Your password has been reset successfully!'); }) .catch(dbError => { console.error(dbError); res.status(500).json('Sorry something is wrong. Please try again later.'); }); }); });
关键改进点
- 验证更新有效性:通过
updateResult.matchedCount判断查询条件是否匹配到用户,以此验证令牌和用户ID的合法性 - 完善错误捕获:分别处理bcrypt生成盐、哈希密码以及数据库操作的错误,避免错误被遗漏
- 代码可读性优化:用async/await替代嵌套回调,让逻辑流程更清晰直观
这样修改后,当用户提供错误的令牌时,updateOne会返回matchedCount: 0,此时我们会返回400错误提示,不会执行密码更新操作。
内容的提问来源于stack exchange,提问作者Irfan Gumelar




