Browse Source

add prettier, format

pull/1250/head
Bernd Storath 9 months ago
parent
commit
de2ee3dc58
  1. 6
      src/.prettierrc.json
  2. 1959
      src/app.vue
  3. 10
      src/assets/css/app.css
  4. 4
      src/eslint.config.mjs
  5. 1190
      src/i18n.config.ts
  6. 8
      src/middleware/session.ts
  7. 4
      src/nuxt.config.ts
  8. 4
      src/package.json
  9. 4
      src/plugins/apexcharts.client.ts
  10. 6219
      src/pnpm-lock.yaml
  11. 20
      src/public/manifest.json
  12. 10
      src/server/api/lang.ts
  13. 10
      src/server/api/release.ts
  14. 100
      src/server/api/session.ts
  15. 10
      src/server/api/ui-chart-type.ts
  16. 10
      src/server/api/ui-traffic-stats.ts
  17. 14
      src/server/api/wireguard/backup.ts
  18. 24
      src/server/api/wireguard/client/[clientId]/address.ts
  19. 32
      src/server/api/wireguard/client/[clientId]/configuration.ts
  20. 22
      src/server/api/wireguard/client/[clientId]/disable.ts
  21. 22
      src/server/api/wireguard/client/[clientId]/enable.ts
  22. 12
      src/server/api/wireguard/client/[clientId]/index.ts
  23. 24
      src/server/api/wireguard/client/[clientId]/name.ts
  24. 14
      src/server/api/wireguard/client/[clientId]/qrcode.svg.ts
  25. 18
      src/server/api/wireguard/client/index.ts
  26. 12
      src/server/api/wireguard/restore.ts
  27. 5
      src/tailwind.config.ts
  28. 99
      src/utils/WireGuard.ts
  29. 98
      src/utils/api.ts
  30. 39
      src/utils/cmd.ts
  31. 35
      src/utils/config.ts
  32. 10
      src/utils/crypto.ts
  33. 18
      src/utils/ip.ts
  34. 47
      src/utils/localStorage.ts
  35. 20
      src/utils/password.ts
  36. 7
      src/wgpw.js

6
src/.prettierrc.json

@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}

1959
src/app.vue

File diff suppressed because it is too large

10
src/assets/css/app.css

@ -1,6 +1,6 @@
[v-cloak] { [v-cloak] {
display: none; display: none;
} }
.line-chart .apexcharts-svg{ .line-chart .apexcharts-svg {
transform: translateY(3px); transform: translateY(3px);
} }

4
src/eslint.config.mjs

@ -1,3 +1,3 @@
import { createConfigForNuxt } from '@nuxt/eslint-config/flat' import { createConfigForNuxt } from '@nuxt/eslint-config/flat';
export default createConfigForNuxt({}) export default createConfigForNuxt({});

1190
src/i18n.config.ts

File diff suppressed because it is too large

8
src/middleware/session.ts

@ -1,5 +1,5 @@
export default defineNuxtRouteMiddleware(async (to) => { export default defineNuxtRouteMiddleware(async (to) => {
if (REQUIRES_PASSWORD || !to.path.startsWith('/api/')) { if (REQUIRES_PASSWORD || !to.path.startsWith('/api/')) {
return abortNavigation(); return abortNavigation();
} }
}) });

4
src/nuxt.config.ts

@ -2,5 +2,5 @@
export default defineNuxtConfig({ export default defineNuxtConfig({
compatibilityDate: '2024-04-03', compatibilityDate: '2024-04-03',
devtools: { enabled: true }, devtools: { enabled: true },
modules: ["@nuxtjs/i18n", "@nuxtjs/tailwindcss"] modules: ['@nuxtjs/i18n', '@nuxtjs/tailwindcss'],
}) });

4
src/package.json

@ -13,7 +13,8 @@
"generate": "nuxt generate", "generate": "nuxt generate",
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare", "postinstall": "nuxt prepare",
"lint": "eslint ." "lint": "eslint .",
"format": "prettier . --write"
}, },
"dependencies": { "dependencies": {
"@nuxtjs/i18n": "^8.3.3", "@nuxtjs/i18n": "^8.3.3",
@ -34,6 +35,7 @@
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"eslint": "^9.8.0", "eslint": "^9.8.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vue-tsc": "^2.0.29" "vue-tsc": "^2.0.29"
}, },

4
src/plugins/apexcharts.client.ts

@ -1,5 +1,5 @@
import VueApexCharts from "vue3-apexcharts"; import VueApexCharts from 'vue3-apexcharts';
export default defineNuxtPlugin((nuxtApp) => { export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(VueApexCharts); nuxtApp.vueApp.use(VueApexCharts);
}); });

