diff --git a/test/Authentication.test.ts b/test/Authentication.test.ts new file mode 100644 index 0000000..92a2465 --- /dev/null +++ b/test/Authentication.test.ts @@ -0,0 +1,123 @@ +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 {log} from "../src/Logger"; +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"; + +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'); + } + }()); + + await super.init(); + } + }(addr, port); +}); + +let agent: supertest.SuperTest; + +describe('Authentication system', () => { + test('Obtain session cookies', async () => { + agent = supertest(app.getExpressApp()); + }); + + test('Register with email with username', async () => { + const res = await agent.get('/csrf').expect(200); + const cookies = res.get('Set-Cookie'); + const csrf = res.text; + + expect(cookies).toBeDefined(); + 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('Register with email with email (magic_link)', async () => { + let res = await agent.get('/csrf').expect(200); + const cookies = res.get('Set-Cookie'); + const csrf = res.text; + + expect(cookies).toBeDefined(); + res = 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'); + + 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(); + + // .expect('Location', '/'); + res = await agent.get('/magic/link?' + query) + .expect(200); + res = await agent.get('/magic/lobby') + .set('Cookie', cookies) + .expect(302) + .expect('Location', '/'); + log.debug(res.status, res.headers, res.body, res.text); + + + // 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); + }); +}); diff --git a/test/CsrfProtectionComponent.test.ts b/test/CsrfProtectionComponent.test.ts index 33999f2..c418f6e 100644 --- a/test/CsrfProtectionComponent.test.ts +++ b/test/CsrfProtectionComponent.test.ts @@ -1,10 +1,10 @@ -import useApp, {TestApp} from "./_app"; +import useApp from "./_app"; import Controller from "../src/Controller"; -import CsrfProtectionComponent from "../src/components/CsrfProtectionComponent"; import supertest from "supertest"; +import TestApp from "../src/TestApp"; let app: TestApp; -useApp((addr, port) => { +useApp(async (addr, port) => { return app = new class extends TestApp { protected async init(): Promise { this.use(new class extends Controller { @@ -23,12 +23,6 @@ useApp((addr, port) => { await super.init(); } - - - protected registerComponents() { - super.registerComponents(); - this.use(new CsrfProtectionComponent()); - } }(addr, port); }); diff --git a/test/_app.ts b/test/_app.ts index 51b9d38..bb29469 100644 --- a/test/_app.ts +++ b/test/_app.ts @@ -3,12 +3,12 @@ import {setupMailServer, teardownMailServer} from "./_mail_server"; import TestApp from "../src/TestApp"; -export default function useApp(appSupplier?: (addr: string, port: number) => TestApp): void { +export default function useApp(appSupplier?: (addr: string, port: number) => Promise): void { let app: Application; beforeAll(async (done) => { await setupMailServer(); - app = appSupplier ? appSupplier('127.0.0.1', 8966) : new TestApp('127.0.0.1', 8966); + app = appSupplier ? await appSupplier('127.0.0.1', 8966) : new TestApp('127.0.0.1', 8966); await app.start(); done(); diff --git a/test/_mail_server.ts b/test/_mail_server.ts index 9c914f6..b3aed48 100644 --- a/test/_mail_server.ts +++ b/test/_mail_server.ts @@ -1,4 +1,4 @@ -import MailDev from "maildev"; +import MailDev, {Mail} from "maildev"; export const MAIL_SERVER = new MailDev({ ip: 'localhost', @@ -17,3 +17,22 @@ export async function teardownMailServer(): Promise { else resolve(); })); } + +export async function popEmail(): Promise | null> { + return await new Promise | null>((resolve, reject) => { + MAIL_SERVER.getAllEmail((err: Error | undefined, emails: Mail[]) => { + if (err) return reject(err); + if (emails.length === 0) return resolve(null); + const email = emails[0]; + + expect(email).toBeDefined(); + expect(email.id).toBeDefined(); + return resolve(new Promise>((resolve, reject) => { + MAIL_SERVER.deleteEmail(email.id as string, (err: Error | undefined) => { + if (err) return reject(err); + resolve(email as Record); + }); + })); + }); + }); +}