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 MysqlConnectionManager from "../src/db/MysqlConnectionManager"; import config from "config"; 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 {log} from "../src/Logger"; let app: TestApp; useApp(async (addr, port) => { await MysqlConnectionManager.prepare(); await MysqlConnectionManager.query('DROP DATABASE IF EXISTS ' + config.get('mysql.database')); await MysqlConnectionManager.endPool(); 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); }); async function followMagicLinkFromMail(cookies: string[]): Promise { const mail: Record | null = await popEmail(); expect(mail).not.toBeNull(); const query = (mail?.text as string).split('/magic/link?')[1].split('\n')[0]; expect(query).toBeDefined(); await agent.get('/magic/link?' + query) .expect(200); await agent.get('/magic/lobby') .set('Cookie', cookies) .expect(302) .expect('Location', '/'); } async function testLogout(cookies: string[], csrf: string): Promise { // Authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(200); // Logout await agent.post('/auth/logout') .set('Cookie', cookies) .send({csrf: csrf}) .expect(302); // Not authenticated await agent.get('/is-auth').set('Cookie', cookies).expect(401); } let agent: supertest.SuperTest; beforeAll(() => { agent = supertest(app.getExpressApp()); }); describe('Register with username', () => { 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('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); // Register user await agent.post('/auth/register') .set('Cookie', res1.get('Set-Cookie')) .send({ csrf: res1.text, auth_method: 'password', identifier: 'hordak', password: 'horde_prime_will_rise', password_confirmation: 'horde_prime_will_rise', terms: 'on', }) .expect(302) .expect('Location', '/'); // Verify saved user expect(await User.select() .where('name', 'hordak') .count()).toStrictEqual(1); const res2 = await agent.get('/csrf').expect(200); // Attempt register same user const res = await agent.post('/auth/register') .set('Cookie', res2.get('Set-Cookie')) .send({ csrf: res2.text, 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(cookies); // 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(cookies); // Verify saved user const user = await User.select() .with('mainEmail') .where('name', 'glimmer') .first(); expect(user).toBeDefined(); // Attempt register with another mail but same username const res2 = await agent.get('/csrf').expect(200); await agent.post('/auth/register') .set('Cookie', res2.get('Set-Cookie')) .send({ csrf: res2.text, 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(cookies); // Verify saved user const user = await User.select() .with('mainEmail') .where('name', 'glimmer') .first(); expect(user).toBeDefined(); // Attempt register with another mail but same username const res2 = await agent.get('/csrf').expect(200); await agent.post('/auth/register') .set('Cookie', res2.get('Set-Cookie')) .send({ csrf: res2.text, auth_method: 'magic_link', identifier: 'bow@example.org', name: 'bow2', }) .expect(400); expect(await popEmail()).toBeNull(); }); }); describe('Authenticate with username and 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(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(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(cookies); await testLogout(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(cookies); await testLogout(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); }); });