feat: comprehensive security and architecture improvements

- Add Zod validation schemas with strong password requirements (12+ chars, complexity)
- Implement rate limiting for authentication endpoints (registration, password reset)
- Remove duplicate MetricCard component, consolidate to ui/metric-card.tsx
- Update README.md to use pnpm commands consistently
- Enhance authentication security with 12-round bcrypt hashing
- Add comprehensive input validation for all API endpoints
- Fix security vulnerabilities in user registration and password reset flows

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-06-28 01:52:53 +02:00
parent 192f9497b4
commit 7f48a085bf
68 changed files with 8045 additions and 4542 deletions

View File

@ -48,7 +48,10 @@ function DashboardContent() {
const [company, setCompany] = useState<Company | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [refreshing, setRefreshing] = useState<boolean>(false);
const [dateRange, setDateRange] = useState<{ minDate: string; maxDate: string } | null>(null);
const [dateRange, setDateRange] = useState<{
minDate: string;
maxDate: string;
} | null>(null);
const [selectedStartDate, setSelectedStartDate] = useState<string>("");
const [selectedEndDate, setSelectedEndDate] = useState<string>("");
const [isInitialLoad, setIsInitialLoad] = useState<boolean>(true);
@ -56,7 +59,11 @@ function DashboardContent() {
const isAuditor = session?.user?.role === "AUDITOR";
// Function to fetch metrics with optional date range
const fetchMetrics = async (startDate?: string, endDate?: string, isInitial = false) => {
const fetchMetrics = async (
startDate?: string,
endDate?: string,
isInitial = false
) => {
setLoading(true);
try {
let url = "/api/dashboard/metrics";
@ -85,11 +92,14 @@ function DashboardContent() {
};
// Handle date range changes
const handleDateRangeChange = useCallback((startDate: string, endDate: string) => {
setSelectedStartDate(startDate);
setSelectedEndDate(endDate);
fetchMetrics(startDate, endDate);
}, []);
const handleDateRangeChange = useCallback(
(startDate: string, endDate: string) => {
setSelectedStartDate(startDate);
setSelectedEndDate(endDate);
fetchMetrics(startDate, endDate);
},
[]
);
useEffect(() => {
// Redirect if not authenticated
@ -216,33 +226,48 @@ function DashboardContent() {
};
return [
{ name: "Positive", value: sentimentData.positive, color: "hsl(var(--chart-1))" },
{ name: "Neutral", value: sentimentData.neutral, color: "hsl(var(--chart-2))" },
{ name: "Negative", value: sentimentData.negative, color: "hsl(var(--chart-3))" },
{
name: "Positive",
value: sentimentData.positive,
color: "hsl(var(--chart-1))",
},
{
name: "Neutral",
value: sentimentData.neutral,
color: "hsl(var(--chart-2))",
},
{
name: "Negative",
value: sentimentData.negative,
color: "hsl(var(--chart-3))",
},
];
};
const getSessionsOverTimeData = () => {
if (!metrics?.days) return [];
return Object.entries(metrics.days).map(([date, value]) => ({
date: new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
date: new Date(date).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
}),
value: value as number,
}));
};
const getCategoriesData = () => {
if (!metrics?.categories) return [];
return Object.entries(metrics.categories).map(([name, value]) => ({
name: name.length > 15 ? name.substring(0, 15) + '...' : name,
name: name.length > 15 ? name.substring(0, 15) + "..." : name,
value: value as number,
}));
};
const getLanguagesData = () => {
if (!metrics?.languages) return [];
return Object.entries(metrics.languages).map(([name, value]) => ({
name,
value: value as number,
@ -287,7 +312,9 @@ function DashboardContent() {
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div className="space-y-2">
<div className="flex items-center gap-3">
<h1 className="text-3xl font-bold tracking-tight">{company.name}</h1>
<h1 className="text-3xl font-bold tracking-tight">
{company.name}
</h1>
<Badge variant="secondary" className="text-xs">
Analytics Dashboard
</Badge>
@ -299,7 +326,7 @@ function DashboardContent() {
</span>
</p>
</div>
<div className="flex items-center gap-2">
<Button
onClick={handleRefresh}
@ -307,10 +334,12 @@ function DashboardContent() {
size="sm"
className="gap-2"
>
<RefreshCw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
<RefreshCw
className={`h-4 w-4 ${refreshing ? "animate-spin" : ""}`}
/>
{refreshing ? "Refreshing..." : "Refresh"}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
@ -318,7 +347,9 @@ function DashboardContent() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => signOut({ callbackUrl: "/login" })}>
<DropdownMenuItem
onClick={() => signOut({ callbackUrl: "/login" })}
>
<LogOut className="h-4 w-4 mr-2" />
Sign out
</DropdownMenuItem>
@ -352,7 +383,7 @@ function DashboardContent() {
}}
variant="primary"
/>
<MetricCard
title="Unique Users"
value={metrics.uniqueUsers?.toLocaleString()}
@ -363,7 +394,7 @@ function DashboardContent() {
}}
variant="success"
/>
<MetricCard
title="Avg. Session Time"
value={`${Math.round(metrics.avgSessionLength || 0)}s`}
@ -373,7 +404,7 @@ function DashboardContent() {
isPositive: (metrics.avgSessionTimeTrend ?? 0) >= 0,
}}
/>
<MetricCard
title="Avg. Response Time"
value={`${metrics.avgResponseTime?.toFixed(1) || 0}s`}
@ -384,32 +415,37 @@ function DashboardContent() {
}}
variant="warning"
/>
<MetricCard
title="Daily Costs"
value={`${metrics.avgDailyCosts?.toFixed(4) || '0.0000'}`}
value={`${metrics.avgDailyCosts?.toFixed(4) || "0.0000"}`}
icon={<Euro className="h-5 w-5" />}
description="Average per day"
/>
<MetricCard
title="Peak Usage"
value={metrics.peakUsageTime || 'N/A'}
value={metrics.peakUsageTime || "N/A"}
icon={<TrendingUp className="h-5 w-5" />}
description="Busiest hour"
/>
<MetricCard
title="Resolution Rate"
value={`${metrics.resolvedChatsPercentage?.toFixed(1) || '0.0'}%`}
value={`${metrics.resolvedChatsPercentage?.toFixed(1) || "0.0"}%`}
icon={<CheckCircle className="h-5 w-5" />}
trend={{
value: metrics.resolvedChatsPercentage ?? 0,
isPositive: (metrics.resolvedChatsPercentage ?? 0) >= 80,
}}
variant={metrics.resolvedChatsPercentage && metrics.resolvedChatsPercentage >= 80 ? "success" : "warning"}
variant={
metrics.resolvedChatsPercentage &&
metrics.resolvedChatsPercentage >= 80
? "success"
: "warning"
}
/>
<MetricCard
title="Active Languages"
value={Object.keys(metrics.languages || {}).length}
@ -426,7 +462,7 @@ function DashboardContent() {
className="lg:col-span-2"
height={350}
/>
<ModernDonutChart
data={getSentimentData()}
title="Conversation Sentiment"
@ -444,7 +480,7 @@ function DashboardContent() {
title="Sessions by Category"
height={350}
/>
<ModernDonutChart
data={getLanguagesData()}
title="Languages Used"
@ -508,8 +544,8 @@ function DashboardContent() {
{metrics.totalTokens?.toLocaleString() || 0}
</Badge>
<Badge variant="outline" className="gap-1">
<span className="font-semibold">Total Cost:</span>
{metrics.totalTokensEur?.toFixed(4) || 0}
<span className="font-semibold">Total Cost:</span>
{metrics.totalTokensEur?.toFixed(4) || 0}
</Badge>
</div>
</div>

View File

@ -46,7 +46,8 @@ const DashboardPage: FC = () => {
const navigationCards = [
{
title: "Analytics Overview",
description: "View comprehensive metrics, charts, and insights from your chat sessions",
description:
"View comprehensive metrics, charts, and insights from your chat sessions",
icon: <BarChart3 className="h-6 w-6" />,
href: "/dashboard/overview",
variant: "primary" as const,
@ -54,7 +55,8 @@ const DashboardPage: FC = () => {
},
{
title: "Session Browser",
description: "Browse, search, and analyze individual conversation sessions",
description:
"Browse, search, and analyze individual conversation sessions",
icon: <MessageSquare className="h-6 w-6" />,
href: "/dashboard/sessions",
variant: "success" as const,
@ -64,16 +66,22 @@ const DashboardPage: FC = () => {
? [
{
title: "Company Settings",
description: "Configure company settings, integrations, and API connections",
description:
"Configure company settings, integrations, and API connections",
icon: <Settings className="h-6 w-6" />,
href: "/dashboard/company",
variant: "warning" as const,
features: ["API configuration", "Integration settings", "Data management"],
features: [
"API configuration",
"Integration settings",
"Data management",
],
adminOnly: true,
},
{
title: "User Management",
description: "Invite team members and manage user accounts and permissions",
description:
"Invite team members and manage user accounts and permissions",
icon: <Users className="h-6 w-6" />,
href: "/dashboard/users",
variant: "default" as const,
@ -129,7 +137,7 @@ const DashboardPage: FC = () => {
Choose a section below to explore your analytics dashboard
</p>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Shield className="h-4 w-4" />
@ -152,7 +160,7 @@ const DashboardPage: FC = () => {
>
{/* Subtle gradient overlay */}
<div className="absolute inset-0 bg-linear-to-br from-white/50 to-transparent dark:from-white/5 pointer-events-none" />
<CardHeader className="relative">
<div className="flex items-start justify-between">
<div className="space-y-3">
@ -186,7 +194,10 @@ const DashboardPage: FC = () => {
{/* Features List */}
<div className="space-y-2">
{card.features.map((feature, featureIndex) => (
<div key={featureIndex} className="flex items-center gap-2 text-sm">
<div
key={featureIndex}
className="flex items-center gap-2 text-sm"
>
<div className="h-1.5 w-1.5 rounded-full bg-current opacity-60" />
<span className="text-muted-foreground">{feature}</span>
</div>
@ -232,7 +243,7 @@ const DashboardPage: FC = () => {
</div>
<p className="text-sm text-muted-foreground">Data updates</p>
</div>
<div className="text-center space-y-2">
<div className="flex items-center justify-center gap-2">
<Shield className="h-5 w-5 text-green-600" />
@ -240,7 +251,7 @@ const DashboardPage: FC = () => {
</div>
<p className="text-sm text-muted-foreground">Data protection</p>
</div>
<div className="text-center space-y-2">
<div className="flex items-center justify-center gap-2">
<BarChart3 className="h-5 w-5 text-blue-600" />