105 lines
3.4 KiB
TypeScript
105 lines
3.4 KiB
TypeScript
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;
|
|
}
|
|
}
|