From 03bfd565e8d4c9ebd54506cb1cbbe7ab4d99939d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Fri, 10 Apr 2026 20:28:55 +0300 Subject: [PATCH] feat(auth): enforce length limits for name and password during registration feat(comments): add content length validation for comments and annotations feat(guest-gate): restrict guest name length and update localStorage handling feat(share-link-unlock): set maxLength for password input field --- app/api/auth/register/route.ts | 8 ++++---- app/api/versions/[versionId]/comments/route.ts | 11 +++++++++++ components/guest-gate.tsx | 8 +++++--- components/share-link-unlock.tsx | 1 + 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts index b45ab46..3303986 100644 --- a/app/api/auth/register/route.ts +++ b/app/api/auth/register/route.ts @@ -21,8 +21,8 @@ export async function POST(request: NextRequest) { const { name, email, password, inviteCode, invitationToken } = body; // Validate required fields - if (!name || typeof name !== 'string' || name.trim().length < 2) { - return apiErrors.badRequest('Name must be at least 2 characters'); + if (!name || typeof name !== 'string' || name.trim().length < 2 || name.trim().length > 100) { + return apiErrors.badRequest('Name must be between 2 and 100 characters'); } if (!email || typeof email !== 'string') { @@ -72,8 +72,8 @@ export async function POST(request: NextRequest) { } } - if (!password || typeof password !== 'string' || password.length < 8) { - return apiErrors.badRequest('Password must be at least 8 characters'); + if (!password || typeof password !== 'string' || password.length < 8 || password.length > 128) { + return apiErrors.badRequest('Password must be between 8 and 128 characters'); } // Check if email already exists diff --git a/app/api/versions/[versionId]/comments/route.ts b/app/api/versions/[versionId]/comments/route.ts index 135a733..7fa5ad8 100644 --- a/app/api/versions/[versionId]/comments/route.ts +++ b/app/api/versions/[versionId]/comments/route.ts @@ -252,6 +252,17 @@ export async function POST(request: NextRequest, { params }: RouteParams) { return apiErrors.badRequest('Either content, a voice recording, an image attachment, or an annotation is required'); } + // Length limits to prevent DB bloat and DoS on export/notification paths + if (content !== undefined && content !== null && String(content).length > 10_000) { + return apiErrors.badRequest('Comment content must be 10,000 characters or fewer'); + } + if (guestName !== undefined && guestName !== null && String(guestName).length > 100) { + return apiErrors.badRequest('Guest name must be 100 characters or fewer'); + } + if (annotationData !== undefined && annotationData !== null && JSON.stringify(annotationData).length > 50_000) { + return apiErrors.badRequest('Annotation data is too large'); + } + // If replying, verify parent exists in same version if (parentId) { const parent = await db.comment.findFirst({ diff --git a/components/guest-gate.tsx b/components/guest-gate.tsx index a13cbb7..09c715b 100644 --- a/components/guest-gate.tsx +++ b/components/guest-gate.tsx @@ -26,8 +26,9 @@ export function GuestGate({ children }: { children: ReactNode }) { } const confirm = () => { - if (!guestName.trim()) return; - localStorage.setItem('openframe_guest_name', guestName.trim()); + const trimmed = guestName.trim(); + if (!trimmed || trimmed.length > 100) return; + localStorage.setItem('openframe_guest_name', trimmed); setConfirmed(true); }; @@ -47,13 +48,14 @@ export function GuestGate({ children }: { children: ReactNode }) { setGuestName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') confirm(); }} autoFocus /> - diff --git a/components/share-link-unlock.tsx b/components/share-link-unlock.tsx index 0f418af..efbb058 100644 --- a/components/share-link-unlock.tsx +++ b/components/share-link-unlock.tsx @@ -61,6 +61,7 @@ export function ShareLinkUnlock({ videoId }: ShareLinkUnlockProps) { type="password" placeholder="Password" value={password} + maxLength={128} onChange={(event) => setPassword(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') {