diff --git a/changelog.md b/changelog.md index ba25349..d0f7a18 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 2026-03-31 - 11.20.1 - fix(vpn-manager) +persist WireGuard private keys for valid client exports and QR codes + +- Store each client's WireGuard private key when creating and rotating keys. +- Inject the stored private key into exported WireGuard configs so generated configs are complete and scannable. + ## 2026-03-30 - 11.20.0 - feat(vpn-ui) add QR code export for WireGuard client configurations diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 5bb7f4f..524186f 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.20.0', + version: '11.20.1', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts/vpn/classes.vpn-manager.ts b/ts/vpn/classes.vpn-manager.ts index 966d689..f7837fd 100644 --- a/ts/vpn/classes.vpn-manager.ts +++ b/ts/vpn/classes.vpn-manager.ts @@ -46,6 +46,8 @@ interface IPersistedClient { assignedIp?: string; noisePublicKey: string; wgPublicKey: string; + /** WireGuard private key — stored so exports and QR codes produce valid configs */ + wgPrivateKey?: string; createdAt: number; updatedAt: number; expiresAt?: string; @@ -188,7 +190,7 @@ export class VpnManager { description: opts.description, }); - // Persist client entry (without private keys) + // Persist client entry (including WG private key for export/QR) const persisted: IPersistedClient = { clientId: bundle.entry.clientId, enabled: bundle.entry.enabled ?? true, @@ -197,6 +199,7 @@ export class VpnManager { assignedIp: bundle.entry.assignedIp, noisePublicKey: bundle.entry.publicKey, wgPublicKey: bundle.entry.wgPublicKey || '', + wgPrivateKey: bundle.secrets?.wgPrivateKey, createdAt: Date.now(), updatedAt: Date.now(), expiresAt: bundle.entry.expiresAt, @@ -265,11 +268,12 @@ export class VpnManager { if (!this.vpnServer) throw new Error('VPN server not running'); const bundle = await this.vpnServer.rotateClientKey(clientId); - // Update persisted entry with new public keys + // Update persisted entry with new keys (including private key for export/QR) const client = this.clients.get(clientId); if (client) { client.noisePublicKey = bundle.entry.publicKey; client.wgPublicKey = bundle.entry.wgPublicKey || ''; + client.wgPrivateKey = bundle.secrets?.wgPrivateKey; client.updatedAt = Date.now(); await this.persistClient(client); } @@ -278,11 +282,24 @@ export class VpnManager { } /** - * Export a client config (without secrets). + * Export a client config. Injects the stored WG private key for complete configs. */ public async exportClientConfig(clientId: string, format: 'smartvpn' | 'wireguard'): Promise { if (!this.vpnServer) throw new Error('VPN server not running'); - return this.vpnServer.exportClientConfig(clientId, format); + let config = await this.vpnServer.exportClientConfig(clientId, format); + + // Inject stored WG private key so exports produce valid, scannable configs + if (format === 'wireguard') { + const persisted = this.clients.get(clientId); + if (persisted?.wgPrivateKey) { + config = config.replace( + '[Interface]\n', + `[Interface]\nPrivateKey = ${persisted.wgPrivateKey}\n`, + ); + } + } + + return config; } // ── Tag-based access control ─────────────────────────────────────────── diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 5bb7f4f..524186f 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.20.0', + version: '11.20.1', description: 'A multifaceted routing service handling mail and SMS delivery functions.' }