Security hardening release addressing CodeQL and Dependabot alerts: - Fix stack trace exposure in error responses - Add SSRF protection with DNS resolution checking - Implement proper URL hostname validation (replaces substring matching) - Add centralized path sanitization to prevent path traversal - Fix ReDoS vulnerability in email validation regex - Improve HTML sanitization in validation utilities - Fix capability wildcard matching in auth utilities - Update glob dependency to address CVE - Add CodeQL suppression comments for verified false positives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { AlertTriangle, RefreshCw } from 'lucide-react';
|
|
|
|
interface ErrorBoundaryState {
|
|
hasError: boolean;
|
|
error?: Error;
|
|
}
|
|
|
|
interface ErrorBoundaryProps {
|
|
children: React.ReactNode;
|
|
fallback?: React.ComponentType<{ error?: Error; resetError: () => void }>;
|
|
}
|
|
|
|
class ErrorBoundaryClass extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
constructor(props: ErrorBoundaryProps) {
|
|
super(props);
|
|
this.state = { hasError: false };
|
|
}
|
|
|
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
return { hasError: true, error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
console.error('Error caught by boundary:', error, errorInfo);
|
|
}
|
|
|
|
resetError = () => {
|
|
this.setState({ hasError: false, error: undefined });
|
|
};
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
if (this.props.fallback) {
|
|
const FallbackComponent = this.props.fallback;
|
|
return <FallbackComponent error={this.state.error} resetError={this.resetError} />;
|
|
}
|
|
|
|
return <DefaultErrorFallback error={this.state.error} resetError={this.resetError} />;
|
|
}
|
|
|
|
return this.props.children;
|
|
}
|
|
}
|
|
|
|
function DefaultErrorFallback({ error, resetError }: { error?: Error; resetError: () => void }) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px] p-6">
|
|
<Card className="w-full max-w-md">
|
|
<CardHeader className="text-center">
|
|
<div className="mx-auto mb-4 w-16 h-16 flex items-center justify-center rounded-full bg-red-100">
|
|
<AlertTriangle className="w-8 h-8 text-red-600" />
|
|
</div>
|
|
<CardTitle className="text-red-600">Something went wrong</CardTitle>
|
|
<CardDescription>
|
|
An error occurred while loading this page. Please try refreshing.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{error && (
|
|
<div className="text-sm text-muted-foreground bg-muted p-3 rounded-md">
|
|
<strong>Error:</strong> {error.message}
|
|
</div>
|
|
)}
|
|
<div className="flex space-x-2">
|
|
<Button onClick={resetError} className="flex-1">
|
|
<RefreshCw className="w-4 h-4 mr-2" />
|
|
Try Again
|
|
</Button>
|
|
<Button variant="secondary" onClick={() => window.location.reload()} className="flex-1">
|
|
Reload Page
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Hook version for functional components
|
|
export function useErrorBoundary() {
|
|
const [error, setError] = React.useState<Error | null>(null);
|
|
|
|
const resetError = React.useCallback(() => {
|
|
setError(null);
|
|
}, []);
|
|
|
|
const catchError = React.useCallback((error: Error) => {
|
|
setError(error);
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
}, [error]);
|
|
|
|
return { catchError, resetError };
|
|
}
|
|
|
|
export { ErrorBoundaryClass as ErrorBoundary }; |