const crypto = require('crypto'); const mongoose = require('mongoose'); const sendEmail = require('../_helpers/send-email'); const User = mongoose.model('User'); module.exports = { register, verifyEmail, forgotPassword, validateResetToken, resetPassword, }; async function register(params, origin) { const user = await User.findOne({ email: params.email }); if (user) { // send already registered error in email to prevent account enumeration return await sendAlreadyRegisteredEmail(params.email, origin); } const newUser = new User(); newUser.username = params.username; newUser.email = params.email; newUser.setPassword(params.password); newUser.verificationToken = randomTokenString(); newUser.needsEmailValidation = true; await newUser.save(); // send email await sendVerificationEmail(newUser, origin); } async function verifyEmail({ token }) { const account = await User.findOne({ verificationToken: token }); if (!account) { throw Error('Verification failed'); } account.needsEmailValidation = false; account.verificationToken = undefined; await account.save(); } async function forgotPassword({ email }, origin) { const account = await User.findOne({ email }); console.log('forgotPassword', account, email); // always return ok response to prevent email enumeration if (!account) return; // create reset token that expires after 24 hours account.resetToken = { token: randomTokenString(), expires: new Date(Date.now() + 24 * 60 * 60 * 1000), }; await account.save(); console.log('forgotPassword account saved', account); // send email await sendPasswordResetEmail(account, origin); } async function validateResetToken({ token }) { const account = await User.findOne({ 'resetToken.token': token, 'resetToken.expires': { $gt: Date.now() }, }); if (!account) { throw Error('Invalid token'); } } async function resetPassword({ token, password }) { const account = await User.findOne({ 'resetToken.token': token, 'resetToken.expires': { $gt: Date.now() }, }); if (!account) { throw Error('Invalid token'); } // update password and remove reset token account.setPassword(password); account.resetToken = undefined; await account.save(); } function randomTokenString() { return crypto.randomBytes(40).toString('hex'); } async function sendVerificationEmail(account, origin) { let message; if (origin) { const verifyUrl = `${origin}/account/verify-email?token=${account.verificationToken}`; message = `
Please click the below link to verify your email address:
`; } else { message = `Please use the below token to verify your email address with the /account/verify-email
api route:
${account.verificationToken}
Thanks for registering!
${message}`, }); } async function sendAlreadyRegisteredEmail(email, origin) { let message; if (origin) { message = `If you don't know your password please visit the forgot password page.
`; } else { message = `If you don't know your password you can reset it via the /account/forgot-password
api route.
Your email ${email} is already registered.
${message}`, }); } async function sendPasswordResetEmail(account, origin) { let message; if (origin) { const resetUrl = `${origin}/account/reset-password?token=${account.resetToken.token}`; message = `Please click the below link to reset your password, the link will be valid for 1 day:
`; } else { message = `Please use the below token to reset your password with the /account/reset-password
api route:
${account.resetToken.token}