fix: Complete CSRF protection implementation

Closes three CSRF security gaps identified in code review:

1. Added X-CSRF-Token and X-Workspace-Id to CORS allowed headers
   - Updated apps/api/src/main.ts to accept CSRF token headers

2. Integrated CSRF token handling in web client
   - Added fetchCsrfToken() to fetch token from API
   - Store token in memory (not localStorage for security)
   - Automatically include X-CSRF-Token in POST/PUT/PATCH/DELETE
   - Implement automatic token refresh on 403 CSRF errors
   - Added comprehensive test coverage for CSRF functionality

3. Applied CSRF Guard globally
   - Added CsrfGuard as APP_GUARD in app.module.ts
   - Verified @SkipCsrf() decorator works for exempted endpoints

All tests passing. CSRF protection now enforced application-wide.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit is contained in:
2026-02-04 07:12:42 -06:00
co-authored by Claude Sonnet 4.5
parent 41f1dc48ed
commit 3a98b78661
4 changed files with 434 additions and 5 deletions
+5
View File
@@ -3,6 +3,7 @@ import { APP_INTERCEPTOR, APP_GUARD } from "@nestjs/core";
import { ThrottlerModule } from "@nestjs/throttler";
import { BullModule } from "@nestjs/bullmq";
import { ThrottlerValkeyStorageService, ThrottlerApiKeyGuard } from "./common/throttler";
import { CsrfGuard } from "./common/guards/csrf.guard";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { CsrfController } from "./common/controllers/csrf.controller";
@@ -99,6 +100,10 @@ import { FederationModule } from "./federation/federation.module";
provide: APP_GUARD,
useClass: ThrottlerApiKeyGuard,
},
{
provide: APP_GUARD,
useClass: CsrfGuard,
},
],
})
export class AppModule {}
+1 -1
View File
@@ -75,7 +75,7 @@ async function bootstrap() {
},
credentials: true, // Required for cookie-based authentication
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "Cookie"],
allowedHeaders: ["Content-Type", "Authorization", "Cookie", "X-CSRF-Token", "X-Workspace-Id"],
exposedHeaders: ["Set-Cookie"],
maxAge: 86400, // 24 hours - cache preflight requests
});