diff --git a/client/index.html b/client/index.html index b895e24..1351ca3 100644 --- a/client/index.html +++ b/client/index.html @@ -3,7 +3,6 @@ - NeuraMemory AI diff --git a/client/src/App.tsx b/client/src/App.tsx index 9188900..6bc8186 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -21,7 +21,13 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) { .catch(() => setStatus('unauth')); }, []); - if (status === 'loading') return null; + if (status === 'loading') { + return ( +
+
+
+ ); + } if (status === 'unauth') return ; return <>{children}; } diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index b10b8fc..7d6df25 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -4,3 +4,20 @@ export const api = axios.create({ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000', withCredentials: true, }); + +// Automatically attach the CSRF token from cookie to state-changing requests. +// The server sets `csrf_token` cookie on first load (readable by JS since httpOnly=false). +api.interceptors.request.use((config) => { + const method = config.method?.toUpperCase(); + const isStateChanging = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(method ?? ''); + if (isStateChanging) { + const csrfToken = document.cookie + .split('; ') + .find((row) => row.startsWith('csrf_token=')) + ?.split('=')?.[1]; + if (csrfToken) { + config.headers['x-csrf-token'] = csrfToken; + } + } + return config; +}); diff --git a/server/src/index.ts b/server/src/index.ts index 9e27e8f..f579fd2 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -43,7 +43,7 @@ app.use( }, credentials: true, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization', 'x-api-key', 'mcp-session-id'], + allowedHeaders: ['Content-Type', 'Authorization', 'x-api-key', 'mcp-session-id', 'x-csrf-token'], }), );