Files
app/ts/reception/classes.registrationsession.ts

192 lines
5.9 KiB
TypeScript

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<boolean> {
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<plugins.smartmail.IEmailValidationResult> {
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<User> {
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();
}
}