Files
livedash-node/components/providers/CSRFProvider.tsx
Kaj Kowalski e1abedb148 feat: implement cache layer, CSP improvements, and database performance optimizations
- Add Redis cache implementation with LRU eviction
- Enhance Content Security Policy with nonce generation
- Optimize database queries with connection pooling
- Add cache invalidation API endpoints
- Improve security monitoring performance
2025-07-13 11:52:49 +02:00

162 lines
3.8 KiB
TypeScript

/**
* CSRF Provider Component
*
* Provides CSRF token management for the entire application.
* Automatically fetches and manages CSRF tokens for client-side requests.
*/
"use client";
import type React from "react";
import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { CSRFClient } from "../../lib/csrf-client";
interface CSRFContextType {
token: string | null;
loading: boolean;
error: string | null;
refreshToken: () => Promise<void>;
addTokenToFetch: (_options: RequestInit) => RequestInit;
addTokenToFormData: (_formData: FormData) => FormData;
addTokenToObject: <T extends Record<string, unknown>>(
_obj: T
) => T & { csrfToken: string };
}
const CSRFContext = createContext<CSRFContextType | undefined>(undefined);
interface CSRFProviderProps {
children: React.ReactNode;
}
/**
* CSRF Provider Component
*/
export function CSRFProvider({ children }: CSRFProviderProps) {
const [token, setToken] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
/**
* Fetch CSRF token from server
*/
const fetchToken = useCallback(async () => {
try {
setLoading(true);
setError(null);
// First check if we already have a token in cookies
const existingToken = CSRFClient.getToken();
if (existingToken) {
setToken(existingToken);
setLoading(false);
return;
}
// Fetch new token from server
const response = await fetch("/api/csrf-token", {
method: "GET",
credentials: "include",
});
if (!response.ok) {
throw new Error(`Failed to fetch CSRF token: ${response.status}`);
}
const data = await response.json();
if (data.success && data.token) {
setToken(data.token);
} else {
throw new Error("Invalid response from CSRF endpoint");
}
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : "Failed to fetch CSRF token";
setError(errorMessage);
console.error("CSRF token fetch error:", errorMessage);
} finally {
setLoading(false);
}
}, []);
/**
* Refresh token manually
*/
const refreshToken = async () => {
await fetchToken();
};
/**
* Initialize token on mount
*/
useEffect(() => {
fetchToken();
}, [fetchToken]);
/**
* Monitor token changes in cookies
*/
useEffect(() => {
const checkToken = () => {
const currentToken = CSRFClient.getToken();
if (currentToken !== token) {
setToken(currentToken);
}
};
// Check token every 30 seconds
const interval = setInterval(checkToken, 30 * 1000);
return () => clearInterval(interval);
}, [token]);
const contextValue: CSRFContextType = {
token,
loading,
error,
refreshToken,
addTokenToFetch: CSRFClient.addTokenToFetch,
addTokenToFormData: CSRFClient.addTokenToFormData,
addTokenToObject: CSRFClient.addTokenToObject,
};
return (
<CSRFContext.Provider value={contextValue}>{children}</CSRFContext.Provider>
);
}
/**
* Hook to use CSRF context
*/
export function useCSRFContext(): CSRFContextType {
const context = useContext(CSRFContext);
if (context === undefined) {
throw new Error("useCSRFContext must be used within a CSRFProvider");
}
return context;
}
/**
* Higher-order component to wrap components with CSRF protection
*/
export function withCSRF<P extends object>(Component: React.ComponentType<P>) {
const WrappedComponent = (props: P) => (
<CSRFProvider>
<Component {...props} />
</CSRFProvider>
);
WrappedComponent.displayName = `withCSRF(${Component.displayName || Component.name})`;
return WrappedComponent;
}