Browse Source

add prettier, format

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

6
src/.prettierrc.json

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

1323
src/app.vue

File diff suppressed because it is too large

6
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({});

60
src/i18n.config.ts

@ -24,7 +24,8 @@ export default defineI18nConfig(() => ({
disableClient: 'Disable Client', disableClient: 'Disable Client',
enableClient: 'Enable Client', enableClient: 'Enable Client',
noClients: 'There are no clients yet.', noClients: 'There are no clients yet.',
noPrivKey: 'This client has no known private key. Cannot create Configuration.', noPrivKey:
'This client has no known private key. Cannot create Configuration.',
showQR: 'Show QR Code', showQR: 'Show QR Code',
downloadConfig: 'Download Configuration', downloadConfig: 'Download Configuration',
madeBy: 'Made by', madeBy: 'Made by',
@ -85,19 +86,25 @@ export default defineI18nConfig(() => ({
disableClient: 'Выключить клиента', disableClient: 'Выключить клиента',
enableClient: 'Включить клиента', enableClient: 'Включить клиента',
noClients: 'Пока нет клиентов.', noClients: 'Пока нет клиентов.',
noPrivKey: 'Невозможно создать конфигурацию: у клиента нет известного приватного ключа.', noPrivKey:
'Невозможно создать конфигурацию: у клиента нет известного приватного ключа.',
showQR: 'Показать QR-код', showQR: 'Показать QR-код',
downloadConfig: 'Скачать конфигурацию', downloadConfig: 'Скачать конфигурацию',
madeBy: 'Автор', madeBy: 'Автор',
donate: 'Поблагодарить', donate: 'Поблагодарить',
toggleCharts: 'Показать/скрыть графики', toggleCharts: 'Показать/скрыть графики',
theme: { dark: 'Темная тема', light: 'Светлая тема', auto: 'Как в системе' }, theme: {
dark: 'Темная тема',
light: 'Светлая тема',
auto: 'Как в системе',
},
restore: 'Восстановить', restore: 'Восстановить',
backup: 'Резервная копия', backup: 'Резервная копия',
titleRestoreConfig: 'Восстановить конфигурацию', titleRestoreConfig: 'Восстановить конфигурацию',
titleBackupConfig: 'Создать резервную копию конфигурации', titleBackupConfig: 'Создать резервную копию конфигурации',
}, },
tr: { // Müslüm Barış Korkmazer @babico tr: {
// Müslüm Barış Korkmazer @babico
name: 'İsim', name: 'İsim',
password: 'Şifre', password: 'Şifre',
signIn: 'Giriş Yap', signIn: 'Giriş Yap',
@ -119,19 +126,25 @@ export default defineI18nConfig(() => ({
disableClient: 'Kullanıcıyı Devre Dışı Bırak', disableClient: 'Kullanıcıyı Devre Dışı Bırak',
enableClient: 'Kullanıcıyı Etkinleştir', enableClient: 'Kullanıcıyı Etkinleştir',
noClients: 'Henüz kullanıcı yok.', noClients: 'Henüz kullanıcı yok.',
noPrivKey: 'Bu istemcinin bilinen bir özel anahtarı yok. Yapılandırma oluşturulamıyor.', noPrivKey:
'Bu istemcinin bilinen bir özel anahtarı yok. Yapılandırma oluşturulamıyor.',
showQR: 'QR Kodunu Göster', showQR: 'QR Kodunu Göster',
downloadConfig: 'Yapılandırmayı İndir', downloadConfig: 'Yapılandırmayı İndir',
madeBy: 'Yapan Kişi: ', madeBy: 'Yapan Kişi: ',
donate: 'Bağış Yap', donate: 'Bağış Yap',
toggleCharts: 'Grafiği göster/gizle', toggleCharts: 'Grafiği göster/gizle',
theme: { dark: 'Karanlık tema', light: 'Açık tema', auto: 'Otomatik tema' }, theme: {
dark: 'Karanlık tema',
light: 'Açık tema',
auto: 'Otomatik tema',
},
restore: 'Geri yükle', restore: 'Geri yükle',
backup: 'Yedekle', backup: 'Yedekle',
titleRestoreConfig: 'Yapılandırmanızı geri yükleyin', titleRestoreConfig: 'Yapılandırmanızı geri yükleyin',
titleBackupConfig: 'Yapılandırmanızı yedekleyin', titleBackupConfig: 'Yapılandırmanızı yedekleyin',
}, },
no: { // github.com/digvalley no: {
// github.com/digvalley
name: 'Navn', name: 'Navn',
password: 'Passord', password: 'Passord',
signIn: 'Logg Inn', signIn: 'Logg Inn',
@ -158,7 +171,8 @@ export default defineI18nConfig(() => ({
madeBy: 'Laget av', madeBy: 'Laget av',
donate: 'Doner', donate: 'Doner',
}, },
pl: { // github.com/archont94 pl: {
// github.com/archont94
name: 'Nazwa', name: 'Nazwa',
password: 'Hasło', password: 'Hasło',
signIn: 'Zaloguj się', signIn: 'Zaloguj się',
@ -185,7 +199,8 @@ export default defineI18nConfig(() => ({
madeBy: 'Stworzone przez', madeBy: 'Stworzone przez',
donate: 'Wsparcie autora', donate: 'Wsparcie autora',
}, },
fr: { // github.com/clem3109 fr: {
// github.com/clem3109
name: 'Nom', name: 'Nom',
password: 'Mot de passe', password: 'Mot de passe',
signIn: 'Se Connecter', signIn: 'Se Connecter',
@ -216,7 +231,8 @@ export default defineI18nConfig(() => ({
titleRestoreConfig: 'Restaurer votre configuration', titleRestoreConfig: 'Restaurer votre configuration',
titleBackupConfig: 'Sauvegarder votre configuration', titleBackupConfig: 'Sauvegarder votre configuration',
}, },
de: { // github.com/florian-asche de: {
// github.com/florian-asche
name: 'Name', name: 'Name',
password: 'Passwort', password: 'Passwort',
signIn: 'Anmelden', signIn: 'Anmelden',
@ -238,7 +254,8 @@ export default defineI18nConfig(() => ({
disableClient: 'Client deaktivieren', disableClient: 'Client deaktivieren',
enableClient: 'Client aktivieren', enableClient: 'Client aktivieren',
noClients: 'Es wurden noch keine Clients konfiguriert.', noClients: 'Es wurden noch keine Clients konfiguriert.',
noPrivKey: 'Es ist kein Private Key für diesen Client bekannt. Eine Konfiguration kann nicht erstellt werden.', noPrivKey:
'Es ist kein Private Key für diesen Client bekannt. Eine Konfiguration kann nicht erstellt werden.',
showQR: 'Zeige den QR Code', showQR: 'Zeige den QR Code',
downloadConfig: 'Konfiguration herunterladen', downloadConfig: 'Konfiguration herunterladen',
madeBy: 'Erstellt von', madeBy: 'Erstellt von',
@ -248,7 +265,8 @@ export default defineI18nConfig(() => ({
titleRestoreConfig: 'Stelle deine Konfiguration wieder her', titleRestoreConfig: 'Stelle deine Konfiguration wieder her',
titleBackupConfig: 'Sichere deine Konfiguration', titleBackupConfig: 'Sichere deine Konfiguration',
}, },
ca: { // github.com/guillembonet ca: {
// github.com/guillembonet
name: 'Nom', name: 'Nom',
password: 'Contrasenya', password: 'Contrasenya',
signIn: 'Iniciar sessió', signIn: 'Iniciar sessió',
@ -275,7 +293,8 @@ export default defineI18nConfig(() => ({
madeBy: 'Fet per', madeBy: 'Fet per',
donate: 'Donatiu', donate: 'Donatiu',
}, },
es: { // github.com/amarqz es: {
// github.com/amarqz
name: 'Nombre', name: 'Nombre',
password: 'Contraseña', password: 'Contraseña',
signIn: 'Iniciar sesión', signIn: 'Iniciar sesión',
@ -302,7 +321,11 @@ export default defineI18nConfig(() => ({
madeBy: 'Hecho por', madeBy: 'Hecho por',
donate: 'Donar', donate: 'Donar',
toggleCharts: 'Mostrar/Ocultar gráficos', toggleCharts: 'Mostrar/Ocultar gráficos',
theme: { dark: 'Modo oscuro', light: 'Modo claro', auto: 'Modo automático' }, theme: {
dark: 'Modo oscuro',
light: 'Modo claro',
auto: 'Modo automático',
},
restore: 'Restaurar', restore: 'Restaurar',
backup: 'Realizar copia de seguridad', backup: 'Realizar copia de seguridad',
titleRestoreConfig: 'Restaurar su configuración', titleRestoreConfig: 'Restaurar su configuración',
@ -512,7 +535,7 @@ export default defineI18nConfig(() => ({
cancel: 'Annulla', cancel: 'Annulla',
create: 'Crea', create: 'Crea',
createdOn: 'Creato il ', createdOn: 'Creato il ',
lastSeen: 'Visto l\'ultima volta il ', lastSeen: "Visto l'ultima volta il ",
totalDownload: 'Totale Download: ', totalDownload: 'Totale Download: ',
totalUpload: 'Totale Upload: ', totalUpload: 'Totale Upload: ',
newClient: 'Nuovo Client', newClient: 'Nuovo Client',
@ -555,7 +578,8 @@ export default defineI18nConfig(() => ({
madeBy: 'สร้างโดย', madeBy: 'สร้างโดย',
donate: 'บริจาค', donate: 'บริจาค',
}, },
hi: { // github.com/rahilarious hi: {
// github.com/rahilarious
name: 'नाम', name: 'नाम',
password: 'पासवर्ड', password: 'पासवर्ड',
signIn: 'लॉगिन', signIn: 'लॉगिन',
@ -583,5 +607,5 @@ export default defineI18nConfig(() => ({
madeBy: 'सर्जक', madeBy: 'सर्जक',
donate: 'दान करें', donate: 'दान करें',
}, },
} },
})) }));

2
src/middleware/session.ts

@ -2,4 +2,4 @@ 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"
}, },

2
src/plugins/apexcharts.client.ts

@ -1,4 +1,4 @@
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

2
src/public/manifest.json

@ -8,4 +8,4 @@
"type": "image/png" "type": "image/png"
} }
] ]
} }

6
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}"`;
}) });

6
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;
}) });

18
src/server/api/session.ts

@ -1,8 +1,12 @@
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)
@ -12,7 +16,7 @@ export default defineEventHandler(async (event) => {
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);
@ -33,13 +37,13 @@ export default defineEventHandler(async (event) => {
} }
const data = await session.update({ const data = await session.update({
authenticated: true authenticated: true,
}); });
SERVER_DEBUG(`New Session: ${data.id}`); SERVER_DEBUG(`New Session: ${data.id}`);
return { success: true }; return { success: true };
} else if (isMethod(event, "DELETE")) { } else if (isMethod(event, 'DELETE')) {
const session = await useSession(event, SESSION_CONFIG); const session = await useSession(event, SESSION_CONFIG);
const sessionId = session.id; const sessionId = session.id;
@ -48,4 +52,4 @@ export default defineEventHandler(async (event) => {
SERVER_DEBUG(`Deleted Session: ${sessionId}`); SERVER_DEBUG(`Deleted Session: ${sessionId}`);
return { success: true }; return { success: true };
} }
}) });

