import { defaultTo, isEmpty, omit } from 'lodash'; import moment from 'moment'; import crypto from 'crypto'; import { ServiceError } from '@/exceptions'; import { IAuthSignedUpEventPayload, IAuthSigningUpEventPayload, IRegisterDTO, ISystemUser, } from '@/interfaces'; import { ERRORS } from './_constants'; import { Inject } from 'typedi'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import TenantsManagerService from '../Tenancy/TenantsManager'; import events from '@/subscribers/events'; import { hashPassword } from '@/utils'; import config from '@/config'; export class AuthSignupService { @Inject() private eventPublisher: EventPublisher; @Inject('repositories') private sysRepositories: any; @Inject() private tenantsManager: TenantsManagerService; /** * Registers a new tenant with user from user input. * @throws {ServiceErrors} * @param {IRegisterDTO} signupDTO * @returns {Promise} */ public async signUp(signupDTO: IRegisterDTO): Promise { const { systemUserRepository } = this.sysRepositories; // Validates the signup disable restrictions. await this.validateSignupRestrictions(signupDTO.email); // Validates the given email uniqiness. await this.validateEmailUniqiness(signupDTO.email); const hashedPassword = await hashPassword(signupDTO.password); const verifyTokenCrypto = crypto.randomBytes(64).toString('hex'); const verifiedEnabed = defaultTo(config.signupConfirmation.enabled, false); const verifyToken = verifiedEnabed ? verifyTokenCrypto : ''; const verified = !verifiedEnabed; const inviteAcceptedAt = moment().format('YYYY-MM-DD'); // Triggers signin up event. await this.eventPublisher.emitAsync(events.auth.signingUp, { signupDTO, } as IAuthSigningUpEventPayload); const tenant = await this.tenantsManager.createTenant(); const registeredUser = await systemUserRepository.create({ ...omit(signupDTO, 'country'), verifyToken, verified, active: true, password: hashedPassword, tenantId: tenant.id, inviteAcceptedAt, }); // Triggers signed up event. await this.eventPublisher.emitAsync(events.auth.signUp, { signupDTO, tenant, user: registeredUser, } as IAuthSignedUpEventPayload); return registeredUser; } /** * Validates email uniqiness on the storage. * @throws {ServiceErrors} * @param {string} email - Email address */ private async validateEmailUniqiness(email: string) { const { systemUserRepository } = this.sysRepositories; const isEmailExists = await systemUserRepository.findOneByEmail(email); if (isEmailExists) { throw new ServiceError(ERRORS.EMAIL_EXISTS); } } /** * Validate sign-up disable restrictions. * @param {string} email */ private async validateSignupRestrictions(email: string) { // Can't continue if the signup is not disabled. if (!config.signupRestrictions.disabled) return; // Validate the allowed email addresses and domains. if ( !isEmpty(config.signupRestrictions.allowedEmails) || !isEmpty(config.signupRestrictions.allowedDomains) ) { const emailDomain = email.split('@').pop(); const isAllowedEmail = config.signupRestrictions.allowedEmails.indexOf(email) !== -1; const isAllowedDomain = config.signupRestrictions.allowedDomains.some( (domain) => emailDomain === domain ); if (!isAllowedEmail && !isAllowedDomain) { throw new ServiceError(ERRORS.SIGNUP_RESTRICTED_NOT_ALLOWED); } // Throw error if the signup is disabled with no exceptions. } else { throw new ServiceError(ERRORS.SIGNUP_RESTRICTED); } } }