import ApplicationComponent from "../ApplicationComponent"; import {Express, Router} from "express"; import crypto from "crypto"; import {BadRequestError} from "../HttpError"; export default class CsrfProtectionComponent extends ApplicationComponent { private static readonly routeExcluders: ((path: string) => boolean)[] = []; public static addRouteExcluder(excluder: (path: string) => boolean) { this.routeExcluders.push(excluder); } public async start(app: Express, router: Router): Promise { router.use(async (req, res, next) => { for (const excluder of CsrfProtectionComponent.routeExcluders) { if (excluder(req.path)) return next(); } if (!req.session) { throw new Error('Session is unavailable.'); } res.locals.getCSRFToken = () => { if (typeof req.session!.csrf !== 'string') { req.session!.csrf = crypto.randomBytes(64).toString('base64'); } return req.session!.csrf; }; if (!['GET', 'HEAD', 'OPTIONS'].find(s => s === req.method)) { try { if (!(await req.authGuard.isAuthenticatedViaRequest(req))) { if (req.session.csrf === undefined) { throw new InvalidCsrfTokenError(req.baseUrl, `You weren't assigned any CSRF token.`); } else if (req.body.csrf === undefined) { throw new InvalidCsrfTokenError(req.baseUrl, `You didn't provide any CSRF token.`); } else if (req.session.csrf !== req.body.csrf) { throw new InvalidCsrfTokenError(req.baseUrl, `Tokens don't match.`); } } } catch (e) { next(e); return; } } next(); }); } public async stop(): Promise { } } class InvalidCsrfTokenError extends BadRequestError { constructor(url: string, details: string, cause?: Error) { super( `Invalid CSRF token`, `${details} We can't process this request. Please try again.`, url, cause ); } get name(): string { return 'Invalid CSRF Token'; } get errorCode(): number { return 401; } }