This commit is contained in:
2026-02-27 10:18:23 +00:00
commit 3f63d19173
36 changed files with 14285 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
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<T>(filePath: string): Promise<T> {
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<T>(filePath: string, config: T): Promise<void> {
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;
}
}