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:
29
src/hooks.ts
29
src/hooks.ts
@@ -2,17 +2,29 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { Reroute } from '@sveltejs/kit';
|
import type { Reroute } from '@sveltejs/kit';
|
||||||
|
|
||||||
// Read adminPath once at startup. Changes require a server restart.
|
const CONFIG_PATH = path.resolve('data', 'config.json');
|
||||||
let adminPath = 'admin';
|
|
||||||
|
// 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 {
|
try {
|
||||||
const config = JSON.parse(fs.readFileSync(path.resolve('data', 'config.json'), 'utf8'));
|
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
||||||
adminPath = (config.adminPath ?? 'admin').replace(/^\/|\/$/g, '') || 'admin';
|
cachedAdminPath = (config.adminPath ?? 'admin').replace(/^\/|\/$/g, '') || 'admin';
|
||||||
console.log('[hooks] adminPath set to:', adminPath);
|
|
||||||
} catch {
|
} catch {
|
||||||
// config.json missing or unreadable — fall back to 'admin'
|
// config.json missing or unreadable — keep current cached value
|
||||||
}
|
}
|
||||||
|
|
||||||
export { adminPath };
|
cacheExpiry = now + 5000;
|
||||||
|
return cachedAdminPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getAdminPath as adminPath };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewrites /<adminPath>/... to /admin/... before SvelteKit resolves the route.
|
* Rewrites /<adminPath>/... to /admin/... before SvelteKit resolves the route.
|
||||||
@@ -20,18 +32,17 @@ export { adminPath };
|
|||||||
* so attackers cannot enumerate the admin URL.
|
* so attackers cannot enumerate the admin URL.
|
||||||
*/
|
*/
|
||||||
export const reroute: Reroute = ({ url }) => {
|
export const reroute: Reroute = ({ url }) => {
|
||||||
|
const adminPath = getAdminPath();
|
||||||
const prefix = `/${adminPath}`;
|
const prefix = `/${adminPath}`;
|
||||||
|
|
||||||
// Public path → internal /admin
|
// Public path → internal /admin
|
||||||
if (url.pathname === prefix || url.pathname.startsWith(prefix + '/')) {
|
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);
|
return '/admin' + url.pathname.slice(prefix.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block direct /admin access when a custom path is configured
|
// Block direct /admin access when a custom path is configured
|
||||||
if (adminPath !== 'admin') {
|
if (adminPath !== 'admin') {
|
||||||
if (url.pathname === '/admin' || url.pathname.startsWith('/admin/')) {
|
if (url.pathname === '/admin' || url.pathname.startsWith('/admin/')) {
|
||||||
console.log(`[reroute] blocking direct /admin access → /404`);
|
|
||||||
return '/404';
|
return '/404';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user