import * as querystring from "querystring"; import supertest from "supertest"; import User from "../src/auth/models/User.js"; import UserEmail from "../src/auth/models/UserEmail.js"; import UserNameComponent from "../src/auth/models/UserNameComponent.js"; import UserPasswordComponent from "../src/auth/password/UserPasswordComponent.js"; import useApp from "./_app.js"; import {authAppProvider, followMagicLinkFromMail, testLogout} from "./_authentication_common.js"; import {popEmail} from "./_mail_server.js"; const app = useApp(authAppProvider()); let agent: supertest.SuperTest; beforeAll(() => { agent = supertest(app().getExpressApp()); }); test('Approval Mode', () => { expect(User.isApprovalMode()).toStrictEqual(false); }); describe('Register with username and password (password)', () => { let cookies: string[]; let csrf: string; test('General case', async () => { const res = await agent.get('/csrf').expect(200); cookies = res.get('Set-Cookie'); csrf = res.text; // Register user await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'password', identifier: 'entrapta', password: 'darla_is_cute', password_confirmation: 'darla_is_cute', terms: 'on', }) .expect(302) .expect('Location', '/'); // Verify saved user const user = await User.select() .where('name', 'entrapta') .first(); expect(user).toBeDefined(); expect(user?.as(UserNameComponent).getName()).toStrictEqual('entrapta'); await expect(user?.as(UserPasswordComponent).verifyPassword('darla_is_cute')).resolves.toStrictEqual(true); // Proof must not be revoked await agent.get('/has-any-password-auth-proof') .set('Cookie', cookies) .expect(200); }); test('Can\'t register when logged in', async () => { await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'password', identifier: 'entrapta2', password: 'darla_is_cute', password_confirmation: 'darla_is_cute', terms: 'on', }) .expect(302) .expect('Location', '/'); const user2 = await User.select() .where('name', 'entrapta2') .first(); expect(user2).toBeNull(); }); test('Logout', async () => { await testLogout(agent, cookies, csrf); }); test('Cannot register taken username', async () => { // Check that there is no hordak in DB expect(await User.select() .where('name', 'hordak') .count()).toStrictEqual(0); const res1 = await agent.get('/csrf').expect(200); const cookies = res1.get('Set-Cookie'); const csrf = res1.text; // Register user await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'password', identifier: 'hordak', password: 'horde_prime_will_rise', password_confirmation: 'horde_prime_will_rise', terms: 'on', }) .expect(302) .expect('Location', '/'); await testLogout(agent, cookies, csrf); // Verify saved user expect(await User.select() .where('name', 'hordak') .count()).toStrictEqual(1); // Attempt register same user const res = await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'password', identifier: 'hordak', password: 'horde_prime_will_rise_unless', password_confirmation: 'horde_prime_will_rise_unless', terms: 'on', }) .expect(400); // username field should be translated from identifier expect(res.body.messages?.identifier?.name).toStrictEqual('AlreadyExistsValidationError'); // Verify nothing changed expect(await User.select() .where('name', 'hordak') .count()).toStrictEqual(1); }); }); describe('Register with email (magic_link)', () => { test('General case', async () => { const res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; await agent.post('/auth/register?' + querystring.stringify({redirect_uri: '/redirect-uri'})) .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'glimmer@example.org', name: 'glimmer', }) .expect(302) .expect('Location', '/magic/lobby?redirect_uri=%2Fredirect-uri'); await followMagicLinkFromMail(agent, cookies); // Proof must not be revoked await agent.get('/has-any-magic-link') .set('Cookie', cookies) .expect(200); await testLogout(agent, cookies, csrf); // Verify saved user const user = await User.select() .with('mainEmail') .where('name', 'glimmer') .first(); expect(user).toBeDefined(); const email = user?.mainEmail.getOrFail(); expect(email).toBeDefined(); expect(email?.email).toStrictEqual('glimmer@example.org'); expect(user?.as(UserNameComponent).getName()).toStrictEqual('glimmer'); await expect(user?.as(UserPasswordComponent).verifyPassword('')).resolves.toStrictEqual(false); }); test('Cannot register without specifying username', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; res = await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'no_user_name@example.org', }) .expect(400); expect(res.body.messages?.name?.name).toStrictEqual('UndefinedValueValidationError'); expect(await popEmail()).toBeNull(); }); test('Cannot register taken username', async () => { const res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'angella@example.org', name: 'angella', }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); await testLogout(agent, cookies, csrf); // Verify saved user const user = await User.select() .with('mainEmail') .where('name', 'angella') .first(); expect(user).toBeDefined(); // Attempt register with another mail but same username await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'angella_something_else@example.org', name: 'angella', }) .expect(400); expect(await popEmail()).toBeNull(); }); test('Cannot register taken email', async () => { const res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'bow@example.org', name: 'bow', }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); await testLogout(agent, cookies, csrf); // Verify saved user const user = await User.select() .with('mainEmail') .where('name', 'bow') .first(); expect(user).toBeDefined(); // Attempt register with another mail but same username await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'bow@example.org', name: 'bow2', }) .expect(400); expect(await popEmail()).toBeNull(); }); }); describe('Authenticate with username and password (password)', () => { test('Force auth_method', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Bad password res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'entrapta', password: 'darla_is_not_cute', auth_method: 'password', }) .expect(400); expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError'); // Authenticate await agent.post('/auth/login?' + querystring.stringify({redirect_uri: '/redirect-uri'})) .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'entrapta', password: 'darla_is_cute', auth_method: 'password', }) .expect(302) .expect('Location', '/redirect-uri'); await testLogout(agent, cookies, csrf); }); test('Automatic auth_method', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Bad password res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'entrapta', password: 'darla_is_not_cute', }) .expect(400); expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError'); // Authenticate await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'entrapta', password: 'darla_is_cute', }) .expect(302) .expect('Location', '/'); await testLogout(agent, cookies, csrf); }); test('Non-existing username', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Authenticate res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'i_do_not_exist', password: 'there_is_no_point', auth_method: 'password', }) .expect(400); expect(res.body.messages?.identifier?.name).toStrictEqual('UnknownRelationValidationError'); // Authenticate (automatic method) res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'i_do_not_exist', password: 'there_is_no_point', auth_method: 'password', }) .expect(400); expect(res.body.messages?.identifier?.name).toStrictEqual('UnknownRelationValidationError'); }); test('No password user', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Authenticate res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'glimmer', password: '', auth_method: 'password', }) .expect(400); expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError'); // Authenticate (automatic method) res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'glimmer', password: '', }) .expect(400); expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError'); // Authenticate without password res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'angella', auth_method: 'password', }) .expect(400); expect(res.body.messages?.password?.name).toStrictEqual('UndefinedValueValidationError'); // Authenticate without password (automatic method) res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'angella', }) .expect(400); expect(res.body.messages?.password?.name).toStrictEqual('UndefinedValueValidationError'); }); }); describe('Authenticate with email (magic_link)', () => { test('Force auth_method', async () => { const res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Authenticate await agent.post('/auth/login?' + querystring.stringify({redirect_uri: '/redirect-uri'})) .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'glimmer@example.org', auth_method: 'magic_link', }) .expect(302) .expect('Location', '/magic/lobby?redirect_uri=%2Fredirect-uri'); await followMagicLinkFromMail(agent, cookies); await testLogout(agent, cookies, csrf); }); test('Automatic auth_method', async () => { const res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Authenticate await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'angella@example.org', }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); await testLogout(agent, cookies, csrf); }); test('Non-existing email (forced auth_method)', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Authenticate res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'i_do_not_exist@invalid.org', auth_method: 'magic_link', }) .expect(400); expect(res.body.messages?.identifier?.name).toStrictEqual('UnknownRelationValidationError'); await agent.get('/is-auth').set('Cookie', cookies).expect(401); }); test('Non-existing email (automatic auth_method)', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Authenticate res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'i_do_not_exist@invalid.org', }) .expect(400); expect(res.body.messages?.identifier?.name).toStrictEqual('UnknownRelationValidationError'); await agent.get('/is-auth').set('Cookie', cookies).expect(401); }); }); describe('Authenticate with email and password (password)', () => { test('Prepare user', async () => { const res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'double-trouble@example.org', name: 'double-trouble', }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); await testLogout(agent, cookies, csrf); // Verify saved user const user = await User.select() .with('mainEmail') .where('name', 'double-trouble') .first(); await user?.as(UserPasswordComponent).setPassword('trick-or-treat'); await user?.save(); expect(user).toBeDefined(); const email = user?.mainEmail.getOrFail(); expect(email).toBeDefined(); expect(email?.email).toStrictEqual('double-trouble@example.org'); expect(user?.as(UserNameComponent).getName()).toStrictEqual('double-trouble'); await expect(user?.as(UserPasswordComponent).verifyPassword('trick-or-treat')).resolves.toStrictEqual(true); }); test('Force auth_method', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Bad password res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'double-trouble@example.org', password: 'i_have_no_imagination', auth_method: 'password', }) .expect(400); expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError'); // Authenticate await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'double-trouble@example.org', password: 'trick-or-treat', auth_method: 'password', }) .expect(302) .expect('Location', '/'); await testLogout(agent, cookies, csrf); }); test('Automatic auth_method', async () => { let res = await agent.get('/csrf').expect(200); const cookies = res.get('Set-Cookie'); const csrf = res.text; // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); // Bad password res = await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'double-trouble@example.org', password: 'i_have_no_imagination', }) .expect(400); expect(res.body.messages?.password?.name).toStrictEqual('InvalidFormatValidationError'); // Authenticate await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'double-trouble@example.org', password: 'trick-or-treat', }) .expect(302) .expect('Location', '/'); await testLogout(agent, cookies, csrf); }); }); describe('Change password', () => { let cookies: string[], csrf: string; test('Prepare user', async () => { const res = await agent.get('/csrf').expect(200); cookies = res.get('Set-Cookie'); csrf = res.text; await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'aang@example.org', name: 'aang', }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); }); test('Set password to blank from blank', async () => { await agent.post('/account/change-password') .set('Cookie', cookies) .send({ csrf: csrf, 'current_password': '', 'new_password': '', 'new_password_confirmation': '', }) .expect(400); const user = await User.select() .where('name', 'aang') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy(); }); test('Set password to something from blank', async () => { await agent.post('/account/change-password') .set('Cookie', cookies) .send({ csrf: csrf, 'current_password': '', 'new_password': 'a_very_strong_password', 'new_password_confirmation': 'a_very_strong_password', }) .expect(302) .expect('Location', '/account/'); const user = await User.select() .where('name', 'aang') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy(); expect(await user?.as(UserPasswordComponent).verifyPassword('')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password')).toBeTruthy(); }); test('Set password to blank from something', async () => { await agent.post('/account/change-password') .set('Cookie', cookies) .send({ csrf: csrf, 'current_password': 'a_very_strong_password', 'new_password': '', }) .expect(400); const user = await User.select() .where('name', 'aang') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy(); expect(await user?.as(UserPasswordComponent).verifyPassword('')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password')).toBeTruthy(); }); test('Set password to something from something', async () => { await agent.post('/account/change-password') .set('Cookie', cookies) .send({ csrf: csrf, 'current_password': 'a_very_strong_password', 'new_password': 'a_very_strong_password_but_different', 'new_password_confirmation': 'a_very_strong_password_but_different', }) .expect(302) .expect('Location', '/account/'); const user = await User.select() .where('name', 'aang') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy(); expect(await user?.as(UserPasswordComponent).verifyPassword('')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password_but_different')).toBeTruthy(); }); test('Set password to unconfirmed', async () => { await agent.post('/account/change-password') .set('Cookie', cookies) .send({ csrf: csrf, 'current_password': 'a_very_strong_password_but_different', 'new_password': 'a_very_strong_password', 'new_password_confirmation': '', }) .expect(400); await agent.post('/account/change-password') .set('Cookie', cookies) .send({ csrf: csrf, 'current_password': 'a_very_strong_password_but_different', 'new_password': 'a_very_strong_password', 'new_password_confirmation': 'a_very_strong_password_but_different2', }) .expect(400); const user = await User.select() .where('name', 'aang') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy(); expect(await user?.as(UserPasswordComponent).verifyPassword('bad_password')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password')).toBeFalsy(); expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password_but_different')).toBeTruthy(); expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password_but_different2')).toBeFalsy(); }); test('Set password to too short password', async () => { await agent.post('/account/change-password') .set('Cookie', cookies) .send({ csrf: csrf, 'current_password': 'a_very_strong_password_but_different', 'new_password': '123', 'new_password_confirmation': '123', }) .expect(400); const user = await User.select() .where('name', 'aang') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBeTruthy(); expect(await user?.as(UserPasswordComponent).verifyPassword('a_very_strong_password_but_different')).toBeTruthy(); expect(await user?.as(UserPasswordComponent).verifyPassword('123')).toBeFalsy(); }); test('Remove password', async () => { let user = await User.select() .where('name', 'aang') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBe(true); await agent.post('/account/remove-password') .set('Cookie', cookies) .send({ csrf: csrf, }) .expect(302) .expect('Location', '/magic/lobby?redirect_uri=%2Faccount%2F'); await followMagicLinkFromMail(agent, cookies, '/account/'); user = await User.select() .where('name', 'aang') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBe(false); }); test('Can\'t remove password without contact email', async () => { const res = await agent.get('/csrf').expect(200); cookies = res.get('Set-Cookie'); csrf = res.text; await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'password', identifier: 'eleanor', password: 'this_is_a_very_strong_password', password_confirmation: 'this_is_a_very_strong_password', terms: 'on', }) .expect(302) .expect('Location', '/'); let user = await User.select() .where('name', 'eleanor') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBe(true); await agent.post('/account/remove-password') .set('Cookie', cookies) .send({ csrf: csrf, }) .expect(302) .expect('Location', '/account/'); user = await User.select() .where('name', 'eleanor') .first(); expect(user).toBeDefined(); expect(user?.as(UserPasswordComponent).hasPassword()).toBe(true); }); }); describe('Change username', () => { let cookies: string[], csrf: string; test('Prepare user', async () => { const res = await agent.get('/csrf').expect(200); cookies = res.get('Set-Cookie'); csrf = res.text; await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'password', identifier: 'richard', password: 'a_very_strong_password', password_confirmation: 'a_very_strong_password', terms: 'on', }) .expect(302) .expect('Location', '/'); }); test('Initial value of name_changed_at should be the same as created_at', async () => { const user = await User.select().where('name', 'richard').first(); expect(user).toBeDefined(); expect(user?.as(UserNameComponent).getNameChangedAt()?.getTime()).toBe(user?.created_at?.getTime()); }); test('Cannot change username just after registration', async () => { expect(await User.select().where('name', 'richard').count()).toBe(1); await agent.post('/account/change-name') .set('Cookie', cookies) .send({ csrf: csrf, name: 'robert', }) .expect(302) .expect('Location', '/account/'); expect(await User.select().where('name', 'richard').count()).toBe(1); }); test('Can change username after hold period', async () => { // Set last username change to older date const user = await User.select().where('name', 'richard').first(); if (user) { user.as(UserNameComponent).forgetNameChangeDate(); await user.save(); } expect(await User.select().where('name', 'richard').count()).toBe(1); expect(await User.select().where('name', 'robert').count()).toBe(0); await agent.post('/account/change-name') .set('Cookie', cookies) .send({ csrf: csrf, name: 'robert', }) .expect(302) .expect('Location', '/account/'); expect(await User.select().where('name', 'richard').count()).toBe(0); expect(await User.select().where('name', 'robert').count()).toBe(1); }); test('Cannot change username just after changing username', async () => { expect(await User.select().where('name', 'robert').count()).toBe(1); expect(await User.select().where('name', 'bebert').count()).toBe(0); await agent.post('/account/change-name') .set('Cookie', cookies) .send({ csrf: csrf, name: 'bebert', }) .expect(302) .expect('Location', '/account/'); expect(await User.select().where('name', 'robert').count()).toBe(1); expect(await User.select().where('name', 'bebert').count()).toBe(0); }); }); describe('Manage email addresses', () => { async function testMainSecondaryState(main: string, secondary: string) { const user = await User.select('main_email_id').where('name', 'katara').first(); const mainEmail = await UserEmail.select().where('email', main).first(); expect(mainEmail).not.toBeNull(); expect(user?.main_email_id).toBe(mainEmail?.id); const secondaryEmail = await UserEmail.select().where('email', secondary).first(); expect(secondaryEmail).not.toBeNull(); expect(user?.main_email_id).not.toBe(secondaryEmail?.id); return secondaryEmail; } let cookies: string[], csrf: string; test('Prepare user', async () => { const res = await agent.get('/csrf').expect(200); cookies = res.get('Set-Cookie'); csrf = res.text; await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'katara@example.org', name: 'katara', }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); // Add fake, not owned secondary email const user = User.create({ name: 'not_katara', }); await user.save(); await UserEmail.create({ email: 'not_katara@example.org', user_id: user.id, }).save(); }); describe('Add', () => { test('Add invalid email addresses', async () => { await agent.post('/account/add-email') .set('Cookie', cookies) .send({ csrf: csrf, }) .expect(400); await agent.post('/account/add-email') .set('Cookie', cookies) .send({ csrf: csrf, email: '', }) .expect(400); await agent.post('/account/add-email') .set('Cookie', cookies) .send({ csrf: csrf, email: 'katara@example.org', }) .expect(400); expect(await UserEmail.select().where('email', 'katara@example.org').count()).toBe(1); }); test('Add valid email addresses', async () => { const expectedUserId = (await User.select('id').where('name', 'katara').first())?.id; for (const email of [ 'katara2@example.org', 'katara3@example.org', 'katara4@example.org', ]) { await agent.post('/account/add-email') .set('Cookie', cookies) .send({ csrf: csrf, email: email, }) .expect(302) .expect('Location', '/magic/lobby?redirect_uri=%2Faccount%2F'); await followMagicLinkFromMail(agent, cookies, '/account/'); const userEmail = await UserEmail.select().where('email', email).first(); expect(userEmail).not.toBeNull(); expect(userEmail?.user_id).toBe(expectedUserId); } }); }); describe('Set main', () => { test('Set main email address as main email address', async () => { await testMainSecondaryState('katara@example.org', 'katara3@example.org'); // Set secondary as main await agent.post('/account/set-main-email') .set('Cookie', cookies) .send({ csrf: csrf, id: (await UserEmail.select().where('email', 'katara@example.org').first())?.id, }) .expect(400); await testMainSecondaryState('katara@example.org', 'katara3@example.org'); }); test('Set secondary email address as main email address', async () => { const beforeSecondaryEmail = await testMainSecondaryState('katara@example.org', 'katara3@example.org'); // Set secondary as main await agent.post('/account/set-main-email') .set('Cookie', cookies) .send({ csrf: csrf, id: beforeSecondaryEmail?.id, }) .expect(302) .expect('Location', '/account/'); await testMainSecondaryState('katara3@example.org', 'katara@example.org'); }); test('Set non-owned address as main email address', async () => { const beforeSecondaryEmail = await testMainSecondaryState('katara3@example.org', 'not_katara@example.org'); // Set secondary as main await agent.post('/account/set-main-email') .set('Cookie', cookies) .send({ csrf: csrf, id: beforeSecondaryEmail?.id, }) .expect(403); await testMainSecondaryState('katara3@example.org', 'not_katara@example.org'); }); test('Set non-existing address as main email address', async () => { await testMainSecondaryState('katara3@example.org', 'katara4@example.org'); // Set secondary as main await agent.post('/account/set-main-email') .set('Cookie', cookies) .send({ csrf: csrf, id: 999, }) .expect(404); await testMainSecondaryState('katara3@example.org', 'katara4@example.org'); }); }); describe('Remove', () => { test('Remove secondary email address', async () => { expect(await UserEmail.select().where('email', 'katara2@example.org').count()).toBe(1); // Set secondary as main await agent.post('/account/remove-email') .set('Cookie', cookies) .send({ csrf: csrf, id: (await UserEmail.select().where('email', 'katara2@example.org').first())?.id, }) .expect(302) .expect('Location', '/account/'); expect(await UserEmail.select().where('email', 'katara2@example.org').count()).toBe(0); }); test('Remove non-owned email address', async () => { expect(await UserEmail.select().where('email', 'not_katara@example.org').count()).toBe(1); // Set secondary as main await agent.post('/account/remove-email') .set('Cookie', cookies) .send({ csrf: csrf, id: (await UserEmail.select().where('email', 'not_katara@example.org').first())?.id, }) .expect(403); expect(await UserEmail.select().where('email', 'not_katara@example.org').count()).toBe(1); }); test('Remove non-existing email address', async () => { // Set secondary as main await agent.post('/account/remove-email') .set('Cookie', cookies) .send({ csrf: csrf, id: 999, }) .expect(404); }); test('Remove main email address', async () => { expect(await UserEmail.select().where('email', 'katara3@example.org').count()).toBe(1); // Set secondary as main await agent.post('/account/remove-email') .set('Cookie', cookies) .send({ csrf: csrf, id: (await UserEmail.select().where('email', 'katara3@example.org').first())?.id, }) .expect(400); expect(await UserEmail.select().where('email', 'katara3@example.org').count()).toBe(1); }); }); }); describe('Session persistence', () => { let cookies: string[], csrf: string; test('Not persistent at registration', async () => { let res = await agent.get('/csrf').expect(200); cookies = res.get('Set-Cookie'); csrf = res.text; await agent.post('/auth/register') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'zuko@example.org', name: 'zuko', }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); expect(cookies[0]).toMatch(/^connect\.sid=.+; Path=\/; SameSite=Strict$/); res = await agent.get('/csrf') .set('Cookie', cookies) .expect(200); expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; Expires=.+; SameSite=Strict$/); // Logout await agent.post('/auth/logout') .set('Cookie', cookies) .send({csrf}) .expect(302) .expect('Location', '/'); }); test('Login with persistence', async () => { await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'zuko@example.org', persist_session: 'on', }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); const res = await agent.get('/csrf') .set('Cookie', cookies) .expect(200); expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; Expires=.+; SameSite=Strict$/); // Logout await agent.post('/auth/logout') .set('Cookie', cookies) .send({csrf}) .expect(302) .expect('Location', '/'); }); test('Login without persistence', async () => { await agent.post('/auth/login') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'zuko@example.org', persist_session: undefined, }) .expect(302) .expect('Location', '/magic/lobby'); await followMagicLinkFromMail(agent, cookies); const res = await agent.get('/csrf') .set('Cookie', cookies) .expect(200); expect(res.get('Set-Cookie')[0]).toMatch(/^connect\.sid=.+; Path=\/; SameSite=Strict$/); // Logout await agent.post('/auth/logout') .set('Cookie', cookies) .send({csrf}) .expect(302) .expect('Location', '/'); }); });