Fix: Auth 500 error, script tag warning, dynamic import, rate limit

This commit is contained in:
Mustafa Cetin committed 2026-07-29 12:56:28 +00:00
1 parent 8507984c7e
commit 55c916a9e9
8 files changed
+43 -10

No files matched your search

+1 -1
View File
@@ -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 },
});
+1
View File
@@ -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: () => <div className="space-y-4 p-4"><Skeleton className="h-8 w-48" /><div className="grid grid-cols-1 md:grid-cols-3 gap-4">{[1,2,3].map(i => <Skeleton key={i} className="h-32" />)}</div><Skeleton className="h-64" /></div>,
});
+2 -1
View File
@@ -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
<img src={viewingNewsletter.imageUrl} alt="" className="w-full h-48 object-cover" />
</div>
)}
<div className="prose prose-sm dark:prose-invert max-w-none" dangerouslySetInnerHTML={{ __html: viewingNewsletter.content }} />
<div className="prose prose-sm dark:prose-invert max-w-none" dangerouslySetInnerHTML={{ __html: sanitizeHtml(viewingNewsletter.content) }} />
{viewingNewsletter.tags && (() => {
try {
const parsed = JSON.parse(viewingNewsletter.tags);
+1 -1
View File
@@ -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,
});
+2 -2
View File
@@ -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 },
+21
View File
@@ -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 <script> tags and their content
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
// Remove on* event handler attributes (onclick, onload, onerror, etc.)
.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, '')
// Remove javascript: URLs
.replace(/href\s*=\s*(?:"javascript:[^"]*"|'javascript:[^']*')/gi, 'href="#"')
// Remove <iframe> tags
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
// Remove <object> and <embed> tags
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '')
.replace(/<embed\b[^>]*>/gi, '');
}