6219
src/pnpm-lock.yaml

File diff suppressed because it is too large

20
src/public/manifest.json

@ -1,11 +1,11 @@
{ {
"name": "WireGuard", "name": "WireGuard",
"display": "standalone", "display": "standalone",
"background_color": "#fff", "background_color": "#fff",
"icons": [ "icons": [
{ {
"src": "/favicon.png", "src": "/favicon.png",
"type": "image/png" "type": "image/png"
} }
] ]
} }

10
src/server/api/lang.ts

@ -1,7 +1,7 @@
import { LANG } from "~/utils/config"; import { LANG } from '~/utils/config';
export default defineEventHandler((event) => { export default defineEventHandler((event) => {
assertMethod(event, "GET"); assertMethod(event, 'GET');
setHeader(event, 'Content-Type', 'application/json'); setHeader(event, 'Content-Type', 'application/json');
return `"${LANG}"`; return `"${LANG}"`;
}) });

10
src/server/api/release.ts

@ -1,7 +1,7 @@
import { RELEASE } from "~/utils/config"; import { RELEASE } from '~/utils/config';
export default defineEventHandler((event) => { export default defineEventHandler((event) => {
assertMethod(event, "GET"); assertMethod(event, 'GET');
setHeader(event, 'Content-Type', 'application/json'); setHeader(event, 'Content-Type', 'application/json');
return RELEASE; return RELEASE;
}) });

100
src/server/api/session.ts

@ -1,51 +1,55 @@
import { REQUIRES_PASSWORD, SERVER_DEBUG, SESSION_CONFIG } from "~/utils/config"; import {
import { isPasswordValid } from "~/utils/password"; REQUIRES_PASSWORD,
SERVER_DEBUG,
SESSION_CONFIG,
} from '~/utils/config';
import { isPasswordValid } from '~/utils/password';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
if (isMethod(event, "GET")) { if (isMethod(event, 'GET')) {
const session = await useSession(event, SESSION_CONFIG); const session = await useSession(event, SESSION_CONFIG);
const authenticated = REQUIRES_PASSWORD const authenticated = REQUIRES_PASSWORD
? !!(session.data && session.data.authenticated) ? !!(session.data && session.data.authenticated)
: true; : true;
return { return {
REQUIRES_PASSWORD, REQUIRES_PASSWORD,
authenticated, authenticated,
}; };
} else if (isMethod(event, "POST")) { } else if (isMethod(event, 'POST')) {
const session = await useSession(event, SESSION_CONFIG); const session = await useSession(event, SESSION_CONFIG);
const { password } = await readBody(event); const { password } = await readBody(event);
if (!REQUIRES_PASSWORD) { if (!REQUIRES_PASSWORD) {
// if no password is required, the API should never be called. // if no password is required, the API should never be called.
// Do not automatically authenticate the user. // Do not automatically authenticate the user.
throw createError({ throw createError({
status: 401, status: 401,
message: 'Invalid state', message: 'Invalid state',
}); });
}
if (!isPasswordValid(password)) {
throw createError({
status: 401,
message: 'Incorrect Password',
});
}
const data = await session.update({
authenticated: true
});
SERVER_DEBUG(`New Session: ${data.id}`);
return { success: true };
} else if (isMethod(event, "DELETE")) {
const session = await useSession(event, SESSION_CONFIG);
const sessionId = session.id;
await session.clear();
SERVER_DEBUG(`Deleted Session: ${sessionId}`);
return { success: true };
} }
})
if (!isPasswordValid(password)) {
throw createError({
status: 401,
message: 'Incorrect Password',
});
}
const data = await session.update({
authenticated: true,
});
SERVER_DEBUG(`New Session: ${data.id}`);
return { success: true };
} else if (isMethod(event, 'DELETE')) {
const session = await useSession(event, SESSION_CONFIG);
const sessionId = session.id;
await session.clear();
SERVER_DEBUG(`Deleted Session: ${sessionId}`);
return { success: true };
}
});

10
src/server/api/ui-chart-type.ts

@ -1,7 +1,7 @@
import { UI_CHART_TYPE } from "~/utils/config"; import { UI_CHART_TYPE } from '~/utils/config';
export default defineEventHandler((event) => { export default defineEventHandler((event) => {
assertMethod(event, "GET"); assertMethod(event, 'GET');
setHeader(event, 'Content-Type', 'application/json'); setHeader(event, 'Content-Type', 'application/json');
return `"${UI_CHART_TYPE}"`; return `"${UI_CHART_TYPE}"`;
}) });

