Node.js+MongoDB Atlas邮箱验证问题:登录失败与文档异常
问题解决方案
1. Bcrypt密码比对始终返回false的问题
原因
你的pre("save")钩子会在每次保存用户文档时重新哈希user_password字段——包括验证码验证通过后调用user.save()、更新用户信息时的user.save()。这会导致原本已经哈希过的密码被二次哈希,登录时用原始密码和二次哈希后的密码比对必然失败。
解决方案
修改Schema中的pre("save")钩子,仅当密码字段是新创建或被修改时才执行哈希操作:
user_schema.pre("save", async function (next) { try { // 仅在密码字段被修改(或新创建)时执行哈希 if (!this.isModified("user_password")) { return next(); } const hash = await bcrypt.hash(this.user_password, 15); this.user_password = hash; next(); } catch (error) { next(error); } });
2. 15分钟未验证自动删除、验证后误删的问题
原因
当前Schema中createdAt字段设置了固定的expires: "60m"TTL索引,无论用户是否验证通过,文档都会在创建后60分钟被删除。验证时更新createdAt只是延长了过期时间,无法彻底取消过期规则。
解决方案
改用条件TTL索引,仅对未验证用户(isEmailValidated: false)应用15分钟过期规则,验证通过后自动终止删除逻辑:
- 修改Schema,移除
createdAt的直接expires配置,添加条件索引:
const user_schema = new Schema({ // ... 其他字段保持不变 isEmailValidated: { type: Boolean, default: false, }, createdAt: { type: Date, default: Date.now, }, // ... 其他字段保持不变 }); // 添加条件TTL索引:未验证用户15分钟后自动删除 user_schema.index( { createdAt: 1 }, { expireAfterSeconds: 15 * 60, partialFilterExpression: { isEmailValidated: false } } );
- 调整验证码验证逻辑,无需更新
createdAt,只需标记验证通过即可:
async (req,res)=>{ const {email, verificationCode} = req.body console.log(email, verificationCode) try { const user = await Model.findOne({ user_email: email, validationCode: verificationCode }); if (user) { // 标记验证通过,条件TTL索引会自动停止对该文档的删除 user.isEmailValidated = true; // 可选:验证通过后清除验证码,避免重复使用 user.validationCode = undefined; await user.save(); console.log('Email validation successful.'); return res.status(201).json({ message: `The Validation is done Successfully `, }); } else { console.error('Email validation failed: Invalid code or email.'); return res.status(400).json({message: 'Invalid verification code or email'}); } } catch (error) { console.error('Error verifying email:', error); return res.status(500).json({message: 'Verification failed', error: error.message}); } },
额外优化建议
- 登录时确保使用正确的bcrypt比对逻辑:
// 登录示例代码 async function login(req, res) { const { user_email, user_password } = req.body; try { const user = await Model.findOne({ user_email }); if (!user || !user.isEmailValidated) { return res.status(401).json({message: 'User not found or not verified'}); } const isMatch = await bcrypt.compare(user_password, user.user_password); if (isMatch) { // 登录成功逻辑 return res.status(200).json({message: 'Login successful'}); } else { return res.status(401).json({message: 'Invalid password'}); } } catch (error) { res.status(500).json({message: 'Login failed', error: error.message}); } }
- 可给验证码单独添加过期时间,避免用户使用过期验证码。
内容的提问来源于stack exchange,提问作者Hani Danial
相关产品推荐
相关产品推荐

