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:

${verifyUrl}

`; } else { message = `

Please use the below token to verify your email address with the /account/verify-email api route:

${account.verificationToken}

`; } await sendEmail({ to: account.email, subject: 'Sign-up Verification API - Verify Email', html: `

Verify Email

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.

`; } await sendEmail({ to: email, subject: 'Sign-up Verification API - Email Already Registered', html: `

Email Already Registered

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:

${resetUrl}

`; } else { message = `

Please use the below token to reset your password with the /account/reset-password api route:

${account.resetToken.token}

`; } await sendEmail({ to: account.email, subject: 'Sign-up Verification API - Reset Password', html: `

Reset Password Email

${message}`, }); }