From 3edabe2809df27932171808213a91455d2e27fef Mon Sep 17 00:00:00 2001 From: David Rotermund Date: Tue, 16 Jul 2024 18:25:03 +0200 Subject: [PATCH] Create UserRegistrationHandler.js --- overleafserver/UserRegistrationHandler.js | 136 ++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 overleafserver/UserRegistrationHandler.js diff --git a/overleafserver/UserRegistrationHandler.js b/overleafserver/UserRegistrationHandler.js new file mode 100644 index 0000000..c45c19b --- /dev/null +++ b/overleafserver/UserRegistrationHandler.js @@ -0,0 +1,136 @@ +const { User } = require('../../models/User') +const UserCreator = require('./UserCreator') +const UserGetter = require('./UserGetter') +const AuthenticationManager = require('../Authentication/AuthenticationManager') +const NewsletterManager = require('../Newsletter/NewsletterManager') +const logger = require('@overleaf/logger') +const crypto = require('crypto') +const EmailHandler = require('../Email/EmailHandler') +const OneTimeTokenHandler = require('../Security/OneTimeTokenHandler') +const settings = require('@overleaf/settings') +const EmailHelper = require('../Helpers/EmailHelper') +const { + callbackify, + callbackifyMultiResult, +} = require('@overleaf/promise-utils') +const OError = require('@overleaf/o-error') + +const UserRegistrationHandler = { + _registrationRequestIsValid(body) { + const invalidEmail = AuthenticationManager.validateEmail(body.email || '') + const invalidPassword = AuthenticationManager.validatePassword( + body.password || '', + body.email + ) + return !(invalidEmail || invalidPassword) + }, + + async _createNewUserIfRequired(user, userDetails) { + if (!user) { + userDetails.holdingAccount = false + return await UserCreator.promises.createNewUser( + { + holdingAccount: false, + email: userDetails.email, + first_name: userDetails.first_name, + last_name: userDetails.last_name, + analyticsId: userDetails.analyticsId, + }, + {} + ) + } + return user + }, + + async registerNewUser(userDetails) { + const requestIsValid = + UserRegistrationHandler._registrationRequestIsValid(userDetails) + + if (!requestIsValid) { + throw new Error('request is not valid') + } + userDetails.email = EmailHelper.parseEmail(userDetails.email) + + let user = await UserGetter.promises.getUserByAnyEmail(userDetails.email) + if (user && user.holdingAccount === false) { + // We add userId to the error object so that the calling function can access + // the id of the already existing user account. + throw new OError('EmailAlreadyRegistered', { userId: user._id }) + } + + user = await UserRegistrationHandler._createNewUserIfRequired( + user, + userDetails + ) + + await User.updateOne( + { _id: user._id }, + { $set: { holdingAccount: false } } + ).exec() + + await AuthenticationManager.promises.setUserPassword( + user, + userDetails.password + ) + + if (userDetails.subscribeToNewsletter === 'true') { + try { + NewsletterManager.subscribe(user) + } catch (error) { + logger.warn( + { err: error, user }, + 'Failed to subscribe user to newsletter' + ) + throw error + } + } + + return user + }, + + async registerNewUserAndSendActivationEmail(email) { + let user + try { + user = await UserRegistrationHandler.registerNewUser({ + email, + password: crypto.randomBytes(32).toString('hex'), + }) + } catch (error) { + if (error.message === 'EmailAlreadyRegistered') { + logger.debug({ email }, 'user already exists, resending welcome email') + user = await UserGetter.promises.getUserByAnyEmail(email) + } else { + throw error + } + } + + const ONE_WEEK = 7 * 24 * 60 * 60 // seconds + const token = await OneTimeTokenHandler.promises.getNewToken( + 'password', + { user_id: user._id.toString(), email: user.email }, + { expiresIn: ONE_WEEK } + ) + + const setNewPasswordUrl = `${settings.siteUrl}/user/activate?token=${token}&user_id=${user._id}` + + try { + await EmailHandler.promises.sendEmail('registered', { + to: user.email, + setNewPasswordUrl, + }) + } catch (error) { + logger.warn({ err: error }, 'failed to send activation email') + } + + return { user, setNewPasswordUrl } + }, +} + +module.exports = { + registerNewUser: callbackify(UserRegistrationHandler.registerNewUser), + registerNewUserAndSendActivationEmail: callbackifyMultiResult( + UserRegistrationHandler.registerNewUserAndSendActivationEmail, + ['user', 'setNewPasswordUrl'] + ), + promises: UserRegistrationHandler, +}