feat(reception): persist email action tokens and registration sessions for authentication and signup flows
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
|
||||
import { EmailActionToken } from '../ts/reception/classes.emailactiontoken.js';
|
||||
import { LoginSession } from '../ts/reception/classes.loginsession.js';
|
||||
import { RegistrationSession } from '../ts/reception/classes.registrationsession.js';
|
||||
import { User } from '../ts/reception/classes.user.js';
|
||||
import * as plugins from '../ts/plugins.js';
|
||||
|
||||
@@ -12,6 +14,42 @@ const createTestLoginSession = () => {
|
||||
return loginSession;
|
||||
};
|
||||
|
||||
const createTestEmailActionToken = () => {
|
||||
const emailActionToken = new EmailActionToken();
|
||||
emailActionToken.id = 'email-action-token';
|
||||
emailActionToken.data.email = 'user@example.com';
|
||||
emailActionToken.data.action = 'emailLogin';
|
||||
emailActionToken.data.validUntil = Date.now() + 60_000;
|
||||
|
||||
let deleted = false;
|
||||
(emailActionToken as EmailActionToken & { delete: () => Promise<void> }).delete = async () => {
|
||||
deleted = true;
|
||||
};
|
||||
|
||||
return {
|
||||
emailActionToken,
|
||||
wasDeleted: () => deleted,
|
||||
};
|
||||
};
|
||||
|
||||
const createTestRegistrationSession = () => {
|
||||
const registrationSession = new RegistrationSession();
|
||||
registrationSession.id = 'registration-session';
|
||||
registrationSession.data.emailAddress = 'user@example.com';
|
||||
registrationSession.data.validUntil = Date.now() + 60_000;
|
||||
|
||||
let deleted = false;
|
||||
(registrationSession as RegistrationSession & { save: () => Promise<void> }).save = async () => undefined;
|
||||
(registrationSession as RegistrationSession & { delete: () => Promise<void> }).delete = async () => {
|
||||
deleted = true;
|
||||
};
|
||||
|
||||
return {
|
||||
registrationSession,
|
||||
wasDeleted: () => deleted,
|
||||
};
|
||||
};
|
||||
|
||||
tap.test('hashes passwords with argon2 and verifies them', async () => {
|
||||
const passwordHash = await User.hashPassword('correct horse battery staple');
|
||||
|
||||
@@ -58,4 +96,45 @@ tap.test('persists transfer tokens as one-time hashes', async () => {
|
||||
expect(await loginSession.validateTransferToken(transferToken)).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('consumes email action tokens exactly once', async () => {
|
||||
const { emailActionToken, wasDeleted } = createTestEmailActionToken();
|
||||
const plainToken = EmailActionToken.createOpaqueToken('emailLogin');
|
||||
emailActionToken.data.tokenHash = EmailActionToken.hashToken(plainToken);
|
||||
|
||||
expect(await emailActionToken.consume(plainToken)).toBeTrue();
|
||||
expect(wasDeleted()).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('invalidates expired email action tokens', async () => {
|
||||
const { emailActionToken, wasDeleted } = createTestEmailActionToken();
|
||||
emailActionToken.data.tokenHash = EmailActionToken.hashToken('expired-token');
|
||||
emailActionToken.data.validUntil = Date.now() - 1;
|
||||
|
||||
expect(await emailActionToken.consume('expired-token')).toBeFalse();
|
||||
expect(wasDeleted()).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('persists registration token validation and sms verification state', async () => {
|
||||
const { registrationSession } = createTestRegistrationSession();
|
||||
const emailToken = 'registration-token';
|
||||
registrationSession.data.hashedEmailToken = RegistrationSession.hashToken(emailToken);
|
||||
|
||||
expect(await registrationSession.validateEmailToken(emailToken)).toBeTrue();
|
||||
expect(registrationSession.data.status).toEqual('emailValidated');
|
||||
expect(registrationSession.data.collectedData.userData.email).toEqual('user@example.com');
|
||||
|
||||
registrationSession.data.smsCodeHash = RegistrationSession.hashToken('123456');
|
||||
expect(await registrationSession.validateSmsCode('123456')).toBeTrue();
|
||||
expect(registrationSession.data.status).toEqual('mobileVerified');
|
||||
});
|
||||
|
||||
tap.test('removes expired registration sessions on token validation', async () => {
|
||||
const { registrationSession, wasDeleted } = createTestRegistrationSession();
|
||||
registrationSession.data.hashedEmailToken = RegistrationSession.hashToken('expired-registration');
|
||||
registrationSession.data.validUntil = Date.now() - 1;
|
||||
|
||||
expect(await registrationSession.validateEmailToken('expired-registration')).toBeFalse();
|
||||
expect(wasDeleted()).toBeTrue();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
|
||||
Reference in New Issue
Block a user