const UsersDBApi = require('../db/api/users'); const ValidationError = require('./notifications/errors/validation'); const ForbiddenError = require('./notifications/errors/forbidden'); const bcrypt = require('bcrypt'); const EmailAddressVerificationEmail = require('./email/list/addressVerification'); const InvitationEmail = require('./email/list/invitation'); const PasswordResetEmail = require('./email/list/passwordReset'); const EmailSender = require('./email'); const config = require('../config'); const helpers = require('../helpers'); class Auth { static async signup(email, password, options = {}, host) { const user = await UsersDBApi.findBy({ email }); const hashedPassword = await bcrypt.hash( password, config.bcrypt.saltRounds, ); if (user) { if (user.authenticationUid) { throw new ValidationError('auth.emailAlreadyInUse'); } if (user.disabled) { throw new ValidationError('auth.userDisabled'); } await UsersDBApi.updatePassword(user.id, hashedPassword, options); if (EmailSender.isConfigured) { await this.sendEmailAddressVerificationEmail(user.email, host); } const data = { user: { id: user.id, email: user.email, }, }; return helpers.jwtSign(data); } const newUser = await UsersDBApi.createFromAuth( { firstName: email.split('@')[0], password: hashedPassword, email: email, }, options, ); if (EmailSender.isConfigured) { await this.sendEmailAddressVerificationEmail(newUser.email, host); } const data = { user: { id: newUser.id, email: newUser.email, }, }; return helpers.jwtSign(data); } static async signin(email, password, options = {}) { const user = await UsersDBApi.findBy({ email }); if (!user) { throw new ValidationError('auth.userNotFound'); } if (user.disabled) { throw new ValidationError('auth.userDisabled'); } if (!user.password) { throw new ValidationError('auth.wrongPassword'); } if (!EmailSender.isConfigured) { user.emailVerified = true; } if (!user.emailVerified) { throw new ValidationError('auth.userNotVerified'); } const passwordsMatch = await bcrypt.compare(password, user.password); if (!passwordsMatch) { throw new ValidationError('auth.wrongPassword'); } const data = { user: { id: user.id, email: user.email, }, }; return helpers.jwtSign(data); } static async sendEmailAddressVerificationEmail(email, host) { let link; try { const token = await UsersDBApi.generateEmailVerificationToken(email); link = `${host}/verify-email?token=${token}`; } catch (error) { console.error(error); throw new ValidationError('auth.emailAddressVerificationEmail.error'); } const emailAddressVerificationEmail = new EmailAddressVerificationEmail( email, link, ); return new EmailSender(emailAddressVerificationEmail).send(); } static async sendPasswordResetEmail(email, type = 'register', host) { let link; try { const token = await UsersDBApi.generatePasswordResetToken(email); link = `${host}/password-reset?token=${token}`; } catch (error) { console.error(error); throw new ValidationError('auth.passwordReset.error'); } let passwordResetEmail; if (type === 'register') { passwordResetEmail = new PasswordResetEmail(email, link); } if (type === 'invitation') { passwordResetEmail = new InvitationEmail(email, link); } return new EmailSender(passwordResetEmail).send(); } static async verifyEmail(token, options = {}) { const user = await UsersDBApi.findByEmailVerificationToken(token, options); if (!user) { throw new ValidationError( 'auth.emailAddressVerificationEmail.invalidToken', ); } return UsersDBApi.markEmailVerified(user.id, options); } static async passwordUpdate(currentPassword, newPassword, options) { const currentUser = options.currentUser || null; if (!currentUser) { throw new ForbiddenError(); } const currentPasswordMatch = await bcrypt.compare( currentPassword, currentUser.password, ); if (!currentPasswordMatch) { throw new ValidationError('auth.wrongPassword'); } const newPasswordMatch = await bcrypt.compare( newPassword, currentUser.password, ); if (newPasswordMatch) { throw new ValidationError('auth.passwordUpdate.samePassword'); } const hashedPassword = await bcrypt.hash( newPassword, config.bcrypt.saltRounds, ); return UsersDBApi.updatePassword(currentUser.id, hashedPassword, options); } static async passwordReset(token, password, options = {}) { const user = await UsersDBApi.findByPasswordResetToken(token, options); if (!user) { throw new ValidationError('auth.passwordReset.invalidToken'); } const hashedPassword = await bcrypt.hash( password, config.bcrypt.saltRounds, ); return UsersDBApi.updatePassword(user.id, hashedPassword, options); } static async updateProfile(data, currentUser) { let transaction = await db.sequelize.transaction(); try { await UsersDBApi.findBy({ id: currentUser.id }, { transaction }); await UsersDBApi.update(currentUser.id, data, { currentUser, transaction, }); await transaction.commit(); } catch (error) { await transaction.rollback(); throw error; } } } module.exports = Auth;