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

@ -42,7 +42,7 @@ export async function GET(request: NextRequest) {
if (startDate && endDate) {
whereClause.startTime = {
gte: new Date(startDate),
lte: new Date(endDate + 'T23:59:59.999Z'), // Include full end date
lte: new Date(endDate + "T23:59:59.999Z"), // Include full end date
};
}
@ -94,10 +94,12 @@ export async function GET(request: NextRequest) {
// Calculate date range from sessions
let dateRange: { minDate: string; maxDate: string } | null = null;
if (prismaSessions.length > 0) {
const dates = prismaSessions.map(s => new Date(s.startTime)).sort((a, b) => a.getTime() - b.getTime());
const dates = prismaSessions
.map((s) => new Date(s.startTime))
.sort((a, b) => a.getTime() - b.getTime());
dateRange = {
minDate: dates[0].toISOString().split('T')[0], // First session date
maxDate: dates[dates.length - 1].toISOString().split('T')[0] // Last session date
minDate: dates[0].toISOString().split("T")[0], // First session date
maxDate: dates[dates.length - 1].toISOString().split("T")[0], // Last session date
};
}

View File

@ -53,9 +53,9 @@ export async function GET(request: NextRequest) {
.map((s) => s.language)
.filter(Boolean) as string[]; // Filter out any nulls and assert as string[]
return NextResponse.json({
categories: distinctCategories,
languages: distinctLanguages
return NextResponse.json({
categories: distinctCategories,
languages: distinctLanguages,
});
} catch (error) {
const errorMessage =

View File

@ -26,10 +26,7 @@ export async function GET(
});
if (!prismaSession) {
return NextResponse.json(
{ error: "Session not found" },
{ status: 404 }
);
return NextResponse.json({ error: "Session not found" }, { status: 404 });
}
// Map Prisma session object to ChatSession type

View File

@ -18,7 +18,7 @@ export async function GET(request: NextRequest) {
const companyId = authSession.user.companyId;
const { searchParams } = new URL(request.url);
const searchTerm = searchParams.get("searchTerm");
const category = searchParams.get("category");
const language = searchParams.get("language");
@ -87,9 +87,7 @@ export async function GET(request: NextRequest) {
| Prisma.SessionOrderByWithRelationInput[];
const primarySortField =
sortKey && validSortKeys[sortKey]
? validSortKeys[sortKey]
: "startTime"; // Default to startTime field if sortKey is invalid/missing
sortKey && validSortKeys[sortKey] ? validSortKeys[sortKey] : "startTime"; // Default to startTime field if sortKey is invalid/missing
const primarySortOrder =
sortOrder === "asc" || sortOrder === "desc" ? sortOrder : "desc"; // Default to desc order

View File

@ -65,7 +65,7 @@ export async function POST(request: NextRequest) {
}
const tempPassword = crypto.randomBytes(12).toString("base64").slice(0, 12); // secure random initial password
await prisma.user.create({
data: {
email,