feat(reception): persist email action tokens and registration sessions for authentication and signup flows

This commit is contained in:
2026-04-20 08:27:35 +00:00
parent 1532c9704b
commit 28d30fe392
12 changed files with 477 additions and 199 deletions
@@ -5,10 +5,14 @@ import { logger } from './logging.js';
export class RegistrationSessionManager {
public receptionRef: Reception;
public registrationSessions = new plugins.lik.FastMap<RegistrationSession>();
public typedRouter = new plugins.typedrequest.TypedRouter();
public get db() {
return this.receptionRef.db.smartdataDb;
}
public CRegistrationSession = plugins.smartdata.setDefaultManagerForDoc(this, RegistrationSession);
constructor(receptionRefArg: Reception) {
this.receptionRef = receptionRefArg;
this.receptionRef.typedrouter.addTypedRouter(this.typedRouter);
@@ -29,17 +33,16 @@ export class RegistrationSessionManager {
`We sent you an Email with more information.`
);
}
// check for exiting SignupSession
const existingSession = this.registrationSessions.getByKey(requestData.email);
if (existingSession) {
const existingSessions = await this.CRegistrationSession.getInstances({
'data.emailAddress': requestData.email,
});
for (const existingSession of existingSessions) {
logger.log('warn', `destroyed old signupSession for ${requestData.email}`);
existingSession.destroy();
await existingSession.destroy();
}
// lets check the email before we create a signup session
const newSignupSession = await RegistrationSession.createRegistrationSessionForEmail(
this,
requestData.email
).catch((e: plugins.typedrequest.TypedResponseError) => {
console.log(e.errorText);
@@ -63,10 +66,7 @@ export class RegistrationSessionManager {
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_AfterRegistrationEmailClicked>(
'afterRegistrationEmailClicked',
async (requestData) => {
console.log(requestData);
const signupSession = await this.registrationSessions.find(async (itemArg) =>
itemArg.validateEmailToken(requestData.token)
);
const signupSession = await this.findRegistrationSessionByToken(requestData.token);
if (signupSession) {
return {
email: signupSession.emailAddress,
@@ -86,9 +86,7 @@ export class RegistrationSessionManager {
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_SetDataForRegistration>(
'setDataForRegistration',
async (requestData) => {
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
itemArg.validateEmailToken(requestData.token)
);
const registrationSession = await this.findRegistrationSessionByToken(requestData.token);
if (!registrationSession) {
throw new plugins.typedrequest.TypedResponseError(
'could not find a matching signupsession'
@@ -114,9 +112,7 @@ export class RegistrationSessionManager {
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_MobileVerificationForRegistration>(
'mobileVerificationForRegistration',
async (requestData) => {
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
itemArg.validateEmailToken(requestData.token)
);
const registrationSession = await this.findRegistrationSessionByToken(requestData.token);
if (!registrationSession) {
throw new plugins.typedrequest.TypedResponseError(
'could not find a matching signupsession'
@@ -131,17 +127,16 @@ export class RegistrationSessionManager {
}
if (requestData.mobileNumber) {
registrationSession.status = 'emailValidated';
registrationSession.collectedData.userData.mobileNumber = requestData.mobileNumber;
await registrationSession.sendValidationSms();
const smsCode = await registrationSession.sendValidationSms();
return {
messageSent: true,
testOnlySmsCode: process.env.TEST_MODE ? registrationSession.smsCode : null,
testOnlySmsCode: process.env.TEST_MODE ? smsCode : null,
};
}
if (requestData.verificationCode) {
const validationResult = registrationSession.validateSmsCode(
const validationResult = await registrationSession.validateSmsCode(
requestData.verificationCode
);
return {
@@ -160,9 +155,7 @@ export class RegistrationSessionManager {
new plugins.typedrequest.TypedHandler<plugins.idpInterfaces.request.IReq_FinishRegistration>(
'finishRegistration',
async (requestData) => {
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
itemArg.validateEmailToken(requestData.token)
);
const registrationSession = await this.findRegistrationSessionByToken(requestData.token);
if (!registrationSession) {
throw new plugins.typedrequest.TypedResponseError(
'could not find a matching signupsession'
@@ -170,7 +163,7 @@ export class RegistrationSessionManager {
}
const resultingUser = await registrationSession.manifestUserWithAccountData();
registrationSession.destroy();
await registrationSession.destroy();
this.receptionRef.receptionMailer.sendWelcomeEMail(resultingUser);
return {
accountData: {
@@ -187,4 +180,17 @@ export class RegistrationSessionManager {
)
);
}
public async findRegistrationSessionByToken(tokenArg: string) {
const registrationSession = await this.CRegistrationSession.getInstance({
'data.hashedEmailToken': RegistrationSession.hashToken(tokenArg),
});
if (!registrationSession) {
return null;
}
const isValid = await registrationSession.validateEmailToken(tokenArg);
return isValid ? registrationSession : null;
}
}