mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 08:52:10 +01:00
CSRF Form Enhancements: - Add optional onError callback prop for better error handling - Remove CSRF token from console logging for security - Provide user-friendly error notifications instead of silent failures Date Filter Optimization: - Refactor sessions route to avoid object mutation issues - Build date filters cleanly without relying on spreading existing objects - Prevent potential undefined startTime mutations Geographic Threat Map Optimization: - Extract country names to reusable constants in lib/constants/countries.ts - Calculate max values once to avoid repeated expensive operations - Centralize threat level color mapping to eliminate duplicated logic - Replace repeated color assignments with centralized THREAT_LEVELS configuration Accessibility Improvements: - Add keyboard support to audit log table rows (Enter/Space keys) - Include proper ARIA labels and focus management - Add tabIndex for screen reader compatibility - Enhance focus indicators with ring styling Performance & Code Organization: - Move COUNTRY_NAMES to shared constants for reusability - Optimize calculation patterns in threat mapping components - Reduce redundant logic and improve maintainability
180 lines
5.0 KiB
TypeScript
180 lines
5.0 KiB
TypeScript
/**
|
|
* CSRF Protected Form Component
|
|
*
|
|
* A wrapper component that automatically adds CSRF protection to forms.
|
|
* This component demonstrates how to integrate CSRF tokens into form submissions.
|
|
*/
|
|
|
|
"use client";
|
|
|
|
import type { FormEvent, ReactNode } from "react";
|
|
import { useId } from "react";
|
|
import { useCSRFForm } from "../../lib/hooks/useCSRF";
|
|
|
|
interface CSRFProtectedFormProps {
|
|
children: ReactNode;
|
|
action: string;
|
|
method?: "POST" | "PUT" | "DELETE" | "PATCH";
|
|
onSubmit?: (formData: FormData) => Promise<void> | void;
|
|
onError?: (error: Error) => void;
|
|
className?: string;
|
|
encType?: string;
|
|
}
|
|
|
|
/**
|
|
* Form component with automatic CSRF protection
|
|
*/
|
|
export function CSRFProtectedForm({
|
|
children,
|
|
action,
|
|
method = "POST",
|
|
onSubmit,
|
|
onError,
|
|
className,
|
|
encType,
|
|
}: CSRFProtectedFormProps) {
|
|
const { token, submitForm, addTokenToFormData } = useCSRFForm();
|
|
|
|
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
|
|
const form = event.currentTarget;
|
|
const formData = new FormData(form);
|
|
|
|
// Add CSRF token to form data
|
|
addTokenToFormData(formData);
|
|
|
|
try {
|
|
if (onSubmit) {
|
|
// Use custom submit handler
|
|
await onSubmit(formData);
|
|
} else {
|
|
// Use default form submission with CSRF protection
|
|
const response = await submitForm(action, formData);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Form submission failed: ${response.status}`);
|
|
}
|
|
|
|
// Handle successful submission
|
|
console.log("Form submitted successfully");
|
|
}
|
|
} catch (error) {
|
|
console.error("Form submission error:", error);
|
|
|
|
// Notify user of the error
|
|
if (onError && error instanceof Error) {
|
|
onError(error);
|
|
} else {
|
|
// Fallback: show alert if no error handler provided
|
|
alert("An error occurred while submitting the form. Please try again.");
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form
|
|
onSubmit={handleSubmit}
|
|
method={method}
|
|
action={action}
|
|
className={className}
|
|
encType={encType}
|
|
>
|
|
{/* Hidden CSRF token field for non-JS fallback */}
|
|
{token && <input type="hidden" name="csrf_token" value={token} />}
|
|
|
|
{children}
|
|
</form>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Example usage component showing how to use CSRF protected forms
|
|
*/
|
|
export function ExampleCSRFForm() {
|
|
// Generate unique IDs for form elements
|
|
const nameId = useId();
|
|
const emailId = useId();
|
|
const messageId = useId();
|
|
|
|
const handleCustomSubmit = async (formData: FormData) => {
|
|
// Custom form submission logic
|
|
// Filter out CSRF token for security when logging
|
|
const data = Object.fromEntries(formData.entries());
|
|
// biome-ignore lint/correctness/noUnusedVariables: csrf_token is intentionally extracted and discarded for security
|
|
const { csrf_token, ...safeData } = data;
|
|
console.log("Form data (excluding CSRF token):", safeData);
|
|
|
|
// You can process the form data here before submission
|
|
// The CSRF token is automatically included in formData
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
|
|
<h2 className="text-xl font-semibold mb-4">
|
|
CSRF Protected Form Example
|
|
</h2>
|
|
|
|
<CSRFProtectedForm
|
|
action="/api/example-endpoint"
|
|
onSubmit={handleCustomSubmit}
|
|
className="space-y-4"
|
|
>
|
|
<div>
|
|
<label
|
|
htmlFor={nameId}
|
|
className="block text-sm font-medium text-gray-700"
|
|
>
|
|
Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id={nameId}
|
|
name="name"
|
|
required
|
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
htmlFor={emailId}
|
|
className="block text-sm font-medium text-gray-700"
|
|
>
|
|
Email
|
|
</label>
|
|
<input
|
|
type="email"
|
|
id={emailId}
|
|
name="email"
|
|
required
|
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
htmlFor={messageId}
|
|
className="block text-sm font-medium text-gray-700"
|
|
>
|
|
Message
|
|
</label>
|
|
<textarea
|
|
id={messageId}
|
|
name="message"
|
|
rows={4}
|
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
>
|
|
Submit
|
|
</button>
|
|
</CSRFProtectedForm>
|
|
</div>
|
|
);
|
|
}
|