fix: adminPath TTL cache in hooks.ts instead of read-once at startup
Previous approach read config.json once at module load time. If the PVC wasn't fully mounted yet, it fell back to 'admin' and stayed there for the lifetime of the process. New approach caches for 5 seconds and re-reads on expiry, so it recovers from startup races and picks up changes without requiring a restart.
This commit is contained in:
33
src/hooks.ts
33
src/hooks.ts
@@ -2,17 +2,29 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import type { Reroute } from '@sveltejs/kit';
|
||||
|
||||
// Read adminPath once at startup. Changes require a server restart.
|
||||
let adminPath = 'admin';
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(path.resolve('data', 'config.json'), 'utf8'));
|
||||
adminPath = (config.adminPath ?? 'admin').replace(/^\/|\/$/g, '') || 'admin';
|
||||
console.log('[hooks] adminPath set to:', adminPath);
|
||||
} catch {
|
||||
// config.json missing or unreadable — fall back to 'admin'
|
||||
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 { adminPath };
|
||||
export { getAdminPath as adminPath };
|
||||
|
||||
/**
|
||||
* Rewrites /<adminPath>/... to /admin/... before SvelteKit resolves the route.
|
||||
@@ -20,18 +32,17 @@ export { adminPath };
|
||||
* 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 + '/')) {
|
||||
console.log(`[reroute] ${url.pathname} → /admin${url.pathname.slice(prefix.length)}`);
|
||||
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/')) {
|
||||
console.log(`[reroute] blocking direct /admin access → /404`);
|
||||
return '/404';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user