I’m currently building a password reset flow and wanted to check this made sense.
So the user would make a request passing their email which would generate a token, this would be sent to the user and stored hashed. I have not salted it as I would imagine a 32 byte random string with a 30 min life would be borderline impossible and not a viable attack vector. (Live read access would also be required).
const error = validate(email, isEmail);
const resetToken = require('crypto').randomBytes(32).toString('hex');
// TODO send token link via email
const hashedToken = crypto
.pbkdf2Sync(resetToken, '', 10000, 64, 'sha512')
.toString('hex');
const thirtyMins = dayjs().add(30, 'minute').toDate();
return (
error ??
db('user').where('email', email).update({
reset_token: hashedToken,
reset_token_expiry: thirtyMins,
})
);
They would then visit a page to enter the email again, and a password (with confirm), this form would also have the token from a query param inserted as a hidden field. This would call and endpoint to reset which would run this code.
const user = await User.getByEmail(req.body.email).catch((e) => next(e));
if (!user) {
return res.redirect(
'/update-password?error=Password could not be updated'
);
}
const hashedToken = crypto
.pbkdf2Sync(req.body.token, '', 10000, 64, 'sha512')
.toString('hex');
if (hashedToken !== user['reset_token']) {
return res.redirect('/update-password?error=Invalid token');
}
if (dayjs().isAfter(user['reset_token_expiry'])) {
return res.redirect('/update-password?error=Token expired');
}
const { salt, hash } = genPassword(req.body.password);
User.updatePassword({
hash: hash,
salt: salt,
resetToken: req.body.token,
email: req.body.email,
})
Thanks in advance for any help