6
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}"`;
}) });

6
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}"`;
}) });

6
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;
}) });

12
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 (
clientId === '__proto__' ||
clientId === 'constructor' ||
clientId === 'prototype'
) {
throw createError({ status: 403 }); throw createError({ status: 403 });
} }
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 };
}) });

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

@ -1,7 +1,7 @@
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 });
@ -10,7 +10,11 @@ export default defineEventHandler(async (event) => {
.replace(/(-{2,}|-$)/g, '-') .replace(/(-{2,}|-$)/g, '-')
.replace(/-$/, '') .replace(/-$/, '')
.substring(0, 32); .substring(0, 32);
setHeader(event, 'Content-Disposition', `attachment; filename="${configName || clientId}.conf"`); setHeader(
event,
'Content-Disposition',
`attachment; filename="${configName || clientId}.conf"`
);
setHeader(event, 'Content-Type', 'text/plain'); setHeader(event, 'Content-Type', 'text/plain');
return config; return config;
}) });

12
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 (
clientId === '__proto__' ||
clientId === 'constructor' ||
clientId === 'prototype'
) {
throw createError({ status: 403 }); throw createError({ status: 403 });
} }
await WireGuard.disableClient({ clientId }); await WireGuard.disableClient({ clientId });
return { success: true }; return { success: true };
}) });

