Browse Source

Merge branch 'master' of https://github.com/utlimate/wg-easy

pull/2294/head
SachinAsus 2 years ago
parent
commit
a898d9bee4
  1. 3
      .github/CODEOWNERS
  2. 4
      .github/ISSUE_TEMPLATE/bug_report.md
  3. 3
      .github/workflows/deploy-development.yml
  4. 2
      .github/workflows/deploy-nightly.yml
  5. 2
      .github/workflows/deploy-pr.yml
  6. 2
      .github/workflows/deploy.yml
  7. 3
      .github/workflows/lint.yml
  8. 3
      .github/workflows/npm-update-bot.yml
  9. 10
      Dockerfile
  10. 34
      How_to_generate_an_bcrypt_hash.md
  11. 56
      README.md
  12. BIN
      assets/screenshot.png
  13. 5
      docker-compose.yml
  14. 88
      src/lib/Server.js
  15. 123
      src/lib/WireGuard.js
  16. 249
      src/package-lock.json
  17. 13
      src/package.json
  18. 54
      src/wgpw.mjs
  19. 5
      src/wgpw.sh
  20. 15
      src/www/css/app.css
  21. 18
      src/www/index.html
  22. 8
      src/www/js/api.js
  23. 19
      src/www/js/app.js
  24. 46
      src/www/js/i18n.js
  25. 8
      src/www/js/vendor/apexcharts.min.js

3
.github/CODEOWNERS

@ -1,2 +1,3 @@
# Copyright (c) Emile Nijssen
# Copyright (c) Emile Nijssen (WeeJeWel)
# Founder and Codeowner of WireGuard Easy (wg-easy)
* @WeeJeWel

4
.github/ISSUE_TEMPLATE/bug_report.md

@ -24,13 +24,13 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- OS: [e.g. macOS 12.1]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- OS: [e.g. iOS 8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]

3
.github/workflows/deploy-development.yml

@ -2,7 +2,6 @@ name: Build & Publish Development
on:
workflow_dispatch:
pull_request:
jobs:
deploy:
@ -31,7 +30,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & Publish Docker Image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
push: true
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8

2
.github/workflows/deploy-nightly.yml

@ -32,7 +32,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & Publish Docker Image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
push: true
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8

2
.github/workflows/deploy-pr.yml

@ -31,7 +31,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker Image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
push: false
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8

2
.github/workflows/deploy.yml

@ -36,7 +36,7 @@ jobs:
run: echo RELEASE=$(cat ./src/package.json | jq -r .release) >> $GITHUB_ENV
- name: Build & Publish Docker Image
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
push: true
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8

3
.github/workflows/lint.yml

@ -21,9 +21,6 @@ jobs:
node-version: '20'
check-latest: true
cache: 'npm'
cache-dependency-path: |
package-lock.json
src/package-lock.json
- name: npm run lint
run: |

3
.github/workflows/npm-update-bot.yml

@ -23,9 +23,6 @@ jobs:
node-version: '20'
check-latest: true
cache: 'npm'
cache-dependency-path: |
package-lock.json
src/package-lock.json
- name: Bot 🤖 "Updating NPM Packages..."
run: |

10
Dockerfile

@ -26,6 +26,10 @@ COPY --from=build_node_modules /app /app
# than what runs inside of docker.
COPY --from=build_node_modules /node_modules /node_modules
# Copy the needed wg-password scripts
COPY --from=build_node_modules /app/wgpw.sh /bin/wgpw
RUN chmod +x /bin/wgpw
# Install Linux packages
RUN apk add --no-cache \
dpkg \
@ -37,13 +41,9 @@ RUN apk add --no-cache \
# Use iptables-legacy
RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save
# Expose Ports (If needed on buildtime)
#EXPOSE 51820/udp
#EXPOSE 51821/tcp
# Set Environment
ENV DEBUG=Server,WireGuard
# Run Web UI
WORKDIR /app
CMD ["/usr/bin/dumb-init", "node", "server.js"]
CMD ["/usr/bin/dumb-init", "node", "server.js"]

34
How_to_generate_an_bcrypt_hash.md

