From 55c916a9e9b6ef6597af2acbce2964c57af17c1f Mon Sep 17 00:00:00 2001 From: Mustafa Cetin Date: Wed, 29 Jul 2026 12:56:28 +0000 Subject: [PATCH] Fix: Auth 500 error, script tag warning, dynamic import, rate limit --- next.config.ts | 3 +++ public/sw.js | 17 ++++++++++++----- src/app/api/auth/route.ts | 2 +- src/components/app-layout.tsx | 1 + src/components/dashboard-view.tsx | 3 ++- src/lib/auth.ts | 2 +- src/lib/rate-limit.ts | 4 ++-- src/lib/utils.ts | 21 +++++++++++++++++++++ 8 files changed, 43 insertions(+), 10 deletions(-) diff --git a/next.config.ts b/next.config.ts index 0bd2f11..9ae23a8 100644 --- a/next.config.ts +++ b/next.config.ts @@ -7,6 +7,9 @@ const nextConfig: NextConfig = { ignoreBuildErrors: true, }, reactStrictMode: false, + allowedDevOrigins: [ + 'preview-chat-7eeacdfa-df81-4042-9a8e-7ddb7becdce4.space-z.ai', + ], }; export default nextConfig; diff --git a/public/sw.js b/public/sw.js index fe8a2a6..ade69d1 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,8 +1,8 @@ // CompetenceTrack — Service Worker for PWA Offline Support -const CACHE_NAME = 'competencetrack-v1'; -const STATIC_CACHE = 'competencetrack-static-v1'; -const DATA_CACHE = 'competencetrack-data-v1'; -const OFFLINE_CACHE = 'competencetrack-offline-v1'; +const CACHE_NAME = 'competencetrack-v2'; +const STATIC_CACHE = 'competencetrack-static-v2'; +const DATA_CACHE = 'competencetrack-data-v2'; +const OFFLINE_CACHE = 'competencetrack-offline-v2'; // Static assets to cache on install const STATIC_ASSETS = [ @@ -81,9 +81,16 @@ self.addEventListener('fetch', (event) => { } // Static assets (CSS, JS, images) — CacheFirst strategy + // But use NetworkFirst for JS/CSS from Next.js dev server (_next/) to avoid stale module factories if ( url.pathname.endsWith('.css') || - url.pathname.endsWith('.js') || + url.pathname.endsWith('.js') + ) { + // Always use NetworkFirst for JS/CSS to avoid stale Turbopack chunks + event.respondWith(networkFirstStrategy(request, STATIC_CACHE)); + return; + } + if ( url.pathname.endsWith('.png') || url.pathname.endsWith('.jpg') || url.pathname.endsWith('.svg') || diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index c52938f..4fa7708 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -41,7 +41,7 @@ export const POST = withRateLimit(async function POST(request: Request) { const { email, password } = parsed.data; - const user = await db.user.findUnique({ + const user = await db.user.findFirst({ where: { email, deletedAt: null }, }); diff --git a/src/components/app-layout.tsx b/src/components/app-layout.tsx index 3acbe3f..64094c1 100644 --- a/src/components/app-layout.tsx +++ b/src/components/app-layout.tsx @@ -153,6 +153,7 @@ import ResourceLibraryView from './resource-library-view'; // Dynamic imports for heavy components with loading skeletons const AnalyticsView = dynamic(() => import('./analytics-view'), { + ssr: false, loading: () =>
{[1,2,3].map(i => )}
, }); diff --git a/src/components/dashboard-view.tsx b/src/components/dashboard-view.tsx index 043c11a..6fee8db 100644 --- a/src/components/dashboard-view.tsx +++ b/src/components/dashboard-view.tsx @@ -101,6 +101,7 @@ import { useAppStore, type ViewName, type CurrentUser } from '@/lib/store'; import { t } from '@/lib/i18n'; import { fetchDashboard, type DashboardData, getStoredNotifications, type AppNotification, addNotification, markNotificationsRead, fetchParentLinks, type ParentStudentLinkData, fetchStudents } from '@/lib/api'; import { apiGet, apiPost, apiPut } from '@/lib/api'; +import { sanitizeHtml } from '@/lib/utils'; import { fetchNewsletters, createNewsletter, type NewsletterData } from '@/lib/api'; const containerVariants = { @@ -2645,7 +2646,7 @@ function DashboardNewsletterCard({ currentUser, locale }: { currentUser: Current )} -
+
{viewingNewsletter.tags && (() => { try { const parsed = JSON.parse(viewingNewsletter.tags); diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 390c12a..39935c7 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -53,7 +53,7 @@ export async function getSession(): Promise<{ if (!userId) return null; - const user = await db.user.findUnique({ + const user = await db.user.findFirst({ where: { id: userId, deletedAt: null }, select: sessionUserSelect, }); diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts index 653d350..2d44eb2 100644 --- a/src/lib/rate-limit.ts +++ b/src/lib/rate-limit.ts @@ -23,8 +23,8 @@ setInterval(() => { // ─── Preset Limits ──────────────────────────────────────────────────── export const RATE_LIMITS = { - // Auth endpoints: 5 requests per minute (strict) - auth: { limit: 5, windowMs: 60 * 1000 }, + // Auth endpoints: 20 requests per minute (allows retries & multiple tabs) + auth: { limit: 20, windowMs: 60 * 1000 }, // Data read endpoints: 60 requests per minute (generous) dataRead: { limit: 60, windowMs: 60 * 1000 }, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index bd0c391..b9f8576 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,3 +4,24 @@ import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } + +/** + * Sanitize HTML content by removing script tags and dangerous event handlers. + * This prevents React's "Encountered a script tag" warning and XSS attacks + * when using dangerouslySetInnerHTML. + */ +export function sanitizeHtml(html: string): string { + if (!html) return ''; + return html + // Remove