import * as plugins from './smartvpn.plugins.js'; import type { IVpnClientConfig, IVpnServerConfig, } from './smartvpn.interfaces.js'; /** * VPN configuration loader, saver, and validator. */ export class VpnConfig { /** * Validate a client config object. Throws on invalid config. */ public static validateClientConfig(config: IVpnClientConfig): void { if (!config.serverUrl) { throw new Error('VpnConfig: serverUrl is required'); } if (!config.serverUrl.startsWith('wss://') && !config.serverUrl.startsWith('ws://')) { throw new Error('VpnConfig: serverUrl must start with wss:// or ws://'); } if (!config.serverPublicKey) { throw new Error('VpnConfig: serverPublicKey is required'); } if (config.mtu !== undefined && (config.mtu < 576 || config.mtu > 65535)) { throw new Error('VpnConfig: mtu must be between 576 and 65535'); } if (config.keepaliveIntervalSecs !== undefined && config.keepaliveIntervalSecs < 1) { throw new Error('VpnConfig: keepaliveIntervalSecs must be >= 1'); } if (config.dns) { for (const dns of config.dns) { if (!VpnConfig.isValidIp(dns)) { throw new Error(`VpnConfig: invalid DNS address: ${dns}`); } } } } /** * Validate a server config object. Throws on invalid config. */ public static validateServerConfig(config: IVpnServerConfig): void { if (!config.listenAddr) { throw new Error('VpnConfig: listenAddr is required'); } if (!config.privateKey) { throw new Error('VpnConfig: privateKey is required'); } if (!config.publicKey) { throw new Error('VpnConfig: publicKey is required'); } if (!config.subnet) { throw new Error('VpnConfig: subnet is required'); } if (!VpnConfig.isValidSubnet(config.subnet)) { throw new Error(`VpnConfig: invalid subnet: ${config.subnet}`); } if (config.mtu !== undefined && (config.mtu < 576 || config.mtu > 65535)) { throw new Error('VpnConfig: mtu must be between 576 and 65535'); } if (config.keepaliveIntervalSecs !== undefined && config.keepaliveIntervalSecs < 1) { throw new Error('VpnConfig: keepaliveIntervalSecs must be >= 1'); } } /** * Load a config from a JSON file. */ public static async loadFromFile(filePath: string): Promise { const content = await plugins.fs.promises.readFile(filePath, 'utf-8'); return JSON.parse(content) as T; } /** * Save a config to a JSON file. */ public static async saveToFile(filePath: string, config: T): Promise { const content = JSON.stringify(config, null, 2); await plugins.fs.promises.writeFile(filePath, content, 'utf-8'); } /** * Basic IP address validation. */ private static isValidIp(ip: string): boolean { const parts = ip.split('.'); if (parts.length !== 4) return false; return parts.every((part) => { const num = parseInt(part, 10); return !isNaN(num) && num >= 0 && num <= 255 && String(num) === part; }); } /** * Basic subnet validation (CIDR notation). */ private static isValidSubnet(subnet: string): boolean { const [ip, prefix] = subnet.split('/'); if (!ip || !prefix) return false; if (!VpnConfig.isValidIp(ip)) return false; const prefixNum = parseInt(prefix, 10); return !isNaN(prefixNum) && prefixNum >= 0 && prefixNum <= 32; } }