10
src/server/api/ui-traffic-stats.ts

@ -1,7 +1,7 @@
import { UI_TRAFFIC_STATS } from "~/utils/config"; import { UI_TRAFFIC_STATS } from '~/utils/config';
export default defineEventHandler((event) => { export default defineEventHandler((event) => {
assertMethod(event, "GET"); assertMethod(event, 'GET');
setHeader(event, 'Content-Type', 'application/json'); setHeader(event, 'Content-Type', 'application/json');
return `"${UI_TRAFFIC_STATS}"`; return `"${UI_TRAFFIC_STATS}"`;
}) });

14
src/server/api/wireguard/backup.ts

@ -1,9 +1,9 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "GET"); assertMethod(event, 'GET');
const config = await WireGuard.backupConfiguration(); const config = await WireGuard.backupConfiguration();
setHeader(event, 'Content-Disposition', 'attachment; filename="wg0.json"'); setHeader(event, 'Content-Disposition', 'attachment; filename="wg0.json"');
setHeader(event, 'Content-Type', 'text/json'); setHeader(event, 'Content-Type', 'text/json');
return config; return config;
}) });

24
src/server/api/wireguard/client/[clientId]/address.ts

@ -1,12 +1,16 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "PUT"); assertMethod(event, 'PUT');
const clientId = getRouterParam(event, 'clientId'); const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (
throw createError({ status: 403 }); clientId === '__proto__' ||
} clientId === 'constructor' ||
const { address } = await readBody(event); clientId === 'prototype'
await WireGuard.updateClientAddress({ clientId, address }); ) {
return { success: true }; throw createError({ status: 403 });
}) }
const { address } = await readBody(event);
await WireGuard.updateClientAddress({ clientId, address });
return { success: true };
});

32
src/server/api/wireguard/client/[clientId]/configuration.ts

@ -1,16 +1,20 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "GET"); assertMethod(event, 'GET');
const clientId = getRouterParam(event, 'clientId'); const clientId = getRouterParam(event, 'clientId');
const client = await WireGuard.getClient({ clientId }); const client = await WireGuard.getClient({ clientId });
const config = await WireGuard.getClientConfiguration({ clientId }); const config = await WireGuard.getClientConfiguration({ clientId });
const configName = client.name const configName = client.name
.replace(/[^a-zA-Z0-9_=+.-]/g, '-') .replace(/[^a-zA-Z0-9_=+.-]/g, '-')
.replace(/(-{2,}|-$)/g, '-') .replace(/(-{2,}|-$)/g, '-')
.replace(/-$/, '') .replace(/-$/, '')
.substring(0, 32); .substring(0, 32);
setHeader(event, 'Content-Disposition', `attachment; filename="${configName || clientId}.conf"`); setHeader(
setHeader(event, 'Content-Type', 'text/plain'); event,
return config; 'Content-Disposition',
}) `attachment; filename="${configName || clientId}.conf"`
);
setHeader(event, 'Content-Type', 'text/plain');
return config;
});

22
src/server/api/wireguard/client/[clientId]/disable.ts

@ -1,11 +1,15 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "POST"); assertMethod(event, 'POST');
const clientId = getRouterParam(event, 'clientId'); const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (
throw createError({ status: 403 }); clientId === '__proto__' ||
} clientId === 'constructor' ||
await WireGuard.disableClient({ clientId }); clientId === 'prototype'
return { success: true }; ) {
}) throw createError({ status: 403 });
}
await WireGuard.disableClient({ clientId });
return { success: true };
});

22
src/server/api/wireguard/client/[clientId]/enable.ts

@ -1,11 +1,15 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "POST"); assertMethod(event, 'POST');
const clientId = getRouterParam(event, 'clientId'); const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (
throw createError({ status: 403 }); clientId === '__proto__' ||
} clientId === 'constructor' ||
await WireGuard.enableClient({ clientId }); clientId === 'prototype'
return { success: true }; ) {
}) throw createError({ status: 403 });
}
await WireGuard.enableClient({ clientId });
return { success: true };
});

12
src/server/api/wireguard/client/[clientId]/index.ts

@ -1,8 +1,8 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "DELETE"); assertMethod(event, 'DELETE');
const clientId = getRouterParam(event, 'clientId'); const clientId = getRouterParam(event, 'clientId');
await WireGuard.deleteClient({ clientId }); await WireGuard.deleteClient({ clientId });
return { success: true }; return { success: true };
}); });

24
src/server/api/wireguard/client/[clientId]/name.ts

