Security hardening for production readiness

- Add security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, etc.)
- Strengthen password requirements (10+ chars, mixed case, numbers)
- Increase shared list slug entropy from 4 to 16 bytes
- Add rate limiting to login, registration, upload, and restore endpoints
- Add file magic number validation for image uploads (JPEG, PNG, WebP, HEIC)
- Add CSV row limit (50k) to restore endpoint
- Update client-side registration form to match new password policy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
JP Scott
2026-03-01 12:55:16 -07:00
parent 969bc9347a
commit 8a582bfa7f
8 changed files with 149 additions and 10 deletions

View File

@@ -2,6 +2,7 @@ import { NextResponse } from "next/server"
import { z } from "zod"
import bcrypt from "bcryptjs"
import { prisma } from "@/lib/prisma"
import { rateLimit } from "@/lib/rate-limit"
const registerSchema = z.object({
name: z
@@ -14,11 +15,26 @@ const registerSchema = z.object({
.email("Invalid email address"),
password: z
.string()
.min(8, "Password must be at least 8 characters"),
.min(10, "Password must be at least 10 characters")
.max(128, "Password must be 128 characters or less")
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
.regex(/[0-9]/, "Password must contain at least one number"),
})
export async function POST(request: Request) {
try {
// Rate limit: 5 registration attempts per IP per minute
const forwarded = request.headers.get("x-forwarded-for")
const ip = forwarded?.split(",")[0]?.trim() ?? "unknown"
const rl = rateLimit(`register:${ip}`, 5, 60 * 1000)
if (!rl.success) {
return NextResponse.json(
{ error: "Too many registration attempts. Please try again later." },
{ status: 429 }
)
}
const body = await request.json()
const result = registerSchema.safeParse(body)