Authentication tests: add no username component tests

Closes #7
This commit is contained in:
Alice Gaudon 2020-11-16 12:08:39 +01:00
parent 01277ea910
commit 7be3e00c46
5 changed files with 234 additions and 64 deletions

View File

@ -3,20 +3,15 @@ import useApp from "./_app";
import Controller from "../src/Controller"; import Controller from "../src/Controller";
import supertest from "supertest"; import supertest from "supertest";
import CsrfProtectionComponent from "../src/components/CsrfProtectionComponent"; import CsrfProtectionComponent from "../src/components/CsrfProtectionComponent";
import MysqlConnectionManager from "../src/db/MysqlConnectionManager";
import config from "config";
import User from "../src/auth/models/User"; import User from "../src/auth/models/User";
import UserNameComponent from "../src/auth/models/UserNameComponent"; import UserNameComponent from "../src/auth/models/UserNameComponent";
import UserPasswordComponent from "../src/auth/password/UserPasswordComponent"; import UserPasswordComponent from "../src/auth/password/UserPasswordComponent";
import {popEmail} from "./_mail_server"; import {popEmail} from "./_mail_server";
import AuthComponent from "../src/auth/AuthComponent"; import AuthComponent from "../src/auth/AuthComponent";
import {followMagicLinkFromMail, testLogout} from "./_authentication_common";
let app: TestApp; let app: TestApp;
useApp(async (addr, port) => { useApp(async (addr, port) => {
await MysqlConnectionManager.prepare();
await MysqlConnectionManager.query('DROP DATABASE IF EXISTS ' + config.get<string>('mysql.database'));
await MysqlConnectionManager.endPool();
return app = new class extends TestApp { return app = new class extends TestApp {
protected async init(): Promise<void> { protected async init(): Promise<void> {
this.use(new class extends Controller { this.use(new class extends Controller {
@ -40,35 +35,6 @@ useApp(async (addr, port) => {
}(addr, port); }(addr, port);
}); });
async function followMagicLinkFromMail(cookies: string[]): Promise<void> {
const mail: Record<string, unknown> | 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<void> {
// 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<supertest.Test>; let agent: supertest.SuperTest<supertest.Test>;
beforeAll(() => { beforeAll(() => {
@ -98,7 +64,6 @@ describe('Register with username and password (password)', () => {
.expect(302) .expect(302)
.expect('Location', '/'); .expect('Location', '/');
// Verify saved user // Verify saved user
const user = await User.select() const user = await User.select()
.where('name', 'entrapta') .where('name', 'entrapta')
@ -129,6 +94,10 @@ describe('Register with username and password (password)', () => {
expect(user2).toBeNull(); expect(user2).toBeNull();
}); });
test('Logout', async () => {
await testLogout(agent, cookies, csrf);
});
test('Cannot register taken username', async () => { test('Cannot register taken username', async () => {
// Check that there is no hordak in DB // Check that there is no hordak in DB
expect(await User.select() expect(await User.select()
@ -136,12 +105,14 @@ describe('Register with username and password (password)', () => {
.count()).toStrictEqual(0); .count()).toStrictEqual(0);
const res1 = await agent.get('/csrf').expect(200); const res1 = await agent.get('/csrf').expect(200);
const cookies = res1.get('Set-Cookie');
const csrf = res1.text;
// Register user // Register user
await agent.post('/auth/register') await agent.post('/auth/register')
.set('Cookie', res1.get('Set-Cookie')) .set('Cookie', cookies)
.send({ .send({
csrf: res1.text, csrf: csrf,
auth_method: 'password', auth_method: 'password',
identifier: 'hordak', identifier: 'hordak',
password: 'horde_prime_will_rise', password: 'horde_prime_will_rise',
@ -150,6 +121,7 @@ describe('Register with username and password (password)', () => {
}) })
.expect(302) .expect(302)
.expect('Location', '/'); .expect('Location', '/');
await testLogout(agent, cookies, csrf);
// Verify saved user // Verify saved user
expect(await User.select() expect(await User.select()
@ -157,13 +129,11 @@ describe('Register with username and password (password)', () => {
.count()).toStrictEqual(1); .count()).toStrictEqual(1);
const res2 = await agent.get('/csrf').expect(200);
// Attempt register same user // Attempt register same user
const res = await agent.post('/auth/register') const res = await agent.post('/auth/register')
.set('Cookie', res2.get('Set-Cookie')) .set('Cookie', cookies)
.send({ .send({
csrf: res2.text, csrf: csrf,
auth_method: 'password', auth_method: 'password',
identifier: 'hordak', identifier: 'hordak',
password: 'horde_prime_will_rise_unless', password: 'horde_prime_will_rise_unless',
@ -198,7 +168,9 @@ describe('Register with email (magic_link)', () => {
.expect(302) .expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf'); .expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf');
await followMagicLinkFromMail(cookies); await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
// Verify saved user // Verify saved user
const user = await User.select() const user = await User.select()
@ -250,23 +222,23 @@ describe('Register with email (magic_link)', () => {
.expect(302) .expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf'); .expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf');
await followMagicLinkFromMail(cookies); await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
// Verify saved user // Verify saved user
const user = await User.select() const user = await User.select()
.with('mainEmail') .with('mainEmail')
.where('name', 'glimmer') .where('name', 'angella')
.first(); .first();
expect(user).toBeDefined(); expect(user).toBeDefined();
// Attempt register with another mail but same username // Attempt register with another mail but same username
const res2 = await agent.get('/csrf').expect(200);
await agent.post('/auth/register') await agent.post('/auth/register')
.set('Cookie', res2.get('Set-Cookie')) .set('Cookie', cookies)
.send({ .send({
csrf: res2.text, csrf: csrf,
auth_method: 'magic_link', auth_method: 'magic_link',
identifier: 'angella_something_else@example.org', identifier: 'angella_something_else@example.org',
name: 'angella', name: 'angella',
@ -292,23 +264,23 @@ describe('Register with email (magic_link)', () => {
.expect(302) .expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf'); .expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf');
await followMagicLinkFromMail(cookies); await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
// Verify saved user // Verify saved user
const user = await User.select() const user = await User.select()
.with('mainEmail') .with('mainEmail')
.where('name', 'glimmer') .where('name', 'bow')
.first(); .first();
expect(user).toBeDefined(); expect(user).toBeDefined();
// Attempt register with another mail but same username // Attempt register with another mail but same username
const res2 = await agent.get('/csrf').expect(200);
await agent.post('/auth/register') await agent.post('/auth/register')
.set('Cookie', res2.get('Set-Cookie')) .set('Cookie', cookies)
.send({ .send({
csrf: res2.text, csrf: csrf,
auth_method: 'magic_link', auth_method: 'magic_link',
identifier: 'bow@example.org', identifier: 'bow@example.org',
name: 'bow2', name: 'bow2',
@ -352,7 +324,7 @@ describe('Authenticate with username and password (password)', () => {
.expect(302) .expect(302)
.expect('Location', '/'); .expect('Location', '/');
await testLogout(cookies, csrf); await testLogout(agent, cookies, csrf);
}); });
test('Automatic auth_method', async () => { test('Automatic auth_method', async () => {
@ -385,7 +357,7 @@ describe('Authenticate with username and password (password)', () => {
.expect(302) .expect(302)
.expect('Location', '/'); .expect('Location', '/');
await testLogout(cookies, csrf); await testLogout(agent, cookies, csrf);
}); });
test('Non-existing username', async () => { test('Non-existing username', async () => {
@ -495,9 +467,9 @@ describe('Authenticate with email (magic_link)', () => {
.expect(302) .expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf'); .expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf');
await followMagicLinkFromMail(cookies); await followMagicLinkFromMail(agent, cookies);
await testLogout(cookies, csrf); await testLogout(agent, cookies, csrf);
}); });
test('Automatic auth_method', async () => { test('Automatic auth_method', async () => {
@ -518,9 +490,9 @@ describe('Authenticate with email (magic_link)', () => {
.expect(302) .expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf'); .expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf');
await followMagicLinkFromMail(cookies); await followMagicLinkFromMail(agent, cookies);
await testLogout(cookies, csrf); await testLogout(agent, cookies, csrf);
}); });
test('Non-existing email (forced auth_method)', async () => { test('Non-existing email (forced auth_method)', async () => {
@ -582,7 +554,9 @@ describe('Authenticate with email and password (password)', () => {
.expect(302) .expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf'); .expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf');
await followMagicLinkFromMail(cookies); await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
// Verify saved user // Verify saved user
const user = await User.select() const user = await User.select()
@ -635,7 +609,7 @@ describe('Authenticate with email and password (password)', () => {
.expect(302) .expect(302)
.expect('Location', '/'); .expect('Location', '/');
await testLogout(cookies, csrf); await testLogout(agent, cookies, csrf);
}); });
test('Automatic auth_method', async () => { test('Automatic auth_method', async () => {
@ -668,6 +642,6 @@ describe('Authenticate with email and password (password)', () => {
.expect(302) .expect(302)
.expect('Location', '/'); .expect('Location', '/');
await testLogout(cookies, csrf); await testLogout(agent, cookies, csrf);
}); });
}); });

View File

@ -0,0 +1,147 @@
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 UserPasswordComponent from "../src/auth/password/UserPasswordComponent";
import {popEmail} from "./_mail_server";
import AuthComponent from "../src/auth/AuthComponent";
import Migration, {MigrationType} from "../src/db/Migration";
import AddNameToUsersMigration from "../src/auth/migrations/AddNameToUsersMigration";
import {followMagicLinkFromMail, testLogout} from "./_authentication_common";
import UserEmail from "../src/auth/models/UserEmail";
let app: TestApp;
useApp(async (addr, port) => {
return app = new class extends TestApp {
protected async init(): Promise<void> {
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();
}
protected getMigrations(): MigrationType<Migration>[] {
return super.getMigrations().filter(m => m !== AddNameToUsersMigration);
}
}(addr, port);
});
let agent: supertest.SuperTest<supertest.Test>;
beforeAll(() => {
agent = supertest(app.getExpressApp());
});
describe('Register with username and password (password)', () => {
test('Must be disabled', async () => {
const res = await agent.get('/csrf').expect(200);
const cookies = res.get('Set-Cookie');
const 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(500);
});
});
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',
})
.expect(302)
.expect('Location', '/magic/lobby?redirect_uri=%2Fcsrf');
await followMagicLinkFromMail(agent, cookies);
await testLogout(agent, cookies, csrf);
// Verify saved user
const email = await UserEmail.select()
.with('user')
.where('email', 'glimmer@example.org')
.first();
const user = email?.user.getOrFail();
expect(user).toBeDefined();
expect(email).toBeDefined();
expect(email?.email).toStrictEqual('glimmer@example.org');
await expect(user?.as(UserPasswordComponent).verifyPassword('')).resolves.toStrictEqual(false);
});
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);
// Verify saved user
const userEmail = await UserEmail.select()
.with('user')
.where('email', 'bow@example.org')
.first();
const user = userEmail?.user.getOrFail();
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();
});
});

View File

@ -5,6 +5,7 @@ import {ValidationBag} from "../src/db/Validator";
import {log} from "../src/Logger"; import {log} from "../src/Logger";
import {ManyThroughModelRelation, OneModelRelation} from "../src/db/ModelRelation"; import {ManyThroughModelRelation, OneModelRelation} from "../src/db/ModelRelation";
import {MIGRATIONS} from "../src/TestApp"; import {MIGRATIONS} from "../src/TestApp";
import config from "config";
class FakeDummyModel extends Model { class FakeDummyModel extends Model {
public id?: number = undefined; public id?: number = undefined;
@ -99,6 +100,10 @@ let roleFactory: ModelFactory<Role>;
let permissionFactory: ModelFactory<Permission>; let permissionFactory: ModelFactory<Permission>;
beforeAll(async () => { beforeAll(async () => {
await MysqlConnectionManager.prepare();
await MysqlConnectionManager.query('DROP DATABASE IF EXISTS ' + config.get<string>('mysql.database'));
await MysqlConnectionManager.endPool();
log.setSettings({minLevel: "trace"}); log.setSettings({minLevel: "trace"});
MysqlConnectionManager.registerMigrations(MIGRATIONS); MysqlConnectionManager.registerMigrations(MIGRATIONS);
ModelFactory.register(FakeDummyModel); ModelFactory.register(FakeDummyModel);

View File

@ -1,12 +1,18 @@
import Application from "../src/Application"; import Application from "../src/Application";
import {setupMailServer, teardownMailServer} from "./_mail_server"; import {setupMailServer, teardownMailServer} from "./_mail_server";
import TestApp from "../src/TestApp"; import TestApp from "../src/TestApp";
import MysqlConnectionManager from "../src/db/MysqlConnectionManager";
import config from "config";
export default function useApp(appSupplier?: (addr: string, port: number) => Promise<TestApp>): void { export default function useApp(appSupplier?: (addr: string, port: number) => Promise<TestApp>): void {
let app: Application; let app: Application;
beforeAll(async (done) => { beforeAll(async (done) => {
await MysqlConnectionManager.prepare();
await MysqlConnectionManager.query('DROP DATABASE IF EXISTS ' + config.get<string>('mysql.database'));
await MysqlConnectionManager.endPool();
await setupMailServer(); await setupMailServer();
app = appSupplier ? await 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);

View File

@ -0,0 +1,38 @@
import {popEmail} from "./_mail_server";
import supertest from "supertest";
export async function followMagicLinkFromMail(
agent: supertest.SuperTest<supertest.Test>,
cookies: string[],
): Promise<void> {
const mail: Record<string, unknown> | 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', '/');
}
export async function testLogout(
agent: supertest.SuperTest<supertest.Test>,
cookies: string[],
csrf: string,
): Promise<void> {
// 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);
}