import TestApp from "../src/TestApp"; import useApp from "./_app"; import Controller from "../src/Controller"; import supertest from "supertest"; import CsrfProtectionComponent from "../src/components/CsrfProtectionComponent"; import User from "../src/auth/models/User"; import UserNameComponent from "../src/auth/models/UserNameComponent"; import UserPasswordComponent from "../src/auth/password/UserPasswordComponent"; import {popEmail} from "./_mail_server"; import AuthComponent from "../src/auth/AuthComponent"; import {followMagicLinkFromMail, testLogout} from "./_authentication_common"; let app: TestApp; useApp(async (addr, port) => { return app = new class extends TestApp { protected async init(): Promise { this.use(new class extends Controller { public routes(): void { this.get('/', (req, res) => { res.render('home'); }, 'home'); this.get('/csrf', (req, res) => { res.send(CsrfProtectionComponent.getCsrfToken(req.getSession())); }, 'csrf'); this.get('/is-auth', async (req, res) => { const proofs = await this.getApp().as(AuthComponent).getAuthGuard().getProofs(req); if (proofs.length > 0) res.sendStatus(200); else res.sendStatus(401); }, 'is-auth'); } }()); await super.init(); } }(addr, port); }); let agent: supertest.SuperTest; beforeAll(() => { agent = supertest(app.getExpressApp()); }); 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).name).toStrictEqual('entrapta'); await expect(user?.as(UserPasswordComponent).verifyPassword('darla_is_cute')).resolves.toStrictEqual(true); }); 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', '/csrf'); 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') .set('Cookie', cookies) .send({ csrf: csrf, auth_method: 'magic_link', identifier: 'glimmer@example.org', name: 'glimmer', }) .expect(302) .expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf'); await followMagicLinkFromMail(agent, cookies); 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).name).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?redirect_uri=%2Fcsrf'); 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?redirect_uri=%2Fcsrf'); 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') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'entrapta', password: 'darla_is_cute', 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: '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('InvalidFormatValidationError'); // 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('InvalidFormatValidationError'); }); }); 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') .set('Cookie', cookies) .send({ csrf: csrf, identifier: 'glimmer@example.org', auth_method: 'magic_link', }) .expect(302) .expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf'); 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?redirect_uri=%2Fcsrf'); 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?redirect_uri=%2Fcsrf'); 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).name).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); }); });