feat(security): add CSRF protection for all state-changing API routes
Build & Push Docker Image / build (push) Successful in 2m16s
Build & Push Docker Image / build (push) Successful in 2m16s
- Synchronizer token stored in session; GET /auth/csrf to obtain it - csrfProtection middleware requires X-CSRF-Token on non-safe methods - customFetch injects the header via setCsrfTokenGetter - toolrate boot loads token; reload after local login (session regenerate) - OpenAPI GET /auth/csrf + CsrfToken schema, orval regenerated
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { Switch, Route, Router as WouterRouter } from "wouter";
|
||||
import { useEffect } from "react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import i18n from "@/i18n";
|
||||
import { loadCsrfToken } from "@/lib/csrf";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
|
||||
@@ -50,6 +52,9 @@ function Router() {
|
||||
}
|
||||
|
||||
function App() {
|
||||
useEffect(() => {
|
||||
void loadCsrfToken();
|
||||
}, []);
|
||||
return (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { setCsrfTokenGetter } from "@workspace/api-client-react";
|
||||
|
||||
let token: string | null = null;
|
||||
|
||||
setCsrfTokenGetter(() => token);
|
||||
|
||||
export function getCsrfToken(): string | null {
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function loadCsrfToken(): Promise<string | null> {
|
||||
try {
|
||||
const res = await fetch(`/api/auth/csrf`, {
|
||||
credentials: "include",
|
||||
});
|
||||
if (!res.ok) {
|
||||
token = null;
|
||||
return null;
|
||||
}
|
||||
const data = (await res.json()) as { token?: string };
|
||||
token = data.token ?? null;
|
||||
return token;
|
||||
} catch {
|
||||
token = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { PasswordInput } from "@/components/password-input";
|
||||
import { loadCsrfToken } from "@/lib/csrf";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { Wrench, AlertCircle } from "lucide-react";
|
||||
@@ -33,6 +34,7 @@ export default function Login() {
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries();
|
||||
void loadCsrfToken();
|
||||
setLocation(returnTo);
|
||||
},
|
||||
onError: (err) => {
|
||||
|
||||
Reference in New Issue
Block a user