@ -1,12 +1,16 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "PUT"); assertMethod(event, 'PUT');
const clientId = getRouterParam(event, 'clientId'); const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (
throw createError({ status: 403 }); clientId === '__proto__' ||
} clientId === 'constructor' ||
const { name } = await readBody(event); clientId === 'prototype'
await WireGuard.updateClientName({ clientId, name }); ) {
return { success: true }; throw createError({ status: 403 });
}) }
const { name } = await readBody(event);
await WireGuard.updateClientName({ clientId, name });
return { success: true };
});

14
src/server/api/wireguard/client/[clientId]/qrcode.svg.ts

@ -1,9 +1,9 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "GET"); assertMethod(event, 'GET');
const clientId = getRouterParam(event, 'clientId'); const clientId = getRouterParam(event, 'clientId');
const svg = await WireGuard.getClientQRCodeSVG({ clientId }); const svg = await WireGuard.getClientQRCodeSVG({ clientId });
setHeader(event, 'Content-Type', 'image/svg+xml'); setHeader(event, 'Content-Type', 'image/svg+xml');
return svg; return svg;
}) });

18
src/server/api/wireguard/client/index.ts

@ -1,11 +1,11 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
if (isMethod(event, "GET")) { if (isMethod(event, 'GET')) {
return WireGuard.getClients(); return WireGuard.getClients();
} else if (isMethod(event, "POST")) { } else if (isMethod(event, 'POST')) {
const { name } = await readBody(event); const { name } = await readBody(event);
await WireGuard.createClient({ name }); await WireGuard.createClient({ name });
return { success: true }; return { success: true };
} }
}) });

12
src/server/api/wireguard/restore.ts

@ -1,8 +1,8 @@
import WireGuard from "~/utils/WireGuard"; import WireGuard from '~/utils/WireGuard';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
assertMethod(event, "PUT"); assertMethod(event, 'PUT');
const { file } = await readBody(event); const { file } = await readBody(event);
await WireGuard.restoreConfiguration(file); await WireGuard.restoreConfiguration(file);
return { success: true }; return { success: true };
}) });

5
src/tailwind.config.ts

@ -1,4 +1,4 @@
import type { Config } from 'tailwindcss' import type { Config } from 'tailwindcss';
export default { export default {
content: [], content: [],
@ -24,5 +24,4 @@ export default {
addUtilities(newUtilities); addUtilities(newUtilities);
}, },
], ],
} satisfies Config } satisfies Config;

99
src/utils/WireGuard.ts

