Csrf protection test: migrate to supertest

This commit is contained in:
Alice Gaudon 2020-08-05 12:05:52 +02:00
parent 76811dd0b8
commit 84f2f7118a
6 changed files with 140 additions and 79 deletions

View File

@ -35,11 +35,13 @@
"@types/nunjucks": "^3.1.3",
"@types/on-finished": "^2.3.1",
"@types/redis": "^2.8.18",
"@types/supertest": "^2.0.10",
"@types/uuid": "^8.0.0",
"@types/ws": "^7.2.4",
"jest": "^26.1.0",
"maildev": "^1.1.0",
"node-fetch": "^2.6.0",
"supertest": "^4.0.2",
"ts-jest": "^26.1.1",
"typescript": "^3.8.3"
},

View File

@ -7,6 +7,7 @@ import compression from "compression";
export default class ExpressAppComponent extends ApplicationComponent<void> {
private readonly port: number;
private server?: Server;
private expressApp?: Express;
constructor(port: number) {
super();
@ -20,6 +21,8 @@ export default class ExpressAppComponent extends ApplicationComponent<void> {
// Proxy
app.set('trust proxy', 'loopback');
this.expressApp = app;
}
public async init(router: Router): Promise<void> {
@ -48,4 +51,8 @@ export default class ExpressAppComponent extends ApplicationComponent<void> {
if (!this.server) throw 'Server was not initialized.';
return this.server;
}
public getExpressApp(): Express {
return this.expressApp!;
}
}

View File

@ -1,10 +1,11 @@
import fetch, {Response} from "node-fetch";
import useApp, {DEFAULT_ADDR, TestApp} from "./_app";
import useApp, {TestApp} from "./_app";
import Controller from "../src/Controller";
import CsrfProtectionComponent from "../src/components/CsrfProtectionComponent";
import supertest from "supertest";
let app: TestApp;
useApp(port => {
return new class extends TestApp {
return app = new class extends TestApp {
protected async init(): Promise<void> {
this.use(new class extends Controller {
routes(): void {
@ -32,86 +33,68 @@ useApp(port => {
});
describe('Test CSRF protection', () => {
let res: Response;
let cookies: string[];
let csrf: string;
test('no csrf token should be in session at first', async () => {
res = await fetch(DEFAULT_ADDR, {
method: 'POST',
});
expect(res.status).toBe(401);
expect(await res.text()).toContain(`You weren't assigned any CSRF token.`);
test('no csrf token should be in session at first', (done) => {
const agent = supertest(app.getExpressApp());
agent.post('/')
.expect(401, (err, res) => {
if (err) return done(err);
expect(res.text).toContain(`You weren't assigned any CSRF token.`);
cookies = res.get('Set-Cookie');
agent.get('/')
.set('Cookie', cookies)
.expect(200, (err, res) => {
if (err) return done(err);
csrf = res.text;
done();
});
});
});
let cookies: string | null;
test('sending no csrf token should fail', async () => {
cookies = res.headers.get('set-cookie');
test('sending no csrf token should fail', (done) => {
expect(cookies).toBeDefined();
res = await fetch(`${DEFAULT_ADDR}${Controller.route('csrf_test')}`, {
headers: {
'Cookie': cookies!,
}
});
expect(res.ok).toBe(true);
const agent = supertest(app.getExpressApp());
agent.post('/')
.set('Cookie', cookies)
.expect(401)
.end((err, res) => {
if (err) return done(err);
res = await fetch(DEFAULT_ADDR, {
method: 'POST',
headers: {
'Cookie': cookies!,
}
});
expect(res.status).toBe(401);
expect(await res.text()).toContain(`You didn't provide any CSRF token.`);
expect(res.text).toContain(`You didn't provide any CSRF token.`);
done();
});
});
test('sending an invalid csrf token should fail', async () => {
cookies = res.headers.get('set-cookie');
test('sending an invalid csrf token should fail', (done) => {
expect(cookies).toBeDefined();
res = await fetch(`${DEFAULT_ADDR}${Controller.route('csrf_test')}`, {
headers: {
'Cookie': cookies!,
}
});
expect(res.ok).toBe(true);
const agent = supertest(app.getExpressApp());
agent.post('/')
.set('Cookie', cookies)
.set('Content-Type', 'application/json')
.send({csrf: 'not_a_valid_csrf'})
.expect(401, (err, res) => {
if (err) return done(err);
res = await fetch(DEFAULT_ADDR, {
method: 'POST',
body: JSON.stringify({
csrf: 'not_a_valid_csrf',
}),
headers: {
'Content-Type': 'application/json',
'Cookie': cookies!,
}
});
expect(res.status).toBe(401);
expect(await res.text()).toContain(`Tokens don't match.`);
expect(res.text).toContain(`Tokens don't match.`);
done();
});
});
test('sending a valid csrf token should success', async () => {
cookies = res.headers.get('set-cookie');
test('sending a valid csrf token should success', (done) => {
expect(cookies).toBeDefined();
res = await fetch(`${DEFAULT_ADDR}${Controller.route('csrf_test')}`, {
headers: {
'Cookie': cookies!,
}
});
expect(res.ok).toBe(true);
let csrf = await res.text();
res = await fetch(DEFAULT_ADDR, {
method: 'POST',
body: JSON.stringify({
csrf: csrf,
}),
headers: {
'Content-Type': 'application/json',
'Cookie': cookies!,
}
});
expect(res.ok).toBe(true);
const agent = supertest(app.getExpressApp());
agent.post('/')
.set('Cookie', cookies)
.set('Content-Type', 'application/json')
.send({csrf: csrf})
.expect(200, done);
});
});

View File

@ -2,7 +2,7 @@ import MysqlConnectionManager from "../src/db/MysqlConnectionManager";
import Model from "../src/db/Model";
import {MIGRATIONS} from "./_migrations";
import ModelFactory from "../src/db/ModelFactory";
import {ValidationBag, ValidationError} from "../src/db/Validator";
import {ValidationBag} from "../src/db/Validator";
class FakeDummyModel extends Model {
public id?: number = undefined;

View File

@ -16,6 +16,7 @@ import MagicLink from "../src/auth/models/MagicLink";
import FormHelperComponent from "../src/components/FormHelperComponent";
import RedirectBackComponent from "../src/components/RedirectBackComponent";
import ServeStaticDirectoryComponent from "../src/components/ServeStaticDirectoryComponent";
import {Express} from "express";
export default function useApp(appSupplier?: (port: number) => TestApp) {
let app: Application;
@ -37,6 +38,7 @@ export default function useApp(appSupplier?: (port: number) => TestApp) {
export class TestApp extends Application {
private readonly port: number;
private expressAppComponent?: ExpressAppComponent;
constructor(port: number) {
super(require('../package.json').version, true);
@ -54,12 +56,12 @@ export class TestApp extends Application {
}
protected registerComponents() {
const expressAppComponent = new ExpressAppComponent(this.port);
this.expressAppComponent = new ExpressAppComponent(this.port);
const redisComponent = new RedisComponent();
const mysqlComponent = new MysqlComponent();
// Base
this.use(expressAppComponent);
this.use(this.expressAppComponent);
this.use(new LogRequestsComponent());
// Static files
@ -93,6 +95,10 @@ export class TestApp extends Application {
protected registerControllers() {
}
public getExpressApp(): Express {
return this.expressAppComponent!.getExpressApp();
}
}
export const DEFAULT_ADDR = 'http://localhost:8966';

View File

@ -585,6 +585,11 @@
resolved "https://registry.toot.party/@types%2fcookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108"
integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==
"@types/cookiejar@*":
version "2.1.1"
resolved "https://registry.toot.party/@types%2fcookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80"
integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==
"@types/events@*":
version "3.0.0"
resolved "https://registry.toot.party/@types%2fevents/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@ -761,6 +766,21 @@
resolved "https://registry.toot.party/@types%2fstack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
"@types/superagent@*":
version "4.1.8"
resolved "https://registry.toot.party/@types%2fsuperagent/-/superagent-4.1.8.tgz#f663dcdd24705e07fce055003ace9b824f2a51c9"
integrity sha512-iol9KxQ7SLHatBJUiZ4uABrS4VS1frLjqPednxZz82eoCzo3Uy3TOH0p0ZIBbfBj8E/xqOtvizjBs9h7xi/l2g==
dependencies:
"@types/cookiejar" "*"
"@types/node" "*"
"@types/supertest@^2.0.10":
version "2.0.10"
resolved "https://registry.toot.party/@types%2fsupertest/-/supertest-2.0.10.tgz#630d79b4d82c73e043e43ff777a9ca98d457cab7"
integrity sha512-Xt8TbEyZTnD5Xulw95GLMOkmjGICrOQyJ2jqgkSjAUR3mm7pAIzSR0NFBaMcwlzVvlpCjNwbATcWWwjNiZiFrQ==
dependencies:
"@types/superagent" "*"
"@types/uuid@^8.0.0":
version "8.0.0"
resolved "https://registry.toot.party/@types%2fuuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0"
@ -1509,7 +1529,7 @@ component-emitter@1.2.1:
resolved "https://registry.toot.party/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
component-emitter@^1.2.1:
component-emitter@^1.2.0, component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.toot.party/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
@ -1621,6 +1641,11 @@ cookie@^0.4.1:
resolved "https://registry.toot.party/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
cookiejar@^2.1.0:
version "2.1.2"
resolved "https://registry.toot.party/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.toot.party/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
@ -1727,7 +1752,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
debug@^3.2.6:
debug@^3.1.0, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.toot.party/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@ -2204,7 +2229,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@~3.0.0, extend@~3.0.2:
extend@^3.0.0, extend@~3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.toot.party/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@ -2317,6 +2342,15 @@ forever-agent@~0.6.1:
resolved "https://registry.toot.party/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^2.3.1:
version "2.5.1"
resolved "https://registry.toot.party/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.0"
resolved "https://registry.toot.party/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
@ -2335,7 +2369,7 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formidable@^1.2.2:
formidable@^1.2.0, formidable@^1.2.2:
version "1.2.2"
resolved "https://registry.toot.party/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
@ -3715,7 +3749,7 @@ merge-stream@^2.0.0:
resolved "https://registry.toot.party/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
methods@~1.1.2:
methods@^1.1.1, methods@^1.1.2, methods@~1.1.2:
version "1.1.2"
resolved "https://registry.toot.party/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
@ -3759,7 +3793,7 @@ mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
dependencies:
mime-db "1.44.0"
mime@1.6.0, mime@^1.6.0:
mime@1.6.0, mime@^1.4.1, mime@^1.6.0:
version "1.6.0"
resolved "https://registry.toot.party/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
@ -4745,6 +4779,11 @@ qs@6.7.0:
resolved "https://registry.toot.party/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@^6.5.1:
version "6.9.4"
resolved "https://registry.toot.party/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687"
integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.toot.party/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@ -4804,7 +4843,7 @@ read-pkg@^5.2.0:
parse-json "^5.0.0"
type-fest "^0.6.0"
readable-stream@2.3.7, readable-stream@^2.0.6:
readable-stream@2.3.7, readable-stream@^2.0.6, readable-stream@^2.3.5:
version "2.3.7"
resolved "https://registry.toot.party/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@ -5494,6 +5533,30 @@ strip-json-comments@~2.0.1:
resolved "https://registry.toot.party/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
superagent@^3.8.3:
version "3.8.3"
resolved "https://registry.toot.party/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128"
integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==
dependencies:
component-emitter "^1.2.0"
cookiejar "^2.1.0"
debug "^3.1.0"
extend "^3.0.0"
form-data "^2.3.1"
formidable "^1.2.0"
methods "^1.1.1"
mime "^1.4.1"
qs "^6.5.1"
readable-stream "^2.3.5"
supertest@^4.0.2:
version "4.0.2"
resolved "https://registry.toot.party/supertest/-/supertest-4.0.2.tgz#c2234dbdd6dc79b6f15b99c8d6577b90e4ce3f36"
integrity sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==
dependencies:
methods "^1.1.2"
superagent "^3.8.3"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.toot.party/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"