2020-04-22 15:52:17 +02:00
|
|
|
import ApplicationComponent from "../ApplicationComponent";
|
|
|
|
import {Express, Router} from "express";
|
|
|
|
import crypto from "crypto";
|
|
|
|
import {BadRequestError} from "../HttpError";
|
|
|
|
|
|
|
|
export default class CsrfProtectionComponent extends ApplicationComponent<void> {
|
|
|
|
public async start(app: Express, router: Router): Promise<void> {
|
|
|
|
router.use((req, res, 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;
|
|
|
|
};
|
|
|
|
|
2020-06-14 21:47:18 +02:00
|
|
|
if (!['GET', 'HEAD', 'OPTIONS'].find(s => s === req.method) && !req.authGuard.isAuthenticatedViaRequest(req)) {
|
2020-04-22 15:52:17 +02:00
|
|
|
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.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public async stop(): Promise<void> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|