import * as plugins from '../plugins.js'; import { RegistrationSessionManager } from './classes.registrationsessionmanager.js'; import { logger } from './logging.js'; import { User } from './classes.user.js'; /** * a RegistrationSession persists a sign up flow across restarts */ @plugins.smartdata.Manager() export class RegistrationSession extends plugins.smartdata.SmartDataDbDoc< RegistrationSession, plugins.idpInterfaces.data.IRegistrationSession, RegistrationSessionManager > { public static hashToken(tokenArg: string) { return plugins.smarthash.sha256FromStringSync(tokenArg); } public static async createRegistrationSessionForEmail( emailArg: string ) { const newRegistrationSession = new RegistrationSession(); newRegistrationSession.id = plugins.smartunique.shortId(); newRegistrationSession.data.emailAddress = emailArg; newRegistrationSession.data.validUntil = Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ minutes: 10 }); newRegistrationSession.data.createdAt = Date.now(); const emailValidationResult = await newRegistrationSession.validateEMailAddress().catch(() => { throw new plugins.typedrequest.TypedResponseError( 'Error occured during email provider & dns validation' ); }); if (!emailValidationResult?.valid) { throw new plugins.typedrequest.TypedResponseError( 'Email Address is not valid. Please use a correctly formated email address' ); } if (emailValidationResult.disposable) { throw new plugins.typedrequest.TypedResponseError( 'Email is disposable. Please use a non disposable email address.' ); } const validationToken = await newRegistrationSession.sendTokenValidationEmail(); newRegistrationSession.unhashedEmailToken = validationToken; return newRegistrationSession; } @plugins.smartdata.unI() public id: string; @plugins.smartdata.svDb() public data: plugins.idpInterfaces.data.IRegistrationSession['data'] = { emailAddress: '', hashedEmailToken: '', smsCodeHash: null, smsvalidationCounter: 0, status: 'announced', validUntil: 0, createdAt: 0, collectedData: { userData: { username: null, connectedOrgs: [], email: null, name: null, status: null, mobileNumber: null, password: null, passwordHash: null, }, }, }; /** * only used during testing */ public unhashedEmailToken?: string; public get emailAddress() { return this.data.emailAddress; } public get status() { return this.data.status; } public set status(statusArg: plugins.idpInterfaces.data.TRegistrationSessionStatus) { this.data.status = statusArg; } public get collectedData() { return this.data.collectedData; } public isExpired() { return this.data.validUntil < Date.now(); } /** * validates a token by comparing its hash against the stored hashed token */ public async validateEmailToken(tokenArg: string): Promise { if (this.isExpired()) { await this.destroy(); return false; } const result = this.data.hashedEmailToken === RegistrationSession.hashToken(tokenArg); if (result && this.data.status === 'announced') { this.data.status = 'emailValidated'; this.data.collectedData.userData.email = this.data.emailAddress; await this.save(); } if (!result && this.data.status === 'announced') { this.data.status = 'failed'; await this.save(); } return result; } /** validates the sms code */ public async validateSmsCode(smsCodeArg: string) { this.data.smsvalidationCounter++; const result = this.data.smsCodeHash === RegistrationSession.hashToken(smsCodeArg); if (this.data.status === 'emailValidated' && result) { this.data.status = 'mobileVerified'; await this.save(); return result; } if (this.data.smsvalidationCounter >= 5) { await this.destroy(); throw new plugins.typedrequest.TypedResponseError( 'Registration cancelled due to repeated wrong verification code submission' ); } await this.save(); return false; } public async validateEMailAddress(): Promise { const result = await new plugins.smartmail.EmailAddressValidator().validate(this.data.emailAddress); return result; } public async sendTokenValidationEmail() { const uuidToSend = plugins.smartunique.uuid4(); this.data.hashedEmailToken = RegistrationSession.hashToken(uuidToSend); await this.save(); this.manager.receptionRef.receptionMailer.sendRegistrationEmail(this, uuidToSend); logger.log('info', `sent a validation email with a verification code to ${this.data.emailAddress}`); return uuidToSend; } public async sendValidationSms() { const smsCode = await this.manager.receptionRef.szPlatformClient.smsConnector.sendSmsVerifcation({ fromName: this.manager.receptionRef.options.name, toNumber: parseInt(this.data.collectedData.userData.mobileNumber), }); this.data.smsCodeHash = RegistrationSession.hashToken(smsCode); await this.save(); return smsCode; } public async manifestUserWithAccountData(): Promise { if (this.data.status !== 'mobileVerified') { throw new plugins.typedrequest.TypedResponseError( 'You can only manifest user that have a validated email Address and Mobile Number' ); } if (!this.data.collectedData) { throw new Error('You have to set the accountdata first'); } const manifestedUser = await this.manager.receptionRef.userManager.CUser.createNewUserForUserData( this.data.collectedData.userData as plugins.idpInterfaces.data.IUser['data'] ); this.data.status = 'registered'; await this.save(); return manifestedUser; } public async destroy() { await this.delete(); } }