export enum ContentType { JSON = 'json', FORM = 'form', MULTIPART = 'multipart', TEXT = 'text', RAW = 'raw', } export interface NtfyConfig { enabled: boolean; server?: string; topic?: string; } export interface RouteConfig { name: string; contentType: ContentType; hcaptchaProtected: boolean; hcaptchaSecret?: string; ntfy?: NtfyConfig; requireToken?: boolean; } export interface StoredRequest { timestamp: number; uuid: string; routeName: string; method: string; headers: Record; body: unknown; files?: Array<{ fieldName: string; originalFilename: string; filename: string; contentType: string; size: number; path: string; }>; } const ROUTE_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/i; export function isSafeRouteName(name: unknown): name is string { if (typeof name !== 'string') return false; if (name !== name.trim()) return false; if (name === '.' || name === '..') return false; if (name.includes('/') || name.includes('\\')) return false; return ROUTE_NAME_PATTERN.test(name); } export function isRouteConfig(obj: unknown): obj is RouteConfig { if (typeof obj !== 'object' || obj === null) return false; const r = obj as Record; const validBasic = isSafeRouteName(r.name) && typeof r.contentType === 'string' && Object.values(ContentType).includes(r.contentType as ContentType) && typeof r.hcaptchaProtected === 'boolean' && (r.hcaptchaProtected === false || typeof r.hcaptchaSecret === 'string'); if (!validBasic) return false; // Validate ntfy config if present if (r.ntfy !== undefined) { if (typeof r.ntfy !== 'object' || r.ntfy === null) return false; const ntfy = r.ntfy as Record; if (typeof ntfy.enabled !== 'boolean') return false; if (ntfy.enabled && (typeof ntfy.server !== 'string' || typeof ntfy.topic !== 'string')) { return false; } } // Validate requireToken if present if (r.requireToken !== undefined && typeof r.requireToken !== 'boolean') { return false; } return true; }