2024-10-01 13:49:18 +02:00
|
|
|
import * as plugins from '../plugins.js';
|
2024-09-29 13:56:38 +02:00
|
|
|
|
|
|
|
|
import { RegistrationSessionManager } from './classes.registrationsessionmanager.js';
|
|
|
|
|
import { logger } from './logging.js';
|
|
|
|
|
import { User } from './classes.user.js';
|
|
|
|
|
|
|
|
|
|
/**
|
2026-04-20 08:27:35 +00:00
|
|
|
* a RegistrationSession persists a sign up flow across restarts
|
2024-09-29 13:56:38 +02:00
|
|
|
*/
|
2026-04-20 08:27:35 +00:00
|
|
|
@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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-29 13:56:38 +02:00
|
|
|
public static async createRegistrationSessionForEmail(
|
|
|
|
|
emailArg: string
|
|
|
|
|
) {
|
2026-04-20 08:27:35 +00:00
|
|
|
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'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2024-09-29 13:56:38 +02:00
|
|
|
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.'
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-20 08:27:35 +00:00
|
|
|
|
|
|
|
|
const validationToken = await newRegistrationSession.sendTokenValidationEmail();
|
|
|
|
|
newRegistrationSession.unhashedEmailToken = validationToken;
|
2024-09-29 13:56:38 +02:00
|
|
|
return newRegistrationSession;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 08:27:35 +00:00
|
|
|
@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,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
2024-09-29 13:56:38 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* only used during testing
|
|
|
|
|
*/
|
|
|
|
|
public unhashedEmailToken?: string;
|
|
|
|
|
|
2026-04-20 08:27:35 +00:00
|
|
|
public get emailAddress() {
|
|
|
|
|
return this.data.emailAddress;
|
|
|
|
|
}
|
2024-09-29 13:56:38 +02:00
|
|
|
|
2026-04-20 08:27:35 +00:00
|
|
|
public get status() {
|
|
|
|
|
return this.data.status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public set status(statusArg: plugins.idpInterfaces.data.TRegistrationSessionStatus) {
|
|
|
|
|
this.data.status = statusArg;
|
|
|
|
|
}
|
2024-09-29 13:56:38 +02:00
|
|
|
|
2026-04-20 08:27:35 +00:00
|
|
|
public get collectedData() {
|
|
|
|
|
return this.data.collectedData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public isExpired() {
|
|
|
|
|
return this.data.validUntil < Date.now();
|
2024-09-29 13:56:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* validates a token by comparing its hash against the stored hashed token
|
|
|
|
|
*/
|
2026-04-20 08:27:35 +00:00
|
|
|
public async validateEmailToken(tokenArg: string): Promise<boolean> {
|
|
|
|
|
if (this.isExpired()) {
|
|
|
|
|
await this.destroy();
|
|
|
|
|
return false;
|
2024-09-29 13:56:38 +02:00
|
|
|
}
|
2026-04-20 08:27:35 +00:00
|
|
|
|
|
|
|
|
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();
|
2024-09-29 13:56:38 +02:00
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** validates the sms code */
|
2026-04-20 08:27:35 +00:00
|
|
|
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();
|
2024-09-29 13:56:38 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
2026-04-20 08:27:35 +00:00
|
|
|
|
|
|
|
|
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;
|
2024-09-29 13:56:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async validateEMailAddress(): Promise<plugins.smartmail.IEmailValidationResult> {
|
2026-04-20 08:27:35 +00:00
|
|
|
const result = await new plugins.smartmail.EmailAddressValidator().validate(this.data.emailAddress);
|
2024-09-29 13:56:38 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async sendTokenValidationEmail() {
|
|
|
|
|
const uuidToSend = plugins.smartunique.uuid4();
|
2026-04-20 08:27:35 +00:00
|
|
|
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;
|
2024-09-29 13:56:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async sendValidationSms() {
|
2026-04-20 08:27:35 +00:00
|
|
|
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;
|
2024-09-29 13:56:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async manifestUserWithAccountData(): Promise<User> {
|
2026-04-20 08:27:35 +00:00
|
|
|
if (this.data.status !== 'mobileVerified') {
|
2024-09-29 13:56:38 +02:00
|
|
|
throw new plugins.typedrequest.TypedResponseError(
|
|
|
|
|
'You can only manifest user that have a validated email Address and Mobile Number'
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-20 08:27:35 +00:00
|
|
|
if (!this.data.collectedData) {
|
2024-09-29 13:56:38 +02:00
|
|
|
throw new Error('You have to set the accountdata first');
|
|
|
|
|
}
|
2026-04-20 08:27:35 +00:00
|
|
|
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();
|
2024-09-29 13:56:38 +02:00
|
|
|
return manifestedUser;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 08:27:35 +00:00
|
|
|
public async destroy() {
|
|
|
|
|
await this.delete();
|
2024-09-29 13:56:38 +02:00
|
|
|
}
|
|
|
|
|
}
|