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; -};