12
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 (
clientId === '__proto__' ||
clientId === 'constructor' ||
clientId === 'prototype'
) {
throw createError({ status: 403 }); throw createError({ status: 403 });
} }
await WireGuard.enableClient({ clientId }); await WireGuard.enableClient({ clientId });
return { success: true }; return { success: true };
}) });

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

@ -1,7 +1,7 @@
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 };

12
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 (
clientId === '__proto__' ||
clientId === 'constructor' ||
clientId === 'prototype'
) {
throw createError({ status: 403 }); throw createError({ status: 403 });
} }
const { name } = await readBody(event); const { name } = await readBody(event);
await WireGuard.updateClientName({ clientId, name }); await WireGuard.updateClientName({ clientId, name });
return { success: true }; return { success: true };
}) });

6
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;
}) });

8
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 };
} }
}) });

6
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;

59
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(
path.join(WG_PATH, 'wg0.json'),
JSON.stringify(config, false, 2),
{
mode: 0o660, 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,7 +154,8 @@ ${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(
([clientId, client]) => ({
id: clientId, id: clientId,
name: client.name, name: client.name,
enabled: client.enabled, enabled: client.enabled,
@ -144,7 +169,8 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
latestHandshakeAt: null, latestHandshakeAt: null,
transferRx: null, transferRx: null,
transferTx: 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', {
@ -169,7 +195,8 @@ ${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 =
latestHandshakeAt === '0'
? null ? null
: new Date(Number(`${latestHandshakeAt}000`)); : new Date(Number(`${latestHandshakeAt}000`));
client.transferRx = Number(transferRx); client.transferRx = Number(transferRx);
@ -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();

82
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[]) =>
clients.map((client) => ({
...client, ...client,
createdAt: new Date(client.createdAt), createdAt: new Date(client.createdAt),
updatedAt: new Date(client.updatedAt), updatedAt: new Date(client.updatedAt),
latestHandshakeAt: client.latestHandshakeAt !== null latestHandshakeAt:
client.latestHandshakeAt !== null
? new Date(client.latestHandshakeAt) ? new Date(client.latestHandshakeAt)
: null, : 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();

21
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(
cmd: string,
{ log }: { log: boolean | string } = { log: true }
) {
if (typeof log === 'string') { if (typeof log === 'string') {
console.log(`$ ${log}`); console.log(`$ ${log}`);
} else if (log === true) { } else if (log === true) {
console.log(`$ ${cmd}`); 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());
});
});
} }
);
});
}

31
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 =
typeof process.env.WG_DEFAULT_DNS === 'string'
? process.env.WG_DEFAULT_DNS ? process.env.WG_DEFAULT_DNS
: '1.1.1.1'; : '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');

4
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(
''
);
} }

2
src/utils/ip.ts

@ -9,4 +9,4 @@ export function isValidIPv4(str) {
} }
return true; return true;
} }

37
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>(
item: K,
value: LocalStorage[K]
) {
if (import.meta.client) { if (import.meta.client) {
localStorage.setItem(item, value) localStorage.setItem(item, value);
return true return true;
} else { } else {
return false return false;
}
} }
}

4
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.
@ -20,4 +20,4 @@ export function isPasswordValid(password: string): boolean {
} }
return false; return false;
}; }

3
src/wgpw.js

@ -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,7 +42,6 @@ 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