@ -0,0 +1,34 @@
# wg-password
`wg-password` (wgpw) is a script that generates bcrypt password hashes for use with `wg-easy`, enhancing security by requiring passwords.
## Features
- Generate bcrypt password hashes.
- Easily integrate with `wg-easy` to enforce password requirements.
## Usage with Docker
To generate a bcrypt password hash using Docker, run the following command:
```sh
docker run ghcr.io/wg-easy/wg-easy wgpw YOUR_PASSWORD
PASSWORD_HASH='$2b$12$coPqCsPtcFO.Ab99xylBNOW4.Iu7OOA2/ZIboHN6/oyxca3MWo7fW' // litteraly YOUR_PASSWORD
```
## Important
Make sure to enclose your password in single quotes when you run a linux host.
```bash
$ echo $2b$12$coPqCsPtcF
b2
$ echo "$2b$12$coPqCsPtcF"
b2
$ echo '$2b$12$coPqCsPtcF'
$2b$12$coPqCsPtcF
```
## LICENSE
[wg-easy license](./LICENSE)

56
README.md

@ -30,6 +30,17 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
* A host with a kernel that supports WireGuard (all modern kernels).
* A host with Docker installed.
## Versions
We provide more then 1 docker image to get, this will help you decide which one is best for you.
| tag | Branch | Example | Description |
| - | - | - | - |
| `latest` | production | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | stable as possbile get bug fixes quickly when needed, deployed against `production`. |
| `13` | production | `ghcr.io/wg-easy/wg-easy:13` | same as latest, stick to a version tag. |
| `nightly` | master | `ghcr.io/wg-easy/wg-easy:nightly` | mostly unstable gets frequent package and code updates, deployed against `master`. |
| `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs before landing into `master`. |
## Installation
### 1. Install Docker
@ -53,7 +64,7 @@ To automatically install & run wg-easy, simply run:
--name=wg-easy \
-e LANG=de \
-e WG_HOST=<🚨YOUR_SERVER_IP> \
-e PASSWORD=<🚨YOUR_ADMIN_PASSWORD> \
-e PASSWORD_HASH=<🚨YOUR_ADMIN_PASSWORD_HASH> \
-e PORT=51821 \
-e WG_PORT=51820 \
-v ~/.wg-easy:/etc/wireguard \
@ -69,7 +80,7 @@ To automatically install & run wg-easy, simply run:
> 💡 Replace `YOUR_SERVER_IP` with your WAN IP, or a Dynamic DNS hostname.
>
> 💡 Replace `YOUR_ADMIN_PASSWORD` with a password to log in on the Web UI.
> 💡 Replace `YOUR_ADMIN_PASSWORD_HASH` with a bcrypt password hash to log in on the Web UI. See [How_to_generate_an_bcrypt_hash.md](./How_to_generate_an_bcrypt_hash.md) for know how generate the hash.
The Web UI will now be available on `http://0.0.0.0:51821`.
@ -87,25 +98,28 @@ Are you enjoying this project? [Buy Emile a beer!](https://github.com/sponsors/W
These options can be configured by setting environment variables using `-e KEY="VALUE"` in the `docker run` command.
| Env | Default | Example | Description |
| - | - | - | - |
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
| `WEBUI_HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
| `PASSWORD` | - | `foobar123` | When set, requires a password when logging in to the Web UI. |
| `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. |
| `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. |
| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. |
| `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. |
| `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. |
| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. |
| `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. If set to blank value, clients will not use any DNS. |
| `WG_ALLOWED_IPS` | `0.0.0.0/0, ::/0` | `192.168.15.0/24, 10.0.1.0/24` | Allowed IPs clients will use. |
| `WG_PRE_UP` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L19) for the default value. |
| `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. |
| `WG_PRE_DOWN` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L27) for the default value. |
| `WG_POST_DOWN` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L28) for the default value. |
| `LANG` | `en` | `de` | Web UI language (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi). |
| `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI |
| Env | Default | Example | Description |
| - | - | - |------------------------------------------------------------------------------------------------------------------------------------------------------|
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
| `WEBUI_HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
| `PASSWORD_HASH` | - | `$2y$05$Ci...` | When set, requires a password when logging in to the Web UI. See [How to generate an bcrypt hash.md]("https://github.com/wg-easy/wg-easy/blob/master/How_to_generate_an_bcrypt_hash.md") for know how generate the hash. |
| `PASSWORD` (deprecated) | - | `foobar123` | When set, requires a password when logging in to the Web UI. *(Not used if `PASSWORD_HASH` is set)* |
| `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. |
| `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. |
| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. |
| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistant Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy)
| `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. |
| `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. |
| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. |
| `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. If set to blank value, clients will not use any DNS. |
| `WG_ALLOWED_IPS` | `0.0.0.0/0, ::/0` | `192.168.15.0/24, 10.0.1.0/24` | Allowed IPs clients will use. |
| `WG_PRE_UP` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L19) for the default value. |
| `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. |
| `WG_PRE_DOWN` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L27) for the default value. |
| `WG_POST_DOWN` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L28) for the default value. |
| `LANG` | `en` | `de` | Web UI language (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi). |
| `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI |
| `UI_CHART_TYPE` | `0` | `1` | UI_CHART_TYPE=0 # Charts disabled, UI_CHART_TYPE=1 # Line chart, UI_CHART_TYPE=2 # Area chart, UI_CHART_TYPE=3 # Bar chart |
> If you change `WG_PORT`, make sure to also change the exposed port.

