Round 19: Fix auth 500/session persistence, script tag warning, module factory error, rate limiting

- Fix auth session persistence: set cookie on both cookies() and NextResponse
- Fix isSecureRequest: check x-forwarded-proto for HTTPS proxy support
- Fix excessive GET /api/auth polling: add useRef guard to prevent duplicate checks
- Increase rate limits: auth POST 30/min, auth GET 120/min
- Fix script tag warning: use ref-based textContent instead of dangerouslySetInnerHTML in chart.tsx
- Fix sanitizeHtml: improve regex patterns for script tag removal
- Fix module factory error: disable SW registration in dev, skip /_next/ caching in sw.js
- Bump SW cache version v2→v3 to force cache invalidation
- Better error handling in auth route with specific error messages
This commit is contained in:
Mustafa Cetin committed 2026-07-29 13:34:33 +00:00
1 parent 760fa70e82
commit 74404708e5
9 files changed
+192 -47

No files matched your search

+51 -11
View File
@@ -7,6 +7,9 @@ import {
createSession,
getSession,
clearSession,
isSecureRequest,
SESSION_COOKIE_NAME,
getSessionCookieOptions,
} from '@/lib/auth';
import { withRateLimit, checkRateLimit, getRateLimitKey, getRateLimitHeaders, createRateLimitResponse } from '@/lib/rate-limit';
@@ -60,9 +63,13 @@ export const POST = withRateLimit(async function POST(request: Request) {
);
}
await createSession(user.id);
const secure = isSecureRequest(request);
await createSession(user.id, secure);
return NextResponse.json({
// Also set cookie on the response object to guarantee the Set-Cookie
// header is included. In some Next.js App Router scenarios, cookies().set()
// alone may not propagate to the NextResponse body.
const response = NextResponse.json({
id: user.id,
email: user.email,
firstName: user.firstName,
@@ -72,6 +79,8 @@ export const POST = withRateLimit(async function POST(request: Request) {
locale: user.locale,
isDemo: user.isDemo,
});
response.cookies.set(SESSION_COOKIE_NAME, user.id, getSessionCookieOptions(secure));
return response;
}
// ── Register ──
@@ -108,9 +117,10 @@ export const POST = withRateLimit(async function POST(request: Request) {
},
});
await createSession(user.id);
const secure = isSecureRequest(request);
await createSession(user.id, secure);
return NextResponse.json(
const response = NextResponse.json(
{
id: user.id,
email: user.email,
@@ -123,28 +133,56 @@ export const POST = withRateLimit(async function POST(request: Request) {
},
{ status: 201 }
);
response.cookies.set(SESSION_COOKIE_NAME, user.id, getSessionCookieOptions(secure));
return response;
}
// ── Logout ──
if (action === 'logout') {
await clearSession();
return NextResponse.json({ message: 'Logged out' });
const response = NextResponse.json({ message: 'Logged out' });
response.cookies.delete(SESSION_COOKIE_NAME);
return response;
}
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
} catch (error) {
console.error('Auth error:', error);
console.error('Auth POST error:', error);
// Return more specific error info for debugging
const message =
error instanceof Error ? error.message : 'Unknown error';
const isZod = error instanceof z.ZodError;
const isPrisma =
error instanceof Error &&
(error.message.includes('Prisma') || error.constructor.name.includes('Prisma'));
if (isZod) {
return NextResponse.json(
{ error: 'Validation error', details: (error as z.ZodError).issues },
{ status: 400 }
);
}
if (isPrisma) {
return NextResponse.json(
{ error: 'Database error', detail: message },
{ status: 500 }
);
}
return NextResponse.json(
{ error: 'Internal server error' },
{ error: 'Internal server error', detail: message },
{ status: 500 }
);
}
}, 'auth');
export async function GET(request: Request) {
// Apply rate limit for auth check endpoint
// Apply rate limit for auth check endpoint — 120 per minute
const key = getRateLimitKey(request);
const result = checkRateLimit(key, 60, 60 * 1000); // 60 per minute for GET
const result = checkRateLimit(key, 120, 60 * 1000);
if (!result.allowed) {
const rateLimitResponse = createRateLimitResponse(result);
@@ -167,9 +205,11 @@ export async function GET(request: Request) {
}
return response;
} catch (error) {
console.error('Auth me error:', error);
console.error('Auth GET error:', error);
const message =
error instanceof Error ? error.message : 'Unknown error';
return NextResponse.json(
{ error: 'Internal server error' },
{ error: 'Internal server error', detail: message },
{ status: 500 }
);
}
+6 -1
View File
@@ -1,6 +1,6 @@
'use client';
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import { useAppStore } from '@/lib/store';
import { fetchCurrentUser } from '@/lib/api';
import AuthView from '@/components/auth-view';
@@ -10,8 +10,13 @@ export default function Home() {
const currentUser = useAppStore((s) => s.currentUser);
const isLoadingAuth = useAppStore((s) => s.isLoadingAuth);
const setCurrentUser = useAppStore((s) => s.setCurrentUser);
const hasCheckedRef = useRef(false);
useEffect(() => {
// Prevent duplicate auth checks: only run once per component lifecycle
if (hasCheckedRef.current) return;
hasCheckedRef.current = true;
async function checkAuth() {
try {
const user = await fetchCurrentUser();