From 2a4a1fb61d5c6453737088dd84c5cd37c8ac22f6 Mon Sep 17 00:00:00 2001 From: Falkan Date: Fri, 20 Mar 2026 15:10:14 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20merge=20hooks.ts=20into=20hooks.server.t?= =?UTF-8?q?s=20=E2=80=94=20root=20cause=20of=20K.resolve=20hydration=20cra?= =?UTF-8?q?sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hooks.ts contained Node-only imports (fs, path) and was being included in the client bundle by SvelteKit because it lacked the .server. designation. This caused 'K.resolve is not a function' in the browser, which aborted hydration and killed ALL client-side interactivity site-wide. Fix: merge reroute export from hooks.ts into hooks.server.ts alongside the existing CSRF handle. Delete hooks.ts. SvelteKit supports both handle and reroute exports from hooks.server.ts. This was the true root cause of today's outage. The CSS and issues were red herrings — the site was broken from commit 231eacc onward. --- src/hooks.server.ts | 58 ++++++++++++++++++++++++++++++++++++++++++--- src/hooks.ts | 51 --------------------------------------- 2 files changed, 55 insertions(+), 54 deletions(-) delete mode 100644 src/hooks.ts diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 6599227..916bfe0 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,9 +1,61 @@ -import { type Handle } from '@sveltejs/kit'; +import fs from 'fs'; +import path from 'path'; +import { type Handle, type Reroute } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; +// ── Admin path rerouting ──────────────────────────────────────────────────── + +const CONFIG_PATH = path.resolve('data', 'config.json'); + +// Cache adminPath with a 5-second TTL so changes take effect quickly +// but we don't hit disk on every request. +let cachedAdminPath = 'admin'; +let cacheExpiry = 0; + +function getAdminPath(): string { + const now = Date.now(); + if (now < cacheExpiry) return cachedAdminPath; + + try { + const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); + cachedAdminPath = (config.adminPath ?? 'admin').replace(/^\/|\/$/g, '') || 'admin'; + } catch { + // config.json missing or unreadable — keep current cached value + } + + cacheExpiry = now + 5000; + return cachedAdminPath; +} + +export { getAdminPath as adminPath }; + +/** + * Rewrites //... to /admin/... before SvelteKit resolves the route. + * When adminPath is not 'admin', direct access to /admin is rewritten to /404 + * so attackers cannot enumerate the admin URL. + */ +export const reroute: Reroute = ({ url }) => { + const adminPath = getAdminPath(); + const prefix = `/${adminPath}`; + + // Public path → internal /admin + if (url.pathname === prefix || url.pathname.startsWith(prefix + '/')) { + return '/admin' + url.pathname.slice(prefix.length); + } + + // Block direct /admin access when a custom path is configured + if (adminPath !== 'admin') { + if (url.pathname === '/admin' || url.pathname.startsWith('/admin/')) { + return '/404'; + } + } + + return url.pathname; +}; + +// ── CSRF origin allowlist ─────────────────────────────────────────────────── + /** - * CSRF origin allowlist. - * * Production: set ALLOWED_ORIGINS to a comma-separated list of trusted origins. * e.g. ALLOWED_ORIGINS=https://humorunits.com,https://dickloads.com * diff --git a/src/hooks.ts b/src/hooks.ts deleted file mode 100644 index 9be71cf..0000000 --- a/src/hooks.ts +++ /dev/null @@ -1,51 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import type { Reroute } from '@sveltejs/kit'; - -const CONFIG_PATH = path.resolve('data', 'config.json'); - -// Cache adminPath with a 5-second TTL so changes take effect quickly -// but we don't hit disk on every request. -let cachedAdminPath = 'admin'; -let cacheExpiry = 0; - -function getAdminPath(): string { - const now = Date.now(); - if (now < cacheExpiry) return cachedAdminPath; - - try { - const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); - cachedAdminPath = (config.adminPath ?? 'admin').replace(/^\/|\/$/g, '') || 'admin'; - } catch { - // config.json missing or unreadable — keep current cached value - } - - cacheExpiry = now + 5000; - return cachedAdminPath; -} - -export { getAdminPath as adminPath }; - -/** - * Rewrites //... to /admin/... before SvelteKit resolves the route. - * When adminPath is not 'admin', direct access to /admin is rewritten to /404 - * so attackers cannot enumerate the admin URL. - */ -export const reroute: Reroute = ({ url }) => { - const adminPath = getAdminPath(); - const prefix = `/${adminPath}`; - - // Public path → internal /admin - if (url.pathname === prefix || url.pathname.startsWith(prefix + '/')) { - return '/admin' + url.pathname.slice(prefix.length); - } - - // Block direct /admin access when a custom path is configured - if (adminPath !== 'admin') { - if (url.pathname === '/admin' || url.pathname.startsWith('/admin/')) { - return '/404'; - } - } - - return url.pathname; -};