/** * CSRF Controller * * Provides CSRF token generation endpoint for client applications. * Tokens are cryptographically bound to the user session via HMAC. */ import { Controller, Get, Res, Req, UseGuards } from "@nestjs/common"; import { Response, Request } from "express"; import { SkipCsrf } from "../decorators/skip-csrf.decorator"; import { CsrfService } from "../services/csrf.service"; import { AuthGuard } from "../../auth/guards/auth.guard"; import type { AuthenticatedUser } from "../types/user.types"; interface AuthenticatedRequest extends Request { user?: AuthenticatedUser; } @Controller("api/v1/csrf") export class CsrfController { constructor(private readonly csrfService: CsrfService) {} /** * Generate and set CSRF token bound to user session * Requires authentication to bind token to session * Returns token to client and sets it in httpOnly cookie */ @Get("token") @UseGuards(AuthGuard) @SkipCsrf() // This endpoint itself doesn't need CSRF protection getCsrfToken( @Req() request: AuthenticatedRequest, @Res({ passthrough: true }) response: Response ): { token: string } { // Get user ID from authenticated request const userId = request.user?.id; if (!userId) { // This should not happen if AuthGuard is working correctly throw new Error("User ID not available after authentication"); } // Generate session-bound CSRF token const token = this.csrfService.generateToken(userId); // Set token in httpOnly cookie response.cookie("csrf-token", token, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "strict", maxAge: 24 * 60 * 60 * 1000, // 24 hours }); // Return token to client (so it can include in X-CSRF-Token header) return { token }; } }