Browse Source

Added the capability for users to edit the AllowedIPs of the client

pull/997/head
xwvike 3 years ago
committed by Julien COSMAO
parent
commit
958be7033a
No known key found for this signature in database GPG Key ID: 69153421048939E2
  1. 11
      src/lib/Server.js
  2. 33
      src/lib/WireGuard.js
  3. 81
      src/www/index.html
  4. 8
      src/www/js/api.js
  5. 20
      src/www/js/app.js

11
src/lib/Server.js

@ -233,7 +233,16 @@ module.exports = class Server {
const { address } = await readBody(event); const { address } = await readBody(event);
await WireGuard.updateClientAddress({ clientId, address }); await WireGuard.updateClientAddress({ clientId, address });
return { success: true }; return { success: true };
})); }))
.put('/api/wireguard/client/:clientId/allowedips', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
throw createError({ status: 403 });
}
const { allowedIPs } = await readBody(event);
await WireGuard.updateClientAllowedIPs({ clientId, allowedIPs });
return { success: true };
}))
const safePathJoin = (base, target) => { const safePathJoin = (base, target) => {
// Manage web root (edge case) // Manage web root (edge case)

33
src/lib/WireGuard.js

@ -110,6 +110,15 @@ PostDown = ${WG_POST_DOWN}
for (const [clientId, client] of Object.entries(config.clients)) { for (const [clientId, client] of Object.entries(config.clients)) {
if (!client.enabled) continue; if (!client.enabled) continue;
let allowedIPs = '';
if(client?.allowedIPs){
for (const allowedIP of client.allowedIPs) {
allowedIPs += `,${allowedIP.address}/${allowedIP.cidr}`;
}
allowedIPs = allowedIPs.substring(1);
}else {
allowedIPs = client.address;
}
result += ` result += `
@ -146,8 +155,9 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
publicKey: client.publicKey, publicKey: client.publicKey,
createdAt: new Date(client.createdAt), createdAt: new Date(client.createdAt),
updatedAt: new Date(client.updatedAt), updatedAt: new Date(client.updatedAt),
allowedIPs: client.allowedIPs,
downloadableConfig: 'privateKey' in client, downloadableConfig: 'privateKey' in client,
allowedIPs: client?.allowedIPs?client.allowedIPs:[{type:'ipv4', address:client.address, cidr: 32}],
persistentKeepalive: null, persistentKeepalive: null,
latestHandshakeAt: null, latestHandshakeAt: null,
transferRx: null, transferRx: null,
@ -249,7 +259,7 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
}); });
if (!client) { if (!client) {
address = `${WG_DEFAULT_ADDRESS.replace('x', i)}/32`; address = `${WG_DEFAULT_ADDRESS.replace('x', i)}`;
break; break;
} }
} }
@ -271,6 +281,7 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
allowedIPs: [{type:'ipv4', address, cidr: 32}],
enabled: true, enabled: true,
}; };
@ -324,12 +335,28 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
if (!Util.isValidIPv4(address)) { if (!Util.isValidIPv4(address)) {
throw new ServerError(`Invalid Address: ${address}`, 400); throw new ServerError(`Invalid Address: ${address}`, 400);
} }
if(client?.allowedIPs){
let allowedIPs = client.allowedIPs
client.allowedIPs[0].address = address;
}
client.address = address; client.address = address;
client.updatedAt = new Date(); client.updatedAt = new Date();
await this.saveConfig(); await this.saveConfig();
} }
async updateClientAllowedIPs({ clientId, allowedIPs }) {
const client = await this.getClient({ clientId });
let ips = allowedIPs.map(item=>item.address)
ips.forEach(ip=>{
if (!Util.isValidIPv4(ip)) {
throw new ServerError(`Invalid Address: ${ip}`, 400);
}
})
client.allowedIPs = allowedIPs;
client.updatedAt = new Date();
await this.saveConfig();
}
async __reloadConfig() { async __reloadConfig() {
await this.__buildConfig(); await this.__buildConfig();

81
src/www/index.html

@ -305,6 +305,21 @@
<div class="rounded-full w-4 h-4 m-1 bg-white"></div> <div class="rounded-full w-4 h-4 m-1 bg-white"></div>
</div> </div>
<!-- AllowedIPs -->
<button
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition"
title="Edit Allowed IPs" @click="clientEditAllowedIPs = client">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 icon icon-tabler icon-tabler-list" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M9 6l11 0" />
<path d="M9 12l11 0" />
<path d="M9 18l11 0" />
<path d="M5 6l0 .01" />
<path d="M5 12l0 .01" />
<path d="M5 18l0 .01" />
</svg>
</button>
<!-- Show QR--> <!-- Show QR-->
<button :disabled="!client.downloadableConfig" <button :disabled="!client.downloadableConfig"
@ -548,6 +563,72 @@
</div> </div>
</div> </div>
</div> </div>
<!--edit allowedIPs dialog-->
<div v-if="clientEditAllowedIPs" class="fixed z-10 inset-0 overflow-y-auto">
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div class="absolute inset-0 bg-gray-500 dark:bg-black opacity-75 dark:opacity-50"></div>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div
class="inline-block align-bottom bg-white dark:bg-neutral-700 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full"
role="dialog" aria-modal="true" aria-labelledby="modal-headline">
<div class="bg-white dark:bg-neutral-700 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-circle-check" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M9 12l2 2l4 -4" /></svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-neutral-200" id="modal-headline">
Edit AllowedIPs
</h3>
<div class="mt-2 flex items-center flex-col">
<div class="w-full min-h-5 mb-2 flex flex-wrap">
<div class="bg-white tracking-wide h-[40px] border-2 border-gray-100 items-center rounded p-1 mb-2 mr-2 px-2 cursor-pointer flex w-fit" v-for="(item,index) in clientEditAllowedIPs.allowedIPs">
{{item.address+'/'+item.cidr}}
<div v-if="item.address!==clientEditAllowedIPs.address" class="inline-block border-l-2 pl-1 ml-2">
<svg @click="removeIP(index)" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-x" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" /></svg>
</div>
</div>
</div>
<div class="w-full flex items-center">
<div class="h-[40px] w-fit text-sm bg-white border-2 border-gray-100 rounded flex items-center px-2">
<input maxlength="3" v-model.number="userInputIP[0]" class="w-[30px] text-center outline-none border-none bg-transparent"/><span class="text-gray-700"></span>
<input maxlength="3" v-model.number="userInputIP[1]" class="w-[30px] text-center outline-none border-none bg-transparent"/><span class="text-gray-700"></span>
<input maxlength="3" v-model.number="userInputIP[2]" class="w-[30px] text-center outline-none border-none bg-transparent"/><span class="text-gray-700"></span>
<input maxlength="3" v-model.number="userInputIP[3]" class="w-[30px] text-center outline-none border-none bg-transparent"/><span class="text-gray-700">/</span>
<input maxlength="2" v-model.number="userInputIP[4]" class="w-[30px] text-center outline-none border-none bg-transparent"/>
</div>
<div @click="addNewIP" class="w-[40px] h-[40px] rounded bg-white border-2 border-gray-100 cursor-pointer ml-2 flex justify-center items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-plus" width="18" height="18" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 5l0 14" />
<path d="M5 12l14 0" /></svg>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 dark:bg-neutral-600 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button type="button" @click="updateClientAllowedIPs(clientEditAllowedIPs); clientEditAllowedIPs = null"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 dark:bg-red-600 text-base font-medium text-white dark:text-white hover:bg-red-700 dark:hover:bg-red-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
Save
</button>
<button type="button" @click="clientEditAllowedIPs = null"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 dark:border-neutral-500 shadow-sm px-4 py-2 bg-white dark:bg-neutral-500 text-base font-medium text-gray-700 dark:text-neutral-50 hover:bg-gray-50 dark:hover:bg-neutral-600 dark:hover:border-neutral-600 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
</div>
</div>
</div>
</div>
<div v-if="authenticated === false"> <div v-if="authenticated === false">
<h1 class="text-4xl font-medium my-16 text-gray-700 dark:text-neutral-200 text-center"> <h1 class="text-4xl font-medium my-16 text-gray-700 dark:text-neutral-200 text-center">

8
src/www/js/api.js

@ -137,6 +137,14 @@ class API {
body: { address }, body: { address },
}); });
} }
async updateClientAllowedIPs({ clientId, allowedIPs }) {
return this.call({
method: 'put',
path: `/wireguard/client/${clientId}/allowedIPs/`,
body: { allowedIPs },
});
}
async restoreConfiguration(file) { async restoreConfiguration(file) {
return this.call({ return this.call({

20
src/www/js/app.js

@ -64,6 +64,8 @@ new Vue({
clientEditNameId: null, clientEditNameId: null,
clientEditAddress: null, clientEditAddress: null,
clientEditAddressId: null, clientEditAddressId: null,
clientEditAllowedIPs: null,
userInputIP:[0,0,0,0,0],
qrcode: null, qrcode: null,
currentRelease: null, currentRelease: null,
@ -317,6 +319,24 @@ new Vue({
alert('Failed to load your file!'); alert('Failed to load your file!');
} }
}, },
updateClientAllowedIPs(client) {
this.api.updateClientAllowedIPs({ clientId: client.id, allowedIPs: client.allowedIPs })
.catch((err) => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error));
},
addNewIP(){
let address = this.userInputIP.slice(0,4).join('.');
let allowedIPs = this.clientEditAllowedIPs.allowedIPs;
let obj = {type:'ipv4', address, cidr: this.userInputIP[4]};
allowedIPs.push(obj);
this.clientEditAllowedIPs.allowedIPs = allowedIPs;
this.userInputIP = [0,0,0,0,0];
},
removeIP(index){
let allowedIPs = this.clientEditAllowedIPs.allowedIPs;
allowedIPs.splice(index,1);
this.clientEditAllowedIPs.allowedIPs = allowedIPs;
},
toggleTheme() { toggleTheme() {
const themes = ['light', 'dark', 'auto']; const themes = ['light', 'dark', 'auto'];
const currentIndex = themes.indexOf(this.uiTheme); const currentIndex = themes.indexOf(this.uiTheme);

Loading…
Cancel
Save