feat(csrf): add decorator to exclude routes from CSRF protection

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson
2026-04-13 15:02:17 +02:00
parent 1eb9ee2385
commit 2d80f3b045
2 changed files with 30 additions and 0 deletions
@@ -4,16 +4,30 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import type { FastifyRequest, FastifyReply } from 'fastify';
import { CSRF_EXEMPT_KEY } from '../../utils/decorators/csrf-exempt.decorator';
const UNPROTECTED_METHODS = ['GET', 'HEAD', 'OPTIONS'];
@Injectable()
export class CsrfGuard implements CanActivate {
constructor(private reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<FastifyRequest>();
const reply = context.switchToHttp().getResponse<FastifyReply>();
// Ignore if the @CsrfExempt() decorator is set for the route
const isCsrfExempt = this.reflector.getAllAndOverride<boolean>(CSRF_EXEMPT_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isCsrfExempt) {
return true;
}
// Ignore unprotected methods (GET, HEAD, OPTIONS)
const method = request.method.toUpperCase();
if (UNPROTECTED_METHODS.includes(method)) {
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2026 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { SetMetadata } from '@nestjs/common';
export const CSRF_EXEMPT_KEY = 'csrf_exempt';
/**
* Decorator to mark a route as exempt from CSRF protection.
* Routes with this decorator won't be protected by CSRF protection. This is required for non-public-API endpoints
* called from non-browsers, e.g. OIDC backchannel-logout.
*/
export const CsrfExempt = () => SetMetadata(CSRF_EXEMPT_KEY, true);