Compare commits

..

6 Commits

Author SHA1 Message Date
Bernd Storath a0b4192cbd Bump version to 15.2.1 2026-01-14 08:51:01 +01:00
Bernd Storath 32a055093a exclude i18n from setup middleware 2026-01-13 13:15:46 +01:00
Bernd Storath 51558c7027 refactor: session handling (#2398)
* refactor session handling

* simplify
2026-01-13 10:11:13 +01:00
binnichtaktiv b85286f0ab Lang(de): Improve and add missing translations (#2393)
* add missing german translations

* fix typos and improve existing translations

* fix punctuation

* fix last overlooked typos
2026-01-13 08:21:08 +01:00
Bernd Storath 48f3fbd715 always generate awg h1-h5 2026-01-13 08:07:13 +01:00
Bernd Storath 458f66818a fix search magnifying icon 2026-01-12 12:04:50 +01:00
19 changed files with 133 additions and 61 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
"aaron-bond.better-comments",
"dbaeumer.vscode-eslint",
"antfu.goto-alias",
"esbenp.prettier-vscode",
"prettier.prettier-vscode",
"yoavbls.pretty-ts-errors",
"bradlc.vscode-tailwindcss",
"vue.volar",
+5 -5
View File
@@ -1,22 +1,22 @@
{
"editor.tabSize": 2,
"editor.useTabStops": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "prettier.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "prettier.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "prettier.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "prettier.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "prettier.prettier-vscode",
"editor.tabSize": 4,
"editor.useTabStops": false
},
+8
View File
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [15.2.1] - 2026-01-14
### Fixed
- Icon in Searchbar (https://github.com/wg-easy/wg-easy/commit/458f66818a400f181e2c6326ede077c8793d71f2)
- Interface save not working (https://github.com/wg-easy/wg-easy/commit/48f3fbd715a889e2425702a8a46332f2752aef91)
- Error Messages in Setup (https://github.com/wg-easy/wg-easy/commit/32a055093a76342c40858d8dcf563b0700a8bd48)
## [15.2.0] - 2026-01-12
### Added
+1
View File
@@ -30,6 +30,7 @@ echo "Updated package.json to version $new_version"
echo "----"
echo "If you changed the major version, remember to update the docker-compose.yml file and docs (search for: ref: major version)"
echo "Make sure to stage any changes before proceeding (e.g. Changelog updates)."
echo "----"
echo "If you did everything press 'y' to commit the changes and create a new tag"
+2 -2
View File
@@ -1,14 +1,14 @@
<template>
<div class="relative w-60 md:mr-2">
<div class="relative flex h-full items-center">
<MagnifyingGlassIcon
<IconsMagnifyingGlass
class="absolute left-2.5 h-4 w-4 text-gray-400 dark:text-neutral-500"
/>
<input
v-model="searchQuery"
type="text"
:placeholder="$t('client.search')"
class="w-full rounded bg-white py-2 pr-8 text-sm text-gray-900 shadow-sm ring-1 ring-gray-300 transition-all placeholder:text-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-red-600 dark:bg-neutral-800 dark:text-white dark:ring-neutral-700 dark:placeholder:text-neutral-500 dark:focus:ring-red-700"
class="w-full rounded bg-white px-8 py-2 text-sm text-gray-900 shadow-sm ring-1 ring-gray-300 transition-all placeholder:text-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-red-600 dark:bg-neutral-800 dark:text-white dark:ring-neutral-700 dark:placeholder:text-neutral-500 dark:focus:ring-red-700"
@input="updateSearch"
/>
<button
@@ -0,0 +1,7 @@
<template>
<MagnifyingGlassIcon />
</template>
<script lang="ts" setup>
import MagnifyingGlassIcon from '@heroicons/vue/24/outline/esm/MagnifyingGlassIcon';
</script>
+6 -4
View File
@@ -4,25 +4,27 @@ export default defineNuxtRouteMiddleware(async (to) => {
return;
}
const event = useRequestEvent();
const authStore = useAuthStore();
const userData = await authStore.getSession();
authStore.userData = await authStore.getSession(event);
// skip login if already logged in
if (to.path === '/login') {
if (userData?.username) {
if (authStore.userData?.username) {
return navigateTo('/', { redirectCode: 302 });
}
return;
}
// Require auth for every page other than Login
if (!userData?.username) {
if (!authStore.userData?.username) {
return navigateTo('/login', { redirectCode: 302 });
}
// Check for admin access
if (to.path.startsWith('/admin')) {
if (!hasPermissions(userData, 'admin', 'any')) {
if (!hasPermissions(authStore.userData, 'admin', 'any')) {
return abortNavigation('Not allowed to access Admin Panel');
}
}
-3
View File
@@ -38,9 +38,6 @@
</template>
<script setup lang="ts">
const authStore = useAuthStore();
authStore.update();
const { t } = useI18n();
const route = useRoute();
-2
View File
@@ -206,9 +206,7 @@
</template>
<script lang="ts" setup>
const authStore = useAuthStore();
const globalStore = useGlobalStore();
authStore.update();
const route = useRoute();
const id = route.params.id as string;
-3
View File
@@ -29,9 +29,6 @@
</template>
<script setup lang="ts">
const authStore = useAuthStore();
authStore.update();
const globalStore = useGlobalStore();
const clientsStore = useClientsStore();
-3
View File
@@ -67,9 +67,6 @@
</template>
<script setup lang="ts">
const authStore = useAuthStore();
authStore.update();
const toast = useToast();
const { t } = useI18n();
-1
View File
@@ -120,7 +120,6 @@
import { encodeQR } from 'qr';
const authStore = useAuthStore();
authStore.update();
const name = ref(authStore.userData?.name);
const email = ref(authStore.userData?.email);
+14 -7
View File
@@ -1,18 +1,25 @@
export const useAuthStore = defineStore('Auth', () => {
const { data: userData, refresh: update } = useFetch('/api/session', {
method: 'get',
});
import type { H3Event } from 'h3';
import type { SharedPublicUser } from '~~/shared/utils/permissions';
async function getSession() {
export const useAuthStore = defineStore('Auth', () => {
const userData = useState<SharedPublicUser | null>('user-data', () => null);
async function getSession(event?: H3Event) {
const fetch = event?.$fetch || $fetch;
try {
const { data } = await useFetch('/api/session', {
const data = await fetch('/api/session', {
method: 'get',
});
return data.value;
return data;
} catch {
return null;
}
}
async function update() {
const data = await getSession();
userData.value = data;
}
return { userData, update, getSession };
});
+74 -25
View File
@@ -11,12 +11,12 @@
}
},
"user": {
"email": "Email"
"email": "E-Mail"
},
"me": {
"currentPassword": "Aktuelles Passwort",
"enable2fa": "Zwei-Faktor-Authentifizierunng aktivieren",
"enable2faDesc": "Scannen Sie den QR-Code mit ihrer Authentifizierungs-App oder geben Sie den Schlüssel manuell ein.",
"enable2fa": "Zwei-Faktor-Authentifizierung aktivieren",
"enable2faDesc": "Scannen Sie den QR-Code mit Ihrer Authentifizierungs-App oder geben Sie den Schlüssel manuell ein.",
"2faKey": "TOTP-Schlüssel",
"2faCodeDesc": "Geben Sie den Code aus Ihrer Authentifizierungs-App ein.",
"disable2fa": "Zwei-Faktor-Authentifizierung deaktivieren",
@@ -45,26 +45,26 @@
},
"setup": {
"welcome": "Willkommen zur Ersteinrichtung von wg-easy",
"welcomeDesc": "Das ist der einfachste Weg, um Wireguard auf jedem Linux-Server zu installieren und zu betreiben.",
"welcomeDesc": "Sie haben den einfachsten Weg gefunden, WireGuard auf jedem Linux-Server zu installieren und zu verwalten.",
"existingSetup": "Haben Sie eine bestehende Einrichtung?",
"createAdminDesc": "Bitte geben Sie zuerst einen Admin-Benutzernamen sowie ein starkes, sicheres Passwort ein. Diese Anmeldedaten benötigen Sie, um sich im Admin-Panel anzumelden.",
"createAdminDesc": "Bitte geben Sie zuerst einen Admin-Benutzernamen sowie ein starkes, sicheres Passwort ein. Diese Anmeldedaten benötigen Sie, um sich in der Admin-Konsole anzumelden.",
"setupConfigDesc": "Bitte geben Sie die Host- und Portinformationen ein. Diese werden für die Client-Konfiguration verwendet, wenn Sie WireGuard auf Ihren Geräten einrichten.",
"setupMigrationDesc": "Bitte halten Sie die Sicherungsdatei bereit, wenn Sie Ihre Daten von Ihrer vorherigen wg-easy Version auf ihre neue Einrichtung migrieren möchten.",
"upload": "Hochladen",
"migration": "Sicherung wiederherstellen:",
"migration": "Backup wiederherstellen:",
"createAccount": "Konto erstellen",
"successful": "Einrichtung erfolgreich",
"hostDesc": "Öffentlicher Hostname mit dem sich die Clients verbinden",
"portDesc": "Öffentlicher UDP-Port an dem sich die Clients verbinden und auf dem Wireguard läuft"
"portDesc": "Öffentlicher UDP-Port an dem sich die Clients verbinden und auf dem WireGuard läuft"
},
"update": {
"updateAvailable": "Es ist ein neue Aktualisierung verfügbar!",
"updateAvailable": "Ein neues Update ist verfügbar!",
"update": "Aktualisieren"
},
"theme": {
"dark": "Dunkles Thema",
"light": "Helles Thema",
"system": "System-Thema"
"system": "System Thema"
},
"layout": {
"toggleCharts": "Statistiken ein-/ausblenden",
@@ -84,16 +84,16 @@
"sort": "Sortieren",
"create": "Client erstellen",
"created": "Client wurde erstellt",
"new": "Neuer client",
"new": "Neuer Client",
"name": "Name",
"expireDate": "Ablaufdatum",
"expireDateDesc": "Datum, an dem der Client deaktiviert wird. Leer lassen, damit dies nie passiert.",
"expireDateDesc": "Datum, an dem der Client deaktiviert wird. Leer lassen für dauerhaft aktiv.",
"deleteClient": "Client löschen",
"deleteDialog1": "Sind Sie sicher, dass Sie diesen Client löschen wollen",
"deleteDialog1": "Sind Sie sicher, dass Sie diesen Client löschen möchten",
"deleteDialog2": "Diese Aktion kann nicht rückgängig gemacht werden.",
"enabled": "Aktiviert",
"address": "Adresse",
"serverAllowedIps": "serverseitig erlaubte IP-Adressen",
"serverAllowedIps": "Serverseitig erlaubte IP-Adressen",
"otlDesc": "Einen kurzen Einmal-Link erzeugen",
"permanent": "Dauerhaft",
"createdOn": "Angelegt am ",
@@ -112,8 +112,15 @@
"persistentKeepaliveDesc": "Legt das Intervall (in Sekunden) für Keepalive-Pakete fest. 0 deaktiviert es",
"hooks": "Hooks",
"hooksDescription": "Hooks funktionieren nur mit wg-quick",
"hooksLeaveEmpty": "Nur für wg-quick. Sonst leer lassen",
"dnsDesc": "DNS-Server, den die Clients benutzen (überschreibt die globale Konfiguration)"
"hooksLeaveEmpty": "Nur für wg-quick. Andernfalls leer lassen",
"dnsDesc": "DNS-Server, den die Clients benutzen (überschreibt die globale Konfiguration)",
"delete": "Löschen",
"notConnected": "Client nicht verbunden",
"endpoint": "Endpunkt",
"endpointDesc": "IP-Adresse des Clients, von dem aus die WireGuard-Verbindung hergestellt wird",
"search": "Suche Clients...",
"config": "Konfiguration",
"viewConfig": "Konfiguration anzeigen"
},
"dialog": {
"change": "Ändern",
@@ -150,24 +157,24 @@
"config": {
"connection": "Verbindung",
"hostDesc": "Öffentlicher Hostname mit dem sich die Clients verbinden (überschreibt die Konfiguration)",
"portDesc": "Öffentlicher UDP-Port an dem sich die Clients verbinden (überschreibt die Konfiguration, vermutlich wollen Sie ebenfalls den Port der Weboberfläche ändern)",
"portDesc": "Öffentlicher UDP-Port an dem sich die Clients verbinden (überschreibt die Konfiguration, vermutlich wollen Sie auch den Interface-Port ändern)",
"allowedIpsDesc": "Erlaubte IP-Adressen, die die Clients nutzen werden (Globale Konfiguration)",
"dnsDesc": "DNS-Server, den die Clients nutzen werden (Globale Konfiguration)",
"mtuDesc": "MTU, den die Clients benutzen werden (nur für neue Clients)",
"persistentKeepaliveDesc": "Intervall in Sekunden, in dem Keepalive-Packete an den Server gesendet werden. 0 = deaktiviert (nur für neue Clients)",
"persistentKeepaliveDesc": "Intervall in Sekunden, in dem Keepalive-Pakete an den Server gesendet werden. 0 = deaktiviert (nur für neue Clients)",
"suggest": "Vorschlagen",
"suggestDesc": "Wählen Sie eine IP-Adresse oder einen Hostnamen für das Host-Feld aus"
},
"interface": {
"cidrSuccess": "CIDR wurde geändert",
"device": "Gerät",
"deviceDesc": "Ethernet-Gerät, durch das der Wireguard-Datenverkehr geleitet werden soll",
"deviceDesc": "Ethernet-Gerät, durch das der WireGuard-Datenverkehr geleitet werden soll",
"mtuDesc": "MTU, den WireGuard benutzen wird",
"portDesc": "UDP Port, auf dem WireGuard lauschen wird (Sie wollen wahrscheinlich auch den Config-Port ändern)",
"portDesc": "UDP-Port, auf dem WireGuard lauschen wird (Sie wollen wahrscheinlich auch den Interface-Port ändern)",
"changeCidr": "CIDR ändern",
"restart": "Interface neu starten",
"restartDesc": "Das WireGuard-Interface neu starten",
"restartWarn": "Sind Sie sicher, dass Sie das Interface neu starten wollen? Dies wird die Verbindungen aller Clients trennen.",
"restartWarn": "Sind Sie sicher, dass Sie das Interface neu starten möchten? Dies wird die Verbindungen aller Clients trennen.",
"restartSuccess": "Interface neu gestartet"
},
"introText": "Willkommen in der Admin-Konsole.\n\nHier können Sie die allgemeinen Einstellungen, die Konfiguration, die Schnittstelleneinstellungen und die Hooks verwalten.\n\nBeginnen Sie, indem Sie einen der Bereiche in der Seitenleiste auswählen."
@@ -188,15 +195,15 @@
"expiresAt": "Läuft ab am",
"address4": "IPv4-Adresse",
"address6": "IPv6-Adresse",
"serverAllowedIps": "serverseitig erlaubte IP-Adressen"
"serverAllowedIps": "Serverseitig erlaubte IP-Adressen"
},
"user": {
"username": "Benutzername",
"password": "Passwort",
"remember": "Merken",
"name": "Name",
"email": "Email",
"emailInvalid": "Die Email-Adresse muss valide sein",
"email": "E-Mail",
"emailInvalid": "Die E-Mail-Adresse muss gültig sein",
"passwordMatch": "Die Passwörter müssen übereinstimmen",
"totpEnable": "TOTP aktivieren",
"totpEnableTrue": "\"TOTP aktivieren\" muss ausgewählt sein",
@@ -212,8 +219,8 @@
},
"interface": {
"cidr": "CIDR",
"device": "Geräte",
"cidrValid": "CIDR muss valide sein"
"device": "Gerät",
"cidrValid": "CIDR muss gültig sein"
},
"otl": "Einmal-Link",
"stringMalformed": "Zeichenkette ist fehlerhaft",
@@ -233,5 +240,47 @@
"postUp": "PostUp",
"preDown": "PreDown",
"postDown": "PostDown"
},
"copy": {
"notSupported": "Kopieren ist nicht unterstützt",
"copied": "Kopiert!",
"failed": "Kopieren fehlgeschlagen",
"copy": "Kopieren"
},
"awg": {
"jCLabel": "Anzahl der Junk-Pakete (Jc)",
"jCDescription": "Anzahl der zu sendenden Junk-Pakete (1-128, empfohlen: 4-12)",
"jMinLabel": "Minimale Junk-Paketgröße (Jmin)",
"jMinDescription": "Mindestgröße von Junk-Paketen (0-1279*, empfohlen: 8, muss < Jmax sein)",
"jMaxLabel": "Maximale Junk-Paketgröße (Jmax)",
"jMaxDescription": "Maximalgröße von Junk-Paketen (1-1280*, empfohlen: 80, muss > Jmin sein)",
"s1Label": "Junk-Paketgröße des Init-Pakets (S1)",
"s1Description": "Junk-Paketgröße des Init-Pakets (0-1132[1280* - 148 = 1132], empfohlen: 15-150, S1+56 ≠ S2)",
"s2Label": "Junk-Paketgröße des Antwort-Pakets (S2)",
"s2Description": "Junk-Paketgröße des Antwort-Pakets (0-1188[1280* - 92 = 1188], empfohlen: 15-150)",
"s3Label": "Junk-Paketgröße des Cookie-Antwort-Pakets (S3)",
"s3Description": "Junk-Paketgröße des Cookie-Antwort-Pakets",
"s4Label": "Junk-Paketgröße des Transport-Pakets (S4)",
"s4Description": "Junk-Paketgröße des Transport-Pakets",
"i1Label": "Spezial-Junk-Paket 1 (I1)",
"i1Description": "Protokoll-Nachahmungspaket im Hex-Format: <b 0x...>",
"i2Label": "Spezial-Junk-Paket 2 (I2)",
"i2Description": "Protokoll-Nachahmungspaket im Hex-Format: <b 0x...>",
"i3Label": "Spezial-Junk-Paket 3 (I3)",
"i3Description": "Protokoll-Nachahmungspaket im Hex-Format: <b 0x...>",
"i4Label": "Spezial-Junk-Paket 4 (I4)",
"i4Description": "Protokoll-Nachahmungspaket im Hex-Format: <b 0x...>",
"i5Label": "Spezial-Junk-Paket 5 (I5)",
"i5Description": "Protokoll-Nachahmungspaket im Hex-Format: <b 0x...>",
"h1Label": "Init-Magic-Header (H1)",
"h1Description": "Wert des Init-Paket-Headers (5-2147483647, muss eindeutig zu H2-H4 sein)",
"h2Label": "Antwort-Magic-Header (H2)",
"h2Description": "Wert des Antwort-Paket-Headers (5-2147483647, muss eindeutig zu H1, H3, H4 sein)",
"h3Label": "Cookie-Antwort-Magic-Header (H3)",
"h3Description": "Wert des Cookie-Antwort-Paket-Headers (5-2147483647, muss eindeutig zu H1, H2, H4 sein)",
"h4Label": "Transport-Magic-Header (H4)",
"h4Description": "Wert des Transport-Paket-Headers (5-2147483647, muss eindeutig zu H1-H3 sein)",
"mtuNote": "Werte hängen von der MTU ab",
"obfuscationParameters": "AmneziaWG Verschleierungsparameter"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wg-easy",
"version": "15.2.0",
"version": "15.2.1",
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
"private": true,
"type": "module",
+7 -2
View File
@@ -1,9 +1,14 @@
import type { SharedPublicUser } from '~~/shared/utils/permissions';
export default defineEventHandler(async (event) => {
const session = await useWGSession(event);
if (!session.data.userId) {
// not logged in
return null;
throw createError({
statusCode: 401,
statusMessage: 'Not authenticated',
});
}
const user = await Database.users.get(session.data.userId);
@@ -21,5 +26,5 @@ export default defineEventHandler(async (event) => {
name: user.name,
email: user.email,
totpVerified: user.totpVerified,
};
} satisfies SharedPublicUser;
});
+1 -1
View File
@@ -3,7 +3,7 @@ export default defineEventHandler(async (event) => {
const url = getRequestURL(event);
// User can't be logged in, and public routes can be accessed whenever
if (url.pathname.startsWith('/api/')) {
if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/_i18n/')) {
return;
}
+1 -1
View File
@@ -213,7 +213,7 @@ class WireGuard {
WG_DEBUG('New Wireguard Keys generated successfully.');
}
if (WG_ENV.WG_EXECUTABLE === 'awg' && wgInterface.h1 === 0) {
if (wgInterface.h1 === 0) {
WG_DEBUG('Generating random AmneziaWG obfuscation parameters...');
const headers = new Set<number>();
+5
View File
@@ -45,6 +45,11 @@ type SharedUserType =
| Pick<UserType, 'id' | 'role'>
| (Pick<UserType, 'id'> & { role: BrandedNumber });
export type SharedPublicUser = Pick<
UserType,
'id' | 'username' | 'name' | 'email' | 'totpVerified'
> & { role: BrandedNumber };
type PermissionCheck<Key extends keyof Permissions> =
| boolean
| ((user: SharedUserType, data: Permissions[Key]['dataType']) => boolean);