Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
/**
|
|
* 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("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 };
|
|
}
|
|
}
|