@ -1,13 +1,27 @@
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import path from 'path'; import path from 'path';
import debug_logger from 'debug' import debug_logger from 'debug';
import crypto from 'node:crypto'; import crypto from 'node:crypto';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
import { WG_PATH, WG_HOST, WG_PORT, WG_CONFIG_PORT, WG_MTU, WG_DEFAULT_DNS, WG_DEFAULT_ADDRESS, WG_PERSISTENT_KEEPALIVE, WG_ALLOWED_IPS, WG_PRE_UP, WG_POST_UP, WG_PRE_DOWN, WG_POST_DOWN } from '~/utils/config'; import {
WG_PATH,
WG_HOST,
WG_PORT,
WG_CONFIG_PORT,
WG_MTU,
WG_DEFAULT_DNS,
WG_DEFAULT_ADDRESS,
WG_PERSISTENT_KEEPALIVE,
WG_ALLOWED_IPS,
WG_PRE_UP,
WG_POST_UP,
WG_PRE_DOWN,
WG_POST_DOWN,
} from '~/utils/config';
import { exec } from '~/utils/cmd'; import { exec } from '~/utils/cmd';
import { isValidIPv4 } from '~/utils/ip'; import { isValidIPv4 } from '~/utils/ip';
const debug = debug_logger('WireGuard') const debug = debug_logger('WireGuard');
class ServerError extends Error { class ServerError extends Error {
statusCode: number; statusCode: number;
@ -15,10 +29,9 @@ class ServerError extends Error {
super(message); super(message);
this.statusCode = statusCode; this.statusCode = statusCode;
} }
}; }
class WireGuard { class WireGuard {
async __buildConfig() { async __buildConfig() {
this.__configPromise = Promise.resolve().then(async () => { this.__configPromise = Promise.resolve().then(async () => {
if (!WG_HOST) { if (!WG_HOST) {
@ -62,8 +75,14 @@ class WireGuard {
await this.__saveConfig(config); await this.__saveConfig(config);
await exec('wg-quick down wg0').catch(() => {}); await exec('wg-quick down wg0').catch(() => {});
await exec('wg-quick up wg0').catch((err) => { await exec('wg-quick up wg0').catch((err) => {
if (err && err.message && err.message.includes('Cannot find device "wg0"')) { if (
throw new Error('WireGuard exited with the error: Cannot find device "wg0"\nThis usually means that your host\'s kernel does not support WireGuard!'); 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; throw err;
@ -108,14 +127,19 @@ PostDown = ${WG_POST_DOWN}
# Client: ${client.name} (${clientId}) # Client: ${client.name} (${clientId})
[Peer] [Peer]
PublicKey = ${client.publicKey} PublicKey = ${client.publicKey}
${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' ${
client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
}AllowedIPs = ${client.address}/32`; }AllowedIPs = ${client.address}/32`;
} }
debug('Config saving...'); debug('Config saving...');
await fs.writeFile(path.join(WG_PATH, 'wg0.json'), JSON.stringify(config, false, 2), { await fs.writeFile(
mode: 0o660, path.join(WG_PATH, 'wg0.json'),
}); JSON.stringify(config, false, 2),
{
mode: 0o660,
}
);
await fs.writeFile(path.join(WG_PATH, 'wg0.conf'), result, { await fs.writeFile(path.join(WG_PATH, 'wg0.conf'), result, {
mode: 0o600, mode: 0o600,
}); });
@ -130,21 +154,23 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
async getClients() { async getClients() {
const config = await this.getConfig(); const config = await this.getConfig();
const clients = Object.entries(config.clients).map(([clientId, client]) => ({ const clients = Object.entries(config.clients).map(
id: clientId, ([clientId, client]) => ({
name: client.name, id: clientId,
enabled: client.enabled, name: client.name,
address: client.address, enabled: client.enabled,
publicKey: client.publicKey, address: client.address,
createdAt: new Date(client.createdAt), publicKey: client.publicKey,
updatedAt: new Date(client.updatedAt), createdAt: new Date(client.createdAt),
allowedIPs: client.allowedIPs, updatedAt: new Date(client.updatedAt),
downloadableConfig: 'privateKey' in client, allowedIPs: client.allowedIPs,
persistentKeepalive: null, downloadableConfig: 'privateKey' in client,
latestHandshakeAt: null, persistentKeepalive: null,
transferRx: null, latestHandshakeAt: null,
transferTx: null, transferRx: null,
})); transferTx: null,
})
);
// Loop WireGuard status // Loop WireGuard status
const dump = await exec('wg show wg0 dump', { const dump = await exec('wg show wg0 dump', {
@ -157,9 +183,9 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
.forEach((line) => { .forEach((line) => {
const [ const [
publicKey, publicKey,
_preSharedKey, _preSharedKey,
_endpoint, _endpoint,
_allowedIps, _allowedIps,
latestHandshakeAt, latestHandshakeAt,
transferRx, transferRx,
transferTx, transferTx,
@ -169,9 +195,10 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
const client = clients.find((client) => client.publicKey === publicKey); const client = clients.find((client) => client.publicKey === publicKey);
if (!client) return; if (!client) return;
client.latestHandshakeAt = latestHandshakeAt === '0' client.latestHandshakeAt =
? null latestHandshakeAt === '0'
: new Date(Number(`${latestHandshakeAt}000`)); ? null
: new Date(Number(`${latestHandshakeAt}000`));
client.transferRx = Number(transferRx); client.transferRx = Number(transferRx);
client.transferTx = Number(transferTx); client.transferTx = Number(transferTx);
client.persistentKeepalive = persistentKeepalive; client.persistentKeepalive = persistentKeepalive;
@ -203,7 +230,8 @@ ${WG_MTU ? `MTU = ${WG_MTU}\n` : ''}\
[Peer] [Peer]
PublicKey = ${config.server.publicKey} PublicKey = ${config.server.publicKey}
${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' ${
client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
}AllowedIPs = ${WG_ALLOWED_IPS} }AllowedIPs = ${WG_ALLOWED_IPS}
PersistentKeepalive = ${WG_PERSISTENT_KEEPALIVE} PersistentKeepalive = ${WG_PERSISTENT_KEEPALIVE}
Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
@ -344,7 +372,6 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
async Shutdown() { async Shutdown() {
await exec('wg-quick down wg0').catch(() => {}); await exec('wg-quick down wg0').catch(() => {});
} }
}
}; export default new WireGuard();
export default new WireGuard();

98
src/utils/api.ts

@ -1,36 +1,34 @@
export type APIClient = { export type APIClient = {
"id": string, id: string;
"name": string, name: string;
"enabled": boolean, enabled: boolean;
"address": string, address: string;
"publicKey": string, publicKey: string;
"createdAt": string, createdAt: string;
"updatedAt": string, updatedAt: string;
"downloadableConfig": boolean, downloadableConfig: boolean;
"persistentKeepalive": string, persistentKeepalive: string;
"latestHandshakeAt": null, latestHandshakeAt: null;
"transferRx": number, transferRx: number;
"transferTx": number transferTx: number;
} };
class API { class API {
async call({
async call({ method, path, body }: { method,
method: string, path,
path: string, body,
body?: Record<string, unknown> }: {
method: string;
path: string;
body?: Record<string, unknown>;
}) { }) {
const res = await fetch(`./api${path}`, { const res = await fetch(`./api${path}`, {
method, method,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: body body: body ? JSON.stringify(body) : undefined,
? JSON.stringify(body)
: undefined,
}); });
if (res.status === 204) { if (res.status === 204) {
@ -81,7 +79,7 @@ class API {
}); });
} }
async createSession({ password }: {password: string|null}) { async createSession({ password }: { password: string | null }) {
return this.call({ return this.call({
method: 'post', method: 'post',
path: '/session', path: '/session',
@ -100,17 +98,20 @@ class API {
return this.call({ return this.call({
method: 'get', method: 'get',
path: '/wireguard/client', path: '/wireguard/client',
}).then((clients: APIClient[]) => clients.map((client) => ({ }).then((clients: APIClient[]) =>
...client, clients.map((client) => ({
createdAt: new Date(client.createdAt), ...client,
updatedAt: new Date(client.updatedAt), createdAt: new Date(client.createdAt),
latestHandshakeAt: client.latestHandshakeAt !== null updatedAt: new Date(client.updatedAt),
? new Date(client.latestHandshakeAt) latestHandshakeAt:
: null, client.latestHandshakeAt !== null
}))); ? new Date(client.latestHandshakeAt)
} : null,
}))
async createClient({ name }: {name: string}) { );
}
async createClient({ name }: { name: string }) {
return this.call({ return this.call({
method: 'post', method: 'post',
path: '/wireguard/client', path: '/wireguard/client',
@ -118,28 +119,34 @@ class API {
}); });
} }
async deleteClient({ clientId }: {clientId: string}) { async deleteClient({ clientId }: { clientId: string }) {
return this.call({ return this.call({
method: 'delete', method: 'delete',
path: `/wireguard/client/${clientId}`, path: `/wireguard/client/${clientId}`,
}); });
} }
async enableClient({ clientId }: {clientId: string}) { async enableClient({ clientId }: { clientId: string }) {
return this.call({ return this.call({
method: 'post', method: 'post',
path: `/wireguard/client/${clientId}/enable`, path: `/wireguard/client/${clientId}/enable`,
}); });
} }
async disableClient({ clientId }: {clientId: string}) { async disableClient({ clientId }: { clientId: string }) {
return this.call({ return this.call({
method: 'post', method: 'post',
path: `/wireguard/client/${clientId}/disable`, path: `/wireguard/client/${clientId}/disable`,
}); });
} }
async updateClientName({ clientId, name }: {clientId: string, name: string}) { async updateClientName({
clientId,
name,
}: {
clientId: string;
name: string;
}) {
return this.call({ return this.call({
method: 'put', method: 'put',
path: `/wireguard/client/${clientId}/name/`, path: `/wireguard/client/${clientId}/name/`,
@ -147,7 +154,13 @@ class API {
}); });
} }
async updateClientAddress({ clientId, address }: {clientId: string, address: string}) { async updateClientAddress({
clientId,
address,
}: {
clientId: string;
address: string;
}) {
return this.call({ return this.call({
method: 'put', method: 'put',
path: `/wireguard/client/${clientId}/address/`, path: `/wireguard/client/${clientId}/address/`,
@ -162,7 +175,6 @@ class API {
body: { file }, body: { file },
}); });
} }
} }
export default new API(); export default new API();

39
src/utils/cmd.ts

@ -1,24 +1,29 @@
import childProcess from 'child_process'; import childProcess from 'child_process';
export function exec(cmd: string, {log}: {log: boolean|string} = {log: true}) { export function exec(
if (typeof log === 'string') { cmd: string,
{ log }: { log: boolean | string } = { log: true }
console.log(`$ ${log}`); ) {
} else if (log === true) { if (typeof log === 'string') {
console.log(`$ ${log}`);
console.log(`$ ${cmd}`); } else if (log === true) {
} console.log(`$ ${cmd}`);
}
if (process.platform !== 'linux') { if (process.platform !== 'linux') {
return Promise.resolve(""); return Promise.resolve('');
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
childProcess.exec(cmd, { childProcess.exec(
cmd,
{
shell: 'bash', shell: 'bash',
}, (err, stdout) => { },
(err, stdout) => {
if (err) return reject(err); if (err) return reject(err);
return resolve(String(stdout).trim()); return resolve(String(stdout).trim());
}); }
}); );
} });
}

35
src/utils/config.ts

@ -1,6 +1,6 @@
import type {SessionConfig} from 'h3'; import type { SessionConfig } from 'h3';
import {getRandomHex} from '~/utils/crypto' import { getRandomHex } from '~/utils/crypto';
import packageJSON from '../package.json'; import packageJSON from '../package.json';
import debug from 'debug'; import debug from 'debug';
const version = packageJSON.release.version; const version = packageJSON.release.version;
@ -13,30 +13,41 @@ export const WG_PATH = process.env.WG_PATH || '/etc/wireguard/';
export const WG_DEVICE = process.env.WG_DEVICE || 'eth0'; export const WG_DEVICE = process.env.WG_DEVICE || 'eth0';
export const WG_HOST = process.env.WG_HOST; export const WG_HOST = process.env.WG_HOST;
export const WG_PORT = process.env.WG_PORT || '51820'; export const WG_PORT = process.env.WG_PORT || '51820';
export const WG_CONFIG_PORT = process.env.WG_CONFIG_PORT || process.env.WG_PORT || '51820'; export const WG_CONFIG_PORT =
process.env.WG_CONFIG_PORT || process.env.WG_PORT || '51820';
export const WG_MTU = process.env.WG_MTU || null; export const WG_MTU = process.env.WG_MTU || null;
export const WG_PERSISTENT_KEEPALIVE = process.env.WG_PERSISTENT_KEEPALIVE || '0'; export const WG_PERSISTENT_KEEPALIVE =
process.env.WG_PERSISTENT_KEEPALIVE || '0';
export const WG_DEFAULT_ADDRESS = process.env.WG_DEFAULT_ADDRESS || '10.8.0.x'; export const WG_DEFAULT_ADDRESS = process.env.WG_DEFAULT_ADDRESS || '10.8.0.x';
export const WG_DEFAULT_DNS = typeof process.env.WG_DEFAULT_DNS === 'string' export const WG_DEFAULT_DNS =
? process.env.WG_DEFAULT_DNS typeof process.env.WG_DEFAULT_DNS === 'string'
: '1.1.1.1'; ? process.env.WG_DEFAULT_DNS
: '1.1.1.1';
export const WG_ALLOWED_IPS = process.env.WG_ALLOWED_IPS || '0.0.0.0/0, ::/0'; export const WG_ALLOWED_IPS = process.env.WG_ALLOWED_IPS || '0.0.0.0/0, ::/0';
export const WG_PRE_UP = process.env.WG_PRE_UP || ''; export const WG_PRE_UP = process.env.WG_PRE_UP || '';
export const WG_POST_UP = process.env.WG_POST_UP || ` export const WG_POST_UP =
process.env.WG_POST_UP ||
`
iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${WG_DEVICE} -j MASQUERADE; iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${WG_DEVICE} -j MASQUERADE;
iptables -A INPUT -p udp -m udp --dport ${WG_PORT} -j ACCEPT; iptables -A INPUT -p udp -m udp --dport ${WG_PORT} -j ACCEPT;
iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT;
iptables -A FORWARD -o wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT;
`.split('\n').join(' '); `
.split('\n')
.join(' ');
export const WG_PRE_DOWN = process.env.WG_PRE_DOWN || ''; export const WG_PRE_DOWN = process.env.WG_PRE_DOWN || '';
export const WG_POST_DOWN = process.env.WG_POST_DOWN || ` export const WG_POST_DOWN =
process.env.WG_POST_DOWN ||
`
iptables -t nat -D POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${WG_DEVICE} -j MASQUERADE; iptables -t nat -D POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${WG_DEVICE} -j MASQUERADE;
iptables -D INPUT -p udp -m udp --dport ${WG_PORT} -j ACCEPT; iptables -D INPUT -p udp -m udp --dport ${WG_PORT} -j ACCEPT;
iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT;
iptables -D FORWARD -o wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT;
`.split('\n').join(' '); `
.split('\n')
.join(' ');
export const LANG = process.env.LANG || 'en'; export const LANG = process.env.LANG || 'en';
export const UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false'; export const UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false';
export const UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0; export const UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0;
@ -44,7 +55,7 @@ export const UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0;
export const REQUIRES_PASSWORD = !!PASSWORD_HASH; export const REQUIRES_PASSWORD = !!PASSWORD_HASH;
export const SESSION_CONFIG = { export const SESSION_CONFIG = {
password: getRandomHex(256) password: getRandomHex(256),
} satisfies SessionConfig; } satisfies SessionConfig;
export const SERVER_DEBUG = debug('Server'); export const SERVER_DEBUG = debug('Server');

10
src/utils/crypto.ts

@ -1,5 +1,7 @@
export function getRandomHex(size: number) { export function getRandomHex(size: number) {
const array = new Uint8Array(size); const array = new Uint8Array(size);
crypto.getRandomValues(array); crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(
} ''
);
}

18
src/utils/ip.ts

@ -1,12 +1,12 @@
export function isValidIPv4(str) { export function isValidIPv4(str) {
const blocks = str.split('.'); const blocks = str.split('.');
if (blocks.length !== 4) return false; if (blocks.length !== 4) return false;
for (let value of blocks) { for (let value of blocks) {
value = parseInt(value, 10); value = parseInt(value, 10);
if (Number.isNaN(value)) return false; if (Number.isNaN(value)) return false;
if (value < 0 || value > 255) return false; if (value < 0 || value > 255) return false;
} }
return true; return true;
} }

47
src/utils/localStorage.ts

@ -1,25 +1,30 @@
export type Theme = 'light' | 'dark' | 'auto' export type Theme = 'light' | 'dark' | 'auto';
export type LocalStorage = { export type LocalStorage = {
theme: Theme, theme: Theme;
uiShowCharts: '1' | '0', uiShowCharts: '1' | '0';
lang: string lang: string;
} };
export function getItem<K extends keyof LocalStorage>(item: K): LocalStorage[K]|null { export function getItem<K extends keyof LocalStorage>(
if (import.meta.client) { item: K
return localStorage.getItem(item) as LocalStorage[K]|null ): LocalStorage[K] | null {
} else { if (import.meta.client) {
return null return localStorage.getItem(item) as LocalStorage[K] | null;
} } else {
return null;
}
} }
export function setItem<K extends keyof LocalStorage>(item: K, value: LocalStorage[K]) { export function setItem<K extends keyof LocalStorage>(
if (import.meta.client) { item: K,
localStorage.setItem(item, value) value: LocalStorage[K]
) {
return true if (import.meta.client) {
} else { localStorage.setItem(item, value);
return false
} return true;
} } else {
return false;
}
}

20
src/utils/password.ts

@ -1,6 +1,6 @@
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { PASSWORD_HASH } from "~/utils/config"; import { PASSWORD_HASH } from '~/utils/config';
/** /**
* Checks if `password` matches the PASSWORD_HASH. * Checks if `password` matches the PASSWORD_HASH.
@ -11,13 +11,13 @@ import { PASSWORD_HASH } from "~/utils/config";
* @returns {boolean} true if matching environment, otherwise false * @returns {boolean} true if matching environment, otherwise false
*/ */
export function isPasswordValid(password: string): boolean { export function isPasswordValid(password: string): boolean {
if (typeof password !== 'string') { if (typeof password !== 'string') {
return false;
}
if (PASSWORD_HASH) {
return bcrypt.compareSync(password, PASSWORD_HASH);
}
return false; return false;
}; }
if (PASSWORD_HASH) {
return bcrypt.compareSync(password, PASSWORD_HASH);
}
return false;
}

7
src/wgpw.js

@ -6,7 +6,7 @@ const generateHash = async (password) => {
try { try {
const salt = await bcrypt.genSalt(12); const salt = await bcrypt.genSalt(12);
const hash = await bcrypt.hash(password, salt); const hash = await bcrypt.hash(password, salt);
console.log(`PASSWORD_HASH='${hash}'`); console.log(`PASSWORD_HASH='${hash}'`);
} catch (error) { } catch (error) {
throw new Error(`Failed to generate hash : ${error}`); throw new Error(`Failed to generate hash : ${error}`);
@ -18,10 +18,8 @@ const comparePassword = async (password, hash) => {
try { try {
const match = await bcrypt.compare(password, hash); const match = await bcrypt.compare(password, hash);
if (match) { if (match) {
console.log('Password matches the hash !'); console.log('Password matches the hash !');
} else { } else {
console.log('Password does not match the hash.'); console.log('Password does not match the hash.');
} }
} catch (error) { } catch (error) {
@ -44,9 +42,8 @@ const comparePassword = async (password, hash) => {
await generateHash(password); await generateHash(password);
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
process.exit(1); process.exit(1);
} }
})(); })();

Loading…
Cancel
Save