BIN
assets/screenshot.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 104 KiB

5
docker-compose.yml

@ -12,9 +12,11 @@ services:
- WG_HOST=raspberrypi.local
# Optional:
# - PASSWORD=foobar123
# - PASSWORD=foobar123 (deprecated, see readme)
# - PASSWORD_HASH=$$2y$$10$$hBCoykrB95WSzuV4fafBzOHWKu9sbyVa34GJr8VV5R/pIelfEMYyG (needs double $$, hash of 'foobar123'; see "How_to_generate_an_bcrypt_hash.md" for generate the hash)
# - PORT=51821
# - WG_PORT=51820
# - WG_CONFIG_PORT=92820
# - WG_DEFAULT_ADDRESS=10.8.0.x
# - WG_DEFAULT_DNS=1.1.1.1
# - WG_MTU=1420
@ -38,6 +40,7 @@ services:
cap_add:
- NET_ADMIN
- SYS_MODULE
# - NET_RAW # ⚠️ Uncomment if using Podman
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1

88
src/lib/Server.js

@ -4,7 +4,7 @@ const bcrypt = require('bcryptjs');
const crypto = require('node:crypto');
const { createServer } = require('node:http');
const { stat, readFile } = require('node:fs/promises');
const { join } = require('node:path');
const { resolve, sep } = require('node:path');
const expressSession = require('express-session');
const debug = require('debug')('Server');
@ -28,12 +28,34 @@ const {
PORT,
WEBUI_HOST,
RELEASE,
PASSWORD,
PASSWORD_HASH,
LANG,
UI_TRAFFIC_STATS,
UI_CHART_TYPE,
} = require('../config');
const requiresPassword = !!PASSWORD_HASH;
/**
* Checks if `password` matches the PASSWORD_HASH.
*
* If environment variable is not set, the password is always invalid.
*
* @param {string} password String to test
* @returns {boolean} true if matching environment, otherwise false
*/
const isPasswordValid = (password) => {
if (typeof password !== 'string') {
return false;
}
if (PASSWORD_HASH) {
return bcrypt.compareSync(password, PASSWORD_HASH);
}
return false;
};
module.exports = class Server {
constructor() {
@ -72,7 +94,6 @@ module.exports = class Server {
// Authentication
.get('/api/session', defineEventHandler((event) => {
const requiresPassword = !!process.env.PASSWORD;
const authenticated = requiresPassword
? !!(event.node.req.session && event.node.req.session.authenticated)
: true;
@ -85,14 +106,16 @@ module.exports = class Server {
.post('/api/session', defineEventHandler(async (event) => {
const { password } = await readBody(event);
if (typeof password !== 'string') {
if (!requiresPassword) {
// if no password is required, the API should never be called.
// Do not automatically authenticate the user.
throw createError({
status: 401,
message: 'Missing: Password',
message: 'Invalid state',
});
}
if (password !== PASSWORD) {
if (!isPasswordValid(password)) {
throw createError({
status: 401,
message: 'Incorrect Password',
@ -104,13 +127,13 @@ module.exports = class Server {
debug(`New Session: ${event.node.req.session.id}`);
return { succcess: true };
return { success: true };
}));
// WireGuard
app.use(
fromNodeMiddleware((req, res, next) => {
if (!PASSWORD || !req.url.startsWith('/api/')) {
if (!requiresPassword || !req.url.startsWith('/api/')) {
return next();
}
@ -119,7 +142,7 @@ module.exports = class Server {
}
if (req.url.startsWith('/api/') && req.headers['authorization']) {
if (bcrypt.compareSync(req.headers['authorization'], bcrypt.hashSync(PASSWORD, 10))) {
if (isPasswordValid(req.headers['authorization'])) {
return next();
}
return res.status(401).json({
@ -212,15 +235,58 @@ module.exports = class Server {
return { success: true };
}));
const safePathJoin = (base, target) => {
// Manage web root (edge case)
if (target === '/') {
return `${base}${sep}`;
}
// Prepend './' to prevent absolute paths
const targetPath = `.${sep}${target}`;
// Resolve the absolute path
const resolvedPath = resolve(base, targetPath);
// Check if resolvedPath is a subpath of base
if (resolvedPath.startsWith(`${base}${sep}`)) {
return resolvedPath;
}
throw createError({
status: 400,
message: 'Bad Request',
});
};
// backup_restore
const router3 = createRouter();
app.use(router3);
router3
.get('/api/wireguard/backup', defineEventHandler(async (event) => {
const config = await WireGuard.backupConfiguration();
setHeader(event, 'Content-Disposition', 'attachment; filename="wg0.json"');
setHeader(event, 'Content-Type', 'text/json');
return config;
}))
.put('/api/wireguard/restore', defineEventHandler(async (event) => {
const { file } = await readBody(event);
await WireGuard.restoreConfiguration(file);
return { success: true };
}));
// Static assets
const publicDir = '/home/ubuntu/wg-easy/src/www';
app.use(
defineEventHandler((event) => {
return serveStatic(event, {
getContents: (id) => readFile(join(publicDir, id)),
getContents: (id) => {
return readFile(safePathJoin(publicDir, id));
},
getMeta: async (id) => {
const stats = await stat(join(publicDir, id)).catch(() => {});
const filePath = safePathJoin(publicDir, id);
const stats = await stat(filePath).catch(() => {});
if (!stats || !stats.isFile()) {
return;
}

123
src/lib/WireGuard.js

@ -1,10 +1,9 @@
'use strict';
const fs = require('fs').promises;
const fs = require('node:fs/promises');
const path = require('path');
const debug = require('debug')('WireGuard');
const uuid = require('uuid');
const crypto = require('node:crypto');
const QRCode = require('qrcode');
const Util = require('./Util');
@ -14,6 +13,7 @@ const {
WG_PATH,
WG_HOST,
WG_PORT,
WG_CONFIG_PORT,
WG_MTU,
WG_DEFAULT_DNS,
WG_DEFAULT_ADDRESS,
@ -27,54 +27,60 @@ const {
module.exports = class WireGuard {
async __buildConfig() {
this.__configPromise = Promise.resolve().then(async () => {
if (!WG_HOST) {
throw new Error('WG_HOST Environment Variable Not Set!');
}
debug('Loading configuration...');
let config;
try {
config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8');
config = JSON.parse(config);
debug('Configuration loaded.');
} catch (err) {
const privateKey = await Util.exec('wg genkey');
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, {
log: 'echo ***hidden*** | wg pubkey',
});
const address = WG_DEFAULT_ADDRESS.replace('x', '1');
config = {
server: {
privateKey,
publicKey,
address,
},
clients: {},
};
debug('Configuration generated.');
}
return config;
});
return this.__configPromise;
}
async getConfig() {
if (!this.__configPromise) {
this.__configPromise = Promise.resolve().then(async () => {
if (!WG_HOST) {
throw new Error('WG_HOST Environment Variable Not Set!');
}
const config = await this.__buildConfig();
debug('Loading configuration...');
let config;
try {
config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8');
config = JSON.parse(config);
debug('Configuration loaded.');
} catch (err) {
const privateKey = await Util.exec('wg genkey');
const publicKey = await Util.exec(`echo ${privateKey} | wg pubkey`, {
log: 'echo ***hidden*** | wg pubkey',
});
const address = WG_DEFAULT_ADDRESS.replace('x', '1');
config = {
server: {
privateKey,
publicKey,
address,
},
clients: {},
};
debug('Configuration generated.');
await this.__saveConfig(config);
await Util.exec('wg-quick down wg0').catch(() => {});
await Util.exec('wg-quick up wg0').catch((err) => {
if (err && err.message && err.message.includes('Cannot find device "wg0"')) {
throw new Error('WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support WireGuard!');
}
await this.__saveConfig(config);
await Util.exec('wg-quick down wg0').catch(() => { });
await Util.exec('wg-quick up wg0').catch((err) => {
if (err && err.message && err.message.includes('Cannot find device "wg0"')) {
throw new Error('WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support WireGuard!');
}
throw err;
});
// await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ' + WG_DEVICE + ' -j MASQUERADE`);
// await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT');
// await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT');
// await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT');
await this.__syncConfig();
return config;
throw err;
});
// await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ' + WG_DEVICE + ' -j MASQUERADE`);
// await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT');
// await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT');
// await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT');
await this.__syncConfig();
}
return this.__configPromise;
@ -213,7 +219,7 @@ PublicKey = ${config.server.publicKey}
${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
}AllowedIPs = ${WG_ALLOWED_IPS}
PersistentKeepalive = ${WG_PERSISTENT_KEEPALIVE}
Endpoint = ${WG_HOST}:${WG_PORT}`;
Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
}
async getClientQRCodeSVG({ clientId }) {
@ -253,7 +259,7 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
}
// Create Client
const id = uuid.v4();
const id = crypto.randomUUID();
const client = {
id,
name,
@ -324,9 +330,30 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
await this.saveConfig();
}
async __reloadConfig() {
await this.__buildConfig();
await this.__syncConfig();
}
async restoreConfiguration(config) {
debug('Starting configuration restore process.');
const _config = JSON.parse(config);
await this.__saveConfig(_config);
await this.__reloadConfig();
debug('Configuration restore process completed.');
}
async backupConfiguration() {
debug('Starting configuration backup.');
const config = await this.getConfig();
const backup = JSON.stringify(config, null, 2);
debug('Configuration backup completed.');
return backup;
}
// Shutdown wireguard
async Shutdown() {
await Util.exec('wg-quick down wg0').catch(() => { });
await Util.exec('wg-quick down wg0').catch(() => {});
}
};

249
src/package-lock.json

@ -7,19 +7,18 @@
"": {
"name": "wg-easy",
"version": "1.0.1",
"license": "GPL",
"license": "CC BY-NC-SA 4.0",
"dependencies": {
"bcryptjs": "^2.4.3",
"debug": "^4.3.4",
"debug": "^4.3.5",
"express-session": "^1.18.0",
"h3": "^1.11.1",
"qrcode": "^1.5.3",
"uuid": "^9.0.1"
"h3": "^1.12.0",
"qrcode": "^1.5.3"
},
"devDependencies": {
"eslint-config-athom": "^3.1.3",
"nodemon": "^3.1.0",
"tailwindcss": "^3.4.3"
"nodemon": "^3.1.4",
"tailwindcss": "^3.4.4"
},
"engines": {
"node": ">=18"
@ -48,9 +47,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
"integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
"dev": true,
"peer": true,
"engines": {
@ -58,13 +57,13 @@
}
},
"node_modules/@babel/highlight": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
"integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.5",
"@babel/helper-validator-identifier": "^7.24.7",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
@ -167,9 +166,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
"integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
"dev": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
@ -210,6 +209,7 @@
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
"integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==",
"deprecated": "Use @eslint/config-array instead",
"dev": true,
"peer": true,
"dependencies": {
@ -225,6 +225,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"deprecated": "Use @eslint/object-schema instead",
"dev": true,
"peer": true
},
@ -357,9 +358,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
@ -938,12 +939,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@ -1212,9 +1213,9 @@
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dependencies": {
"ms": "2.1.2"
},
@ -1916,9 +1917,9 @@
}
},
"node_modules/esquery": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
"integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"dev": true,
"peer": true,
"dependencies": {
@ -2060,6 +2061,13 @@
"dev": true,
"peer": true
},
"node_modules/fast-uri": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz",
"integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==",
"dev": true,
"peer": true
},
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@ -2083,9 +2091,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@ -2138,9 +2146,9 @@
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
"integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
@ -2265,6 +2273,7 @@
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"peer": true,
"dependencies": {
@ -2365,18 +2374,18 @@
"dev": true
},
"node_modules/h3": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/h3/-/h3-1.11.1.tgz",
"integrity": "sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A==",
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/h3/-/h3-1.12.0.tgz",
"integrity": "sha512-Zi/CcNeWBXDrFNlV0hUBJQR9F7a96RjMeAZweW/ZWkR9fuXrMcvKnSA63f/zZ9l0GgQOZDVHGvXivNN9PWOwhA==",
"dependencies": {
"cookie-es": "^1.0.0",
"crossws": "^0.2.2",
"cookie-es": "^1.1.0",
"crossws": "^0.2.4",
"defu": "^6.1.4",
"destr": "^2.0.3",
"iron-webcrypto": "^1.0.0",
"iron-webcrypto": "^1.1.1",
"ohash": "^1.1.3",
"radix3": "^1.1.0",
"ufo": "^1.4.0",
"radix3": "^1.1.2",
"ufo": "^1.5.3",
"uncrypto": "^0.1.3",
"unenv": "^1.9.0"
}
@ -2509,6 +2518,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"peer": true,
"dependencies": {
@ -2614,12 +2624,15 @@
}
},
"node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz",
"integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==",
"dev": true,
"dependencies": {
"hasown": "^2.0.0"
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -2821,16 +2834,13 @@
"dev": true
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
@ -2839,9 +2849,9 @@
}
},
"node_modules/jiti": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
"integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
"version": "1.21.6",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
"dev": true,
"bin": {
"jiti": "bin/jiti.js"
@ -2966,13 +2976,10 @@
"peer": true
},
"node_modules/lru-cache": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
}
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
"node_modules/merge2": {
"version": "1.4.1",
@ -2984,12 +2991,12 @@
}
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"dev": true,
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
@ -3029,9 +3036,9 @@
}
},
"node_modules/minipass": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
"integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
@ -3083,9 +3090,9 @@
"integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ=="
},
"node_modules/nodemon": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
"integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
"integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==",
"dev": true,
"dependencies": {
"chokidar": "^3.5.2",
@ -3159,10 +3166,13 @@
}
},
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -3331,6 +3341,12 @@
"node": ">=6"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
"dev": true
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -3469,9 +3485,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"version": "8.4.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
"dev": true,
"funding": [
{
@ -3489,7 +3505,7 @@
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"picocolors": "^1.0.1",
"source-map-js": "^1.2.0"
},
"engines": {
@ -3568,9 +3584,9 @@
}
},
"node_modules/postcss-load-config/node_modules/lilconfig": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
"dev": true,
"engines": {
"node": ">=14"
@ -3599,9 +3615,9 @@
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.16",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz",
"integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
@ -3824,6 +3840,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"peer": true,
"dependencies": {
@ -4223,31 +4240,29 @@
}
},
"node_modules/sucrase/node_modules/glob": {
"version": "10.3.15",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz",
"integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==",
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
"minipass": "^7.0.4",
"path-scurry": "^1.11.0"
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sucrase/node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@ -4302,16 +4317,16 @@
}
},
"node_modules/table/node_modules/ajv": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
"integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.4.1"
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
@ -4326,9 +4341,9 @@
"peer": true
},
"node_modules/tailwindcss": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
"integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
"integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==",
"dev": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@ -4623,18 +4638,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz",
@ -4751,9 +4754,9 @@
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"node_modules/yaml": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
"integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
"dev": true,
"bin": {
"yaml": "bin.mjs"

13
src/package.json

@ -11,19 +11,18 @@
"buildcss": "npx tailwindcss -i ./www/src/css/app.css -o ./www/css/app.css"
},
"author": "Emile Nijssen",
"license": "GPL",
"license": "CC BY-NC-SA 4.0",
"dependencies": {
"bcryptjs": "^2.4.3",
"debug": "^4.3.4",
"debug": "^4.3.5",
"express-session": "^1.18.0",
"h3": "^1.11.1",
"qrcode": "^1.5.3",
"uuid": "^9.0.1"
"h3": "^1.12.0",
"qrcode": "^1.5.3"
},
"devDependencies": {
"eslint-config-athom": "^3.1.3",
"nodemon": "^3.1.0",
"tailwindcss": "^3.4.3"
"nodemon": "^3.1.4",
"tailwindcss": "^3.4.4"
},
"nodemonConfig": {
"ignore": [

54
src/wgpw.mjs

@ -0,0 +1,54 @@
'use strict';
// Import needed libraries
import bcrypt from 'bcryptjs';
// Function to generate hash
const generateHash = async (password) => {
try {
const salt = await bcrypt.genSalt(12);
const hash = await bcrypt.hash(password, salt);
// eslint-disable-next-line no-console
console.log(`PASSWORD_HASH='${hash}'`);
} catch (error) {
throw new Error(`Failed to generate hash : ${error}`);
}
};
// Function to compare password with hash
const comparePassword = async (password, hash) => {
try {
const match = await bcrypt.compare(password, hash);
if (match) {
// eslint-disable-next-line no-console
console.log('Password matches the hash !');
} else {
// eslint-disable-next-line no-console
console.log('Password does not match the hash.');
}
} catch (error) {
throw new Error(`Failed to compare password and hash : ${error}`);
}
};
(async () => {
try {
// Retrieve command line arguments
const args = process.argv.slice(2); // Ignore the first two arguments
if (args.length > 2) {
throw new Error('Usage : wgpw YOUR_PASSWORD [HASH]');
}
const [password, hash] = args;
if (password && hash) {
await comparePassword(password, hash);
} else if (password) {
await generateHash(password);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
// eslint-disable-next-line no-process-exit
process.exit(1);
}
})();

5
src/wgpw.sh

@ -0,0 +1,5 @@
#!/bin/sh
# This script is intended to be run only inside a docker container, not on the development host machine
set -e
# proxy command
node /app/wgpw.mjs "$@"

15
src/www/css/app.css

@ -1,5 +1,5 @@
/*
! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com
! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com
*/
/*
@ -802,6 +802,11 @@ video {
display: none;
}
.size-6 {
width: 1.5rem;
height: 1.5rem;
}
.h-1 {
height: 0.25rem;
}
@ -846,6 +851,10 @@ video {
min-height: 100vh;
}
.w-1 {
width: 0.25rem;
}
.w-10 {
width: 2.5rem;
}
@ -1454,6 +1463,10 @@ video {
border-bottom-width: 0px;
}
.hover\:cursor-pointer:hover {
cursor: pointer;
}
.hover\:border-red-800:hover {
--tw-border-opacity: 1;
border-color: rgb(153 27 27 / var(--tw-border-opacity));

18
src/www/index.html

@ -91,6 +91,24 @@
<p class="text-2xl font-medium dark:text-neutral-200">{{$t("clients")}}</p>
</div>
<div class="flex-shrink-0">
<!-- Restore configuration -->
<label for="inputRC" :title="$t('titleRestoreConfig')"
class="hover:cursor-pointer hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded inline-flex items-center transition">
<svg inline class="w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"></path>
</svg>
<span class="text-sm">{{$t("restore")}}</span>
<input id="inputRC" type="file" name="configurationfile" accept="text/*,.json" @change="restoreConfig" class="hidden"/>
</label>
<!-- Backup configuration -->
<a href="./api/wireguard/backup" :title="$t('titleBackupConfig')"
class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded inline-flex items-center transition">
<svg inline class="w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z"></path>
</svg>
<span class="text-sm">{{$t("backup")}}</span>
</a>
<!-- New client -->
<button @click="clientCreate = true; clientCreateName = '';"
class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded inline-flex items-center transition">
<svg class="w-4 mr-2" inline xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"

8
src/www/js/api.js

@ -138,4 +138,12 @@ class API {
});
}
async restoreConfiguration(file) {
return this.call({
method: 'put',
path: '/wireguard/restore',
body: { file },
});
}
}

19
src/www/js/app.js

@ -299,6 +299,22 @@ new Vue({
.catch((err) => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error));
},
restoreConfig(e) {
e.preventDefault();
const file = e.currentTarget.files.item(0);
if (file) {
file.text()
.then((content) => {
this.api.restoreConfiguration(content)
.then((_result) => alert('The configuration was updated.'))
.catch((err) => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error));
})
.catch((err) => alert(err.message || err.toString()));
} else {
alert('Failed to load your file!');
}
},
toggleTheme() {
const themes = ['light', 'dark', 'auto'];
const currentIndex = themes.indexOf(this.uiTheme);
@ -390,9 +406,6 @@ new Vue({
return releasesArray[0];
});
console.log(`Current Release: ${currentRelease}`);
console.log(`Latest Release: ${latestRelease.version}`);
if (currentRelease >= latestRelease.version) return;
this.currentRelease = currentRelease;

46
src/www/js/i18n.js

@ -30,6 +30,10 @@ const messages = { // eslint-disable-line no-unused-vars
donate: 'Donate',
toggleCharts: 'Show/hide Charts',
theme: { dark: 'Dark theme', light: 'Light theme', auto: 'Auto theme' },
restore: 'Restore',
backup: 'Backup',
titleRestoreConfig: 'Restore your configuration',
titleBackupConfig: 'Backup your configuration',
},
ua: {
name: 'Ім`я',
@ -193,6 +197,10 @@ const messages = { // eslint-disable-line no-unused-vars
downloadConfig: 'Télécharger la configuration',
madeBy: 'Développé par',
donate: 'Soutenir',
restore: 'Restaurer',
backup: 'Sauvegarder',
titleRestoreConfig: 'Restaurer votre configuration',
titleBackupConfig: 'Sauvegarder votre configuration',
},
de: { // github.com/florian-asche
name: 'Name',
@ -221,6 +229,10 @@ const messages = { // eslint-disable-line no-unused-vars
downloadConfig: 'Konfiguration herunterladen',
madeBy: 'Erstellt von',
donate: 'Spenden',
restore: 'Wiederherstellen',
backup: 'Sichern',
titleRestoreConfig: 'Stelle deine Konfiguration wieder her',
titleBackupConfig: 'Sichere deine Konfiguration',
},
ca: { // github.com/guillembonet
name: 'Nom',
@ -277,6 +289,10 @@ const messages = { // eslint-disable-line no-unused-vars
donate: 'Donar',
toggleCharts: 'Mostrar/Ocultar gráficos',
theme: { dark: 'Modo oscuro', light: 'Modo claro', auto: 'Modo automático' },
restore: 'Restaurar',
backup: 'Realizar copia de seguridad',
titleRestoreConfig: 'Restaurar su configuración',
titleBackupConfig: 'Realizar copia de seguridad de su configuración',
},
ko: {
name: '이름',
@ -445,27 +461,27 @@ const messages = { // eslint-disable-line no-unused-vars
password: '密碼',
signIn: '登入',
logout: '登出',
updateAvailable: '有新版本可用!',
updateAvailable: '有新版本可以使用!',
update: '更新',
clients: '客戶',
new: '建',
deleteClient: '刪除客戶',
clients: '使用者',
new: '建',
deleteClient: '刪除使用者',
deleteDialog1: '您確定要刪除',
deleteDialog2: '此操作無法撤銷。',
deleteDialog2: '此作業無法復原。',
cancel: '取消',
create: '建立',
createdOn: '建立於 ',
lastSeen: '最後訪問於 ',
lastSeen: '最後存取於 ',
totalDownload: '總下載: ',
totalUpload: '總上傳: ',
newClient: '新戶',
disableClient: '禁用客戶',
enableClient: '啟用客戶',
noClients: '目前沒有客戶。',
showQR: '顯示二維碼',
downloadConfig: '下載配置',
newClient: '新戶',
disableClient: '停用使用者',
enableClient: '啟用使用者',
noClients: '目前沒有使用者。',
showQR: '顯示 QR Code',
downloadConfig: '下載 Config 檔',
madeBy: '由',
donate: '捐贈',
donate: '抖內',
},
it: {
name: 'Nome',
@ -493,6 +509,10 @@ const messages = { // eslint-disable-line no-unused-vars
downloadConfig: 'Scarica configurazione',
madeBy: 'Realizzato da',
donate: 'Donazione',
restore: 'Ripristina',
backup: 'Backup',
titleRestoreConfig: 'Ripristina la tua configurazione',
titleBackupConfig: 'Esegui il backup della tua configurazione',
},
th: {
name: 'ชื่อ',

8
src/www/js/vendor/apexcharts.min.js

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save