mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
Same-origin browser requests don't send Origin header (per CORS spec), causing validateApiKey to reject them. Extract origin from Referer as fallback. Increase rate limit from 60 to 200 req/min to accommodate the ~50 requests fired during page initialization.
67 lines
2.4 KiB
JavaScript
67 lines
2.4 KiB
JavaScript
const DESKTOP_ORIGIN_PATTERNS = [
|
|
/^https?:\/\/tauri\.localhost(:\d+)?$/,
|
|
/^https?:\/\/[a-z0-9-]+\.tauri\.localhost(:\d+)?$/i,
|
|
/^tauri:\/\/localhost$/,
|
|
/^asset:\/\/localhost$/,
|
|
];
|
|
|
|
const BROWSER_ORIGIN_PATTERNS = [
|
|
/^https:\/\/(.*\.)?worldmonitor\.app$/,
|
|
/^https:\/\/worldmonitor-[a-z0-9-]+\.vercel\.app$/,
|
|
...(process.env.NODE_ENV === 'production' ? [] : [
|
|
/^https?:\/\/localhost(:\d+)?$/,
|
|
/^https?:\/\/127\.0\.0\.1(:\d+)?$/,
|
|
]),
|
|
];
|
|
|
|
function isDesktopOrigin(origin) {
|
|
return Boolean(origin) && DESKTOP_ORIGIN_PATTERNS.some(p => p.test(origin));
|
|
}
|
|
|
|
function isTrustedBrowserOrigin(origin) {
|
|
return Boolean(origin) && BROWSER_ORIGIN_PATTERNS.some(p => p.test(origin));
|
|
}
|
|
|
|
function extractOriginFromReferer(referer) {
|
|
if (!referer) return '';
|
|
try {
|
|
return new URL(referer).origin;
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
export function validateApiKey(req) {
|
|
const key = req.headers.get('X-WorldMonitor-Key');
|
|
// Same-origin browser requests don't send Origin (per CORS spec).
|
|
// Fall back to Referer to identify trusted same-origin callers.
|
|
const origin = req.headers.get('Origin') || extractOriginFromReferer(req.headers.get('Referer')) || '';
|
|
|
|
// Desktop app — always require API key
|
|
if (isDesktopOrigin(origin)) {
|
|
if (!key) return { valid: false, required: true, error: 'API key required for desktop access' };
|
|
const validKeys = (process.env.WORLDMONITOR_VALID_KEYS || '').split(',').filter(Boolean);
|
|
if (!validKeys.includes(key)) return { valid: false, required: true, error: 'Invalid API key' };
|
|
return { valid: true, required: true };
|
|
}
|
|
|
|
// Trusted browser origin (worldmonitor.app, Vercel previews, localhost dev) — no key needed
|
|
if (isTrustedBrowserOrigin(origin)) {
|
|
if (key) {
|
|
const validKeys = (process.env.WORLDMONITOR_VALID_KEYS || '').split(',').filter(Boolean);
|
|
if (!validKeys.includes(key)) return { valid: false, required: true, error: 'Invalid API key' };
|
|
}
|
|
return { valid: true, required: false };
|
|
}
|
|
|
|
// Explicit key provided from unknown origin — validate it
|
|
if (key) {
|
|
const validKeys = (process.env.WORLDMONITOR_VALID_KEYS || '').split(',').filter(Boolean);
|
|
if (!validKeys.includes(key)) return { valid: false, required: true, error: 'Invalid API key' };
|
|
return { valid: true, required: true };
|
|
}
|
|
|
|
// No origin, no key — require API key (blocks unauthenticated curl/scripts)
|
|
return { valid: false, required: true, error: 'API key required' };
|
|
}
|