diff --git a/src/app/components/Client/Address.vue b/src/app/components/Client/Address.vue deleted file mode 100644 index ca3e8dc4..00000000 --- a/src/app/components/Client/Address.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/src/app/components/Client/Address4.vue b/src/app/components/Client/Address4.vue new file mode 100644 index 00000000..e4cdfde5 --- /dev/null +++ b/src/app/components/Client/Address4.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/app/components/Client/Client.vue b/src/app/components/Client/Client.vue index 71bd6b4c..c1a9295e 100644 --- a/src/app/components/Client/Client.vue +++ b/src/app/components/Client/Client.vue @@ -12,7 +12,7 @@
- + { event, validateZod(clientIdType) ); - const { address } = await readValidatedBody(event, validateZod(addressType)); - await WireGuard.updateClientAddress({ clientId, address }); + const { address4 } = await readValidatedBody( + event, + validateZod(address4Type) + ); + await WireGuard.updateClientAddress({ clientId, address4 }); return { success: true }; }); diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index fb83b5f1..99093c0d 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -21,8 +21,8 @@ class WireGuard { async #saveWireguardConfig() { const system = await Database.getSystem(); const clients = await Database.getClients(); - const cidrBlock = parseCidr(system.userConfig.addressRange).prefix; - const cidr6Block = parseCidr(system.userConfig.addressRange6).prefix; + const cidr4Block = parseCidr(system.userConfig.address4Range).prefix; + const cidr6Block = parseCidr(system.userConfig.address6Range).prefix; let result = ` # Note: Do not edit this file directly. # Your changes will be overwritten! @@ -30,7 +30,7 @@ class WireGuard { # Server [Interface] PrivateKey = ${system.interface.privateKey} -Address = ${system.interface.address}/${cidrBlock}, ${system.interface.address6}/${cidr6Block} +Address = ${system.interface.address4}/${cidr4Block}, ${system.interface.address6}/${cidr6Block} ListenPort = ${system.wgPort} PreUp = ${system.iptables.PreUp} PostUp = ${system.iptables.PostUp} @@ -47,7 +47,7 @@ PostDown = ${system.iptables.PostDown} [Peer] PublicKey = ${client.publicKey} PresharedKey = ${client.preSharedKey} -AllowedIPs = ${client.address}/32, ${client.address6}/128`; +AllowedIPs = ${client.address4}/32, ${client.address6}/128`; } DEBUG('Config saving...'); @@ -69,7 +69,7 @@ AllowedIPs = ${client.address}/32, ${client.address6}/128`; id: clientId, name: client.name, enabled: client.enabled, - address: client.address, + address4: client.address4, address6: client.address6, publicKey: client.publicKey, createdAt: new Date(client.createdAt), @@ -136,11 +136,13 @@ AllowedIPs = ${client.address}/32, ${client.address6}/128`; async getClientConfiguration({ clientId }: { clientId: string }) { const system = await Database.getSystem(); const client = await this.getClient({ clientId }); + const cidr4Block = parseCidr(system.userConfig.address4Range).prefix; + const cidr6Block = parseCidr(system.userConfig.address6Range).prefix; return ` [Interface] PrivateKey = ${client.privateKey} -Address = ${client.address} +Address = ${client.address4}/${cidr4Block}, ${client.address6}/${cidr6Block} DNS = ${system.userConfig.defaultDns.join(', ')} MTU = ${system.userConfig.mtu} @@ -167,10 +169,6 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; name: string; expireDate: string | null; }) { - if (!name) { - throw new Error('Missing: Name'); - } - const system = await Database.getSystem(); const clients = await Database.getClients(); @@ -181,27 +179,31 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; const preSharedKey = await exec('wg genpsk'); // Calculate next IP - const cidr = parseCidr(system.userConfig.addressRange); - let address; - for (let i = cidr.start + 2n; i <= cidr.end - 1n; i++) { - const currentIp = stringifyIp({ number: i, version: 4 }); + const cidr4 = parseCidr(system.userConfig.address4Range); + let address4; + for (let i = cidr4.start + 2n; i <= cidr4.end - 1n; i++) { + const currentIp4 = stringifyIp({ number: i, version: 4 }); const client = Object.values(clients).find((client) => { - return client.address === currentIp; + return client.address4 === currentIp4; }); if (!client) { - address = currentIp; + address4 = currentIp4; break; } } - if (!address) { - throw new Error('Maximum number of clients reached.'); + if (!address4) { + throw createError({ + statusCode: 409, + statusMessage: 'Maximum number of clients reached.', + data: { cause: 'IPv4 Address Pool exhausted' }, + }); } - const cidr6 = parseCidr(system.userConfig.addressRange6); + const cidr6 = parseCidr(system.userConfig.address6Range); let address6; - for (let i = cidr6.start + 2n; i <= cidr.end - 1n; i++) { + for (let i = cidr6.start + 2n; i <= cidr6.end - 1n; i++) { const currentIp6 = stringifyIp({ number: i, version: 6 }); const client = Object.values(clients).find((client) => { return client.address6 === currentIp6; @@ -214,7 +216,11 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; } if (!address6) { - throw new Error('Maximum number of clients reached.'); + throw createError({ + statusCode: 409, + statusMessage: 'Maximum number of clients reached.', + data: { cause: 'IPv6 Address Pool exhausted' }, + }); } // Create Client @@ -223,7 +229,7 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; const client: NewClient = { id, name, - address, + address4, address6, privateKey, publicKey, @@ -298,19 +304,19 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; async updateClientAddress({ clientId, - address, + address4, }: { clientId: string; - address: string; + address4: string; }) { - if (!isIPv4(address)) { + if (!isIPv4(address4)) { throw createError({ statusCode: 400, - statusMessage: `Invalid Address: ${address}`, + statusMessage: `Invalid Address: ${address4}`, }); } - await Database.updateClientAddress(clientId, address); + await Database.updateClientAddress4(clientId, address4); await this.saveConfig(); } @@ -450,9 +456,9 @@ Endpoint = ${system.wgHost}:${system.wgConfigPort}`; if (client.endpoint !== null) { wireguardConnectedPeersCount++; } - wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferTx)}\n`; - wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${Number(client.transferRx)}\n`; - wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address="${client.address}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`; + wireguardSentBytes += `wireguard_sent_bytes{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${Number(client.transferTx)}\n`; + wireguardReceivedBytes += `wireguard_received_bytes{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${Number(client.transferRx)}\n`; + wireguardLatestHandshakeSeconds += `wireguard_latest_handshake_seconds{interface="wg0",enabled="${client.enabled}",address4="${client.address4}",address6="${client.address6}",name="${client.name}"} ${client.latestHandshakeAt ? (new Date().getTime() - new Date(client.latestHandshakeAt).getTime()) / 1000 : 0}\n`; } let returnText = '# HELP wg-easy and wireguard metrics\n'; diff --git a/src/server/utils/types.ts b/src/server/utils/types.ts index c0f54988..a5acbbcc 100644 --- a/src/server/utils/types.ts +++ b/src/server/utils/types.ts @@ -13,8 +13,8 @@ const id = z .uuid('Client ID must be a valid UUID') .pipe(safeStringRefine); -const address = z - .string({ message: 'Address must be a valid string' }) +const address4 = z + .string({ message: 'IPv4 Address must be a valid string' }) .pipe(safeStringRefine); const name = z @@ -54,9 +54,9 @@ export const clientIdType = z.object( { message: "This shouldn't happen" } ); -export const addressType = z.object( +export const address4Type = z.object( { - address: address, + address4: address4, }, { message: 'Body must be a valid object' } ); diff --git a/src/services/database/lowdb.ts b/src/services/database/lowdb.ts index fc68171b..71086c2a 100644 --- a/src/services/database/lowdb.ts +++ b/src/services/database/lowdb.ts @@ -102,6 +102,7 @@ export default class LowDB extends DatabaseProvider { id: crypto.randomUUID(), password: hashPassword(password), username, + name: 'Administrator', role: isUserEmpty ? 'ADMIN' : 'CLIENT', enabled: true, createdAt: now, @@ -175,17 +176,17 @@ export default class LowDB extends DatabaseProvider { }); } - async updateClientAddress(id: string, address: string) { - DEBUG('Update Client Address'); + async updateClientAddress4(id: string, address4: string) { + DEBUG('Update Client Address4'); await this.#db.update((data) => { if (data.clients[id]) { - data.clients[id].address = address; + data.clients[id].address4 = address4; } }); } async updateClientExpirationDate(id: string, expirationDate: string | null) { - DEBUG('Update Client Address'); + DEBUG('Update Client Expiration Date'); await this.#db.update((data) => { if (data.clients[id]) { data.clients[id].expiresAt = expirationDate; @@ -194,7 +195,7 @@ export default class LowDB extends DatabaseProvider { } async deleteOneTimeLink(id: string) { - DEBUG('Update Client Address'); + DEBUG('Delete Client One Time Link'); await this.#db.update((data) => { if (data.clients[id]) { data.clients[id].oneTimeLink = null; @@ -203,7 +204,7 @@ export default class LowDB extends DatabaseProvider { } async createOneTimeLink(id: string, oneTimeLink: OneTimeLink) { - DEBUG('Update Client Address'); + DEBUG('Create Client One Time Link'); await this.#db.update((data) => { if (data.clients[id]) { data.clients[id].oneTimeLink = oneTimeLink; diff --git a/src/services/database/migrations/1.ts b/src/services/database/migrations/1.ts index ed944bf0..79da292b 100644 --- a/src/services/database/migrations/1.ts +++ b/src/services/database/migrations/1.ts @@ -9,17 +9,17 @@ export async function run1(db: Low) { const publicKey = await exec(`echo ${privateKey} | wg pubkey`, { log: 'echo ***hidden*** | wg pubkey', }); - const addressRange = '10.8.0.0/24'; - const addressRange6 = 'fdcc:ad94:bacf:61a4::cafe:0/112'; - const cidr = parseCidr(addressRange); - const cidr6 = parseCidr(addressRange6); + const address4Range = '10.8.0.0/24'; + const address6Range = 'fdcc:ad94:bacf:61a4::cafe:0/112'; + const cidr4 = parseCidr(address4Range); + const cidr6 = parseCidr(address6Range); const database: Database = { migrations: [], system: { interface: { privateKey: privateKey, publicKey: publicKey, - address: stringifyIp({ number: cidr.start + 1n, version: 4 }), + address4: stringifyIp({ number: cidr4.start + 1n, version: 4 }), address6: stringifyIp({ number: cidr6.start + 1n, version: 6 }), }, sessionTimeout: 3600, // 1 hour @@ -27,8 +27,8 @@ export async function run1(db: Low) { userConfig: { mtu: 1420, persistentKeepalive: 0, - addressRange: addressRange, - addressRange6: addressRange6, + address4Range: address4Range, + address6Range: address6Range, defaultDns: ['1.1.1.1', '2606:4700:4700::1111'], allowedIps: ['0.0.0.0/0', '::/0'], }, @@ -73,11 +73,11 @@ export async function run1(db: Low) { // TODO: use variables inside up/down script // TODO: properly check if ipv6 support database.system.iptables.PostUp = ` -iptables -t nat -A POSTROUTING -s ${database.system.userConfig.addressRange} -o ${database.system.wgDevice} -j MASQUERADE; +iptables -t nat -A POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; -ip6tables -t nat -A POSTROUTING -s ${database.system.userConfig.addressRange6} -o ${database.system.wgDevice} -j MASQUERADE; +ip6tables -t nat -A POSTROUTING -s ${database.system.userConfig.address6Range} -o ${database.system.wgDevice} -j MASQUERADE; ip6tables -A INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT; @@ -86,11 +86,11 @@ ip6tables -A FORWARD -o wg0 -j ACCEPT; .join(' '); database.system.iptables.PostDown = ` -iptables -t nat -D POSTROUTING -s ${database.system.userConfig.addressRange} -o ${database.system.wgDevice} -j MASQUERADE; +iptables -t nat -D POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE; iptables -D INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; -ip6tables -t nat -D POSTROUTING -s ${database.system.userConfig.addressRange6} -o ${database.system.wgDevice} -j MASQUERADE; +ip6tables -t nat -D POSTROUTING -s ${database.system.userConfig.address6Range} -o ${database.system.wgDevice} -j MASQUERADE; ip6tables -D INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT; diff --git a/src/services/database/repositories/client.ts b/src/services/database/repositories/client.ts index eb25eacf..3d641c23 100644 --- a/src/services/database/repositories/client.ts +++ b/src/services/database/repositories/client.ts @@ -7,7 +7,7 @@ export type OneTimeLink = { export type Client = { id: string; name: string; - address: string; + address4: string; address6: string; privateKey: string; publicKey: string; @@ -38,7 +38,7 @@ export interface ClientRepository { deleteClient(id: string): Promise; toggleClient(id: string, enable: boolean): Promise; updateClientName(id: string, name: string): Promise; - updateClientAddress(id: string, address: string): Promise; + updateClientAddress4(id: string, address4: string): Promise; updateClientExpirationDate( id: string, expirationDate: string | null diff --git a/src/services/database/repositories/database.ts b/src/services/database/repositories/database.ts index 3c8c1e5f..2f90f15f 100644 --- a/src/services/database/repositories/database.ts +++ b/src/services/database/repositories/database.ts @@ -59,7 +59,7 @@ export abstract class DatabaseProvider abstract deleteClient(id: string): Promise; abstract toggleClient(id: string, enable: boolean): Promise; abstract updateClientName(id: string, name: string): Promise; - abstract updateClientAddress(id: string, address: string): Promise; + abstract updateClientAddress4(id: string, address4: string): Promise; abstract updateClientExpirationDate( id: string, expirationDate: string | null diff --git a/src/services/database/repositories/system.ts b/src/services/database/repositories/system.ts index e1990a90..aba8129a 100644 --- a/src/services/database/repositories/system.ts +++ b/src/services/database/repositories/system.ts @@ -12,15 +12,15 @@ export type IpTables = { export type WGInterface = { privateKey: string; publicKey: string; - address: string; + address4: string; address6: string; }; export type WGConfig = { mtu: number; persistentKeepalive: number; - addressRange: string; - addressRange6: string; + address4Range: string; + address6Range: string; defaultDns: string[]; allowedIps: string[]; }; diff --git a/src/services/database/repositories/user.ts b/src/services/database/repositories/user.ts index 260b113a..6a9c1bb5 100644 --- a/src/services/database/repositories/user.ts +++ b/src/services/database/repositories/user.ts @@ -16,11 +16,7 @@ export type User = { role: ROLE; username: string; password: string; - name?: string; - address?: string; - privateKey?: string; - publicKey?: string; - preSharedKey?: string; + name: string; /** ISO String */ createdAt: string; /** ISO String */