mirror of https://github.com/wg-easy/wg-easy
97 changed files with 9016 additions and 3165 deletions
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 167 KiB |
@ -0,0 +1 @@ |
|||
setups.@nuxt/test-utils="4.0.0" |
|||
@ -0,0 +1,7 @@ |
|||
:root { |
|||
color-scheme: light; |
|||
} |
|||
|
|||
.dark { |
|||
color-scheme: dark; |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
<template> |
|||
<textarea |
|||
v-model="data" |
|||
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" |
|||
/> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
const data = defineModel<string>(); |
|||
</script> |
|||
@ -0,0 +1,29 @@ |
|||
<template> |
|||
<div class="flex items-center"> |
|||
<FormLabel :for="id"> |
|||
{{ label }} |
|||
</FormLabel> |
|||
<BaseTooltip v-if="description" :text="description"> |
|||
<IconsInfo class="size-4" /> |
|||
</BaseTooltip> |
|||
</div> |
|||
<BaseTextArea |
|||
:id="id" |
|||
v-model.trim="data" |
|||
:name="id" |
|||
:autocomplete="autocomplete" |
|||
:disabled="disabled" |
|||
/> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
defineProps<{ |
|||
id: string; |
|||
label: string; |
|||
description?: string; |
|||
autocomplete?: string; |
|||
disabled?: boolean; |
|||
}>(); |
|||
|
|||
const data = defineModel<string>(); |
|||
</script> |
|||
@ -0,0 +1,7 @@ |
|||
<template> |
|||
<ClipboardDocumentIcon /> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import ClipboardDocumentIcon from '@heroicons/vue/24/outline/esm/ClipboardDocumentIcon'; |
|||
</script> |
|||
@ -0,0 +1,7 @@ |
|||
<template> |
|||
<MagnifyingGlassIcon /> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import MagnifyingGlassIcon from '@heroicons/vue/24/outline/esm/MagnifyingGlassIcon'; |
|||
</script> |
|||
@ -1,18 +1,25 @@ |
|||
import type { H3Event } from 'h3'; |
|||
import type { SharedPublicUser } from '~~/shared/utils/permissions'; |
|||
|
|||
export const useAuthStore = defineStore('Auth', () => { |
|||
const { data: userData, refresh: update } = useFetch('/api/session', { |
|||
method: 'get', |
|||
}); |
|||
const userData = useState<SharedPublicUser | null>('user-data', () => null); |
|||
|
|||
async function getSession() { |
|||
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 }; |
|||
}); |
|||
|
|||
@ -0,0 +1,71 @@ |
|||
import { defineCommand } from 'citty'; |
|||
import { consola } from 'consola'; |
|||
import { eq } from 'drizzle-orm'; |
|||
|
|||
import { db, schema } from '../db'; |
|||
import { hashPassword } from '../../server/utils/password'; |
|||
|
|||
export default defineCommand({ |
|||
meta: { |
|||
name: 'db:admin:reset', |
|||
description: 'Reset the admin user password and TOTP settings', |
|||
}, |
|||
args: { |
|||
password: { |
|||
type: 'string', |
|||
description: 'New password for the admin user', |
|||
required: false, |
|||
}, |
|||
}, |
|||
async run(ctx) { |
|||
let password = ctx.args.password || undefined; |
|||
if (!password) { |
|||
password = await consola.prompt('Please enter a new password:', { |
|||
type: 'text', |
|||
}); |
|||
} |
|||
if (!password) { |
|||
consola.error('Password is required'); |
|||
return; |
|||
} |
|||
if (password.length < 12) { |
|||
consola.error('Password must be at least 12 characters long'); |
|||
return; |
|||
} |
|||
consola.info('Setting new password for admin user...'); |
|||
const hash = await hashPassword(password); |
|||
|
|||
const user = await db.transaction(async (tx) => { |
|||
const user = await tx |
|||
.select() |
|||
.from(schema.user) |
|||
.where(eq(schema.user.id, 1)) |
|||
.get(); |
|||
|
|||
if (!user) { |
|||
consola.error('Admin user not found'); |
|||
return; |
|||
} |
|||
|
|||
await tx |
|||
.update(schema.user) |
|||
.set({ |
|||
password: hash, |
|||
totpVerified: false, |
|||
totpKey: null, |
|||
}) |
|||
.where(eq(schema.user.id, 1)); |
|||
|
|||
return user; |
|||
}); |
|||
|
|||
if (!user) { |
|||
consola.error('Failed to update admin user'); |
|||
return; |
|||
} |
|||
|
|||
consola.success( |
|||
`Successfully updated admin user ${user.id} (${user.username})` |
|||
); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,29 @@ |
|||
import { defineCommand } from 'citty'; |
|||
import { consola } from 'consola'; |
|||
|
|||
import { db } from '../db'; |
|||
|
|||
export default defineCommand({ |
|||
meta: { |
|||
name: 'clients:list', |
|||
description: 'List all clients', |
|||
}, |
|||
async run() { |
|||
consola.info('Listing all clients...'); |
|||
const clients = await db.query.client.findMany({ |
|||
columns: { |
|||
id: true, |
|||
name: true, |
|||
publicKey: true, |
|||
enabled: true, |
|||
}, |
|||
}); |
|||
|
|||
if (clients.length === 0) { |
|||
consola.info('No clients found'); |
|||
return; |
|||
} |
|||
|
|||
console.table(clients); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,71 @@ |
|||
import { defineCommand } from 'citty'; |
|||
import { consola } from 'consola'; |
|||
import { eq } from 'drizzle-orm'; |
|||
|
|||
import { wg } from '../../server/utils/wgHelper'; |
|||
import { encodeQRCodeTerm } from '../../server/utils/qr'; |
|||
import { db, schema } from '../db'; |
|||
|
|||
export default defineCommand({ |
|||
meta: { |
|||
name: 'clients:qr', |
|||
description: 'Generate QR code for a client', |
|||
}, |
|||
args: { |
|||
id: { |
|||
required: true, |
|||
type: 'positional', |
|||
}, |
|||
ipv6: { |
|||
required: false, |
|||
type: 'boolean', |
|||
default: true, |
|||
}, |
|||
}, |
|||
async run(ctx) { |
|||
const clientId = Number(ctx.args.id); |
|||
const enableIpv6 = ctx.args.ipv6; |
|||
|
|||
if (Number.isNaN(clientId)) { |
|||
consola.error('Invalid client ID'); |
|||
return; |
|||
} |
|||
|
|||
consola.info('Generating QR code for client...'); |
|||
|
|||
const wgInterface = await db.query.wgInterface.findFirst({ |
|||
where: eq(schema.wgInterface.name, 'wg0'), |
|||
}); |
|||
if (!wgInterface) { |
|||
consola.error('WireGuard interface not found'); |
|||
return; |
|||
} |
|||
|
|||
const userConfig = await db.query.userConfig.findFirst({ |
|||
where: eq(schema.userConfig.id, 'wg0'), |
|||
}); |
|||
if (!userConfig) { |
|||
consola.error('User config not found'); |
|||
return; |
|||
} |
|||
|
|||
const client = await db.query.client.findFirst({ |
|||
where: eq(schema.client.id, clientId), |
|||
}); |
|||
if (!client) { |
|||
consola.error(`Client with ID ${clientId} not found`); |
|||
return; |
|||
} |
|||
|
|||
const clientConfig = wg.generateClientConfig( |
|||
wgInterface, |
|||
userConfig, |
|||
client, |
|||
{ |
|||
enableIpv6, |
|||
} |
|||
); |
|||
|
|||
consola.log(encodeQRCodeTerm(clientConfig)); |
|||
}, |
|||
}); |
|||
@ -0,0 +1,10 @@ |
|||
import { createClient } from '@libsql/client'; |
|||
import { drizzle } from 'drizzle-orm/libsql'; |
|||
|
|||
import * as schema from '../server/database/schema'; |
|||
|
|||
//const client = createClient({ url: 'file:../data/wg-easy.db' });
|
|||
const client = createClient({ url: 'file:/etc/wireguard/wg-easy.db' }); |
|||
export const db = drizzle({ client, schema }); |
|||
|
|||
export { schema }; |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"lib": ["ESNext"], |
|||
"module": "esnext", |
|||
"target": "es2024", |
|||
"esModuleInterop": true, |
|||
"strict": true, |
|||
"skipLibCheck": true, |
|||
"moduleResolution": "bundler" |
|||
}, |
|||
"include": ["./**/*.ts"] |
|||
} |
|||
@ -0,0 +1,286 @@ |
|||
{ |
|||
"pages": { |
|||
"me": "Профил", |
|||
"clients": "Клиенти", |
|||
"admin": { |
|||
"panel": "Админ Панел", |
|||
"general": "Общи", |
|||
"config": "Конфигурация", |
|||
"interface": "Интерфейс", |
|||
"hooks": "Hooks" |
|||
} |
|||
}, |
|||
"user": { |
|||
"email": "Имейл" |
|||
}, |
|||
"me": { |
|||
"currentPassword": "Текуща парола", |
|||
"enable2fa": "Активирай двуфакторна автентикация", |
|||
"enable2faDesc": "Сканирай QR кода с твоето приложение за автентикатор или въведи ключа ръчно.", |
|||
"2faKey": "TOTP ключ", |
|||
"2faCodeDesc": "Въведи кода от твоето приложение за автентикатор.", |
|||
"disable2fa": "Деактивирай двуфакторна автентикация", |
|||
"disable2faDesc": "Въведи паролата си, за да деактивираш двуфакторната автентикация." |
|||
}, |
|||
"general": { |
|||
"name": "Име", |
|||
"username": "Потребителско име", |
|||
"password": "Парола", |
|||
"newPassword": "Нова парола", |
|||
"updatePassword": "Обнови парола", |
|||
"mtu": "MTU", |
|||
"allowedIps": "Разрешени IP-та", |
|||
"dns": "DNS", |
|||
"persistentKeepalive": "Постоянно поддържане на връзката", |
|||
"logout": "Изход", |
|||
"continue": "Продължи", |
|||
"host": "Хост", |
|||
"port": "Порт", |
|||
"yes": "Да", |
|||
"no": "Не", |
|||
"confirmPassword": "Потвърди парола", |
|||
"loading": "Зареждане...", |
|||
"2fa": "Двуфакторна автентикация", |
|||
"2faCode": "TOTP код" |
|||
}, |
|||
"setup": { |
|||
"welcome": "Добре дошъл в първоначалната настройка на wg-easy", |
|||
"welcomeDesc": "Откри най-лесния начин да инсталираш и управляваш WireGuard на всеки Linux сървър", |
|||
"existingSetup": "Имаш ли вече съществуваща инсталация?", |
|||
"createAdminDesc": "Моля, първо въведи администраторско потребителско име и силна сигурна парола. Тези данни ще се използват за вход в административния панел.", |
|||
"setupConfigDesc": "Моля, въведи хост и порт. Тази информация ще се използва при генериране на клиентски конфигурации за WireGuard.", |
|||
"setupMigrationDesc": "Ако желаеш да мигрираш данните от предишна версия на wg-easy, качи резервното копие.", |
|||
"upload": "Качи", |
|||
"migration": "Възстанови от резервно копие:", |
|||
"createAccount": "Създай акаунт", |
|||
"successful": "Настройката е успешна", |
|||
"hostDesc": "Публично име/адрес, към който клиентите ще се свързват", |
|||
"portDesc": "Публичен UDP порт, на който клиентите ще се свързват и на който WireGuard слуша" |
|||
}, |
|||
"update": { |
|||
"updateAvailable": "Налична е актуализация!", |
|||
"update": "Актуализирай" |
|||
}, |
|||
"theme": { |
|||
"dark": "Тъмна тема", |
|||
"light": "Светла тема", |
|||
"system": "Системна тема" |
|||
}, |
|||
"layout": { |
|||
"toggleCharts": "Покажи/скрий графики", |
|||
"donate": "Дарение" |
|||
}, |
|||
"login": { |
|||
"signIn": "Вход", |
|||
"rememberMe": "Запомни ме", |
|||
"rememberMeDesc": "Остани влязъл след затваряне на браузъра", |
|||
"insecure": "Не можеш да влезеш през несигурна връзка. Използвай HTTPS.", |
|||
"2faRequired": "Изисква се двуфакторна автентикация", |
|||
"2faWrong": "Грешен код за двуфакторна автентикация" |
|||
}, |
|||
"client": { |
|||
"empty": "Все още няма клиенти.", |
|||
"newShort": "Нов", |
|||
"sort": "Сортирай", |
|||
"create": "Създай клиент", |
|||
"created": "Клиентът е създаден", |
|||
"new": "Нов клиент", |
|||
"name": "Име", |
|||
"expireDate": "Дата на изтичане", |
|||
"expireDateDesc": "Дата, след която клиентът ще бъде деактивиран. Празно = постоянен", |
|||
"delete": "Изтрий", |
|||
"deleteClient": "Изтрий клиент", |
|||
"deleteDialog1": "Сигурен ли си, че искаш да изтриеш", |
|||
"deleteDialog2": "Това действие е необратимо.", |
|||
"enabled": "Активен", |
|||
"address": "Адрес", |
|||
"serverAllowedIps": "Разрешени IP-та от сървъра", |
|||
"otlDesc": "Генерирай кратък еднократен линк", |
|||
"permanent": "Постоянно", |
|||
"createdOn": "Създаден на ", |
|||
"lastSeen": "Последно видян на ", |
|||
"totalDownload": "Общо изтеглени: ", |
|||
"totalUpload": "Общо качени: ", |
|||
"newClient": "Нов клиент", |
|||
"disableClient": "Деактивирай клиент", |
|||
"enableClient": "Активирай клиент", |
|||
"noPrivKey": "Този клиент няма известен частен ключ. Не може да се създаде конфигурация.", |
|||
"showQR": "Покажи QR код", |
|||
"downloadConfig": "Изтегли конфигурация", |
|||
"allowedIpsDesc": "Кои IP-та ще се насочват през VPN (замества глобалната настройка)", |
|||
"serverAllowedIpsDesc": "Кои IP-та сървърът ще насочва към клиента", |
|||
"mtuDesc": "Задава максималния размер на пакета (MTU) за VPN тунела", |
|||
"persistentKeepaliveDesc": "Интервал (в секунди) за изпращане на keep-alive пакети. 0 = изключено", |
|||
"hooks": "Hooks", |
|||
"hooksDescription": "Hooks работят само с wg-quick", |
|||
"hooksLeaveEmpty": "Само за wg-quick. В противен случай остави празно", |
|||
"dnsDesc": "DNS сървър, който клиентите ще използват (замества глобалната настройка)", |
|||
"notConnected": "Клиентът не е свързан", |
|||
"endpoint": "Крайна точка", |
|||
"endpointDesc": "IP адресът на клиента, от който е установена WireGuard връзката", |
|||
"search": "Търси клиенти...", |
|||
"config": "Конфигурация", |
|||
"viewConfig": "Прегледай конфигурацията" |
|||
}, |
|||
"dialog": { |
|||
"change": "Промени", |
|||
"cancel": "Отказ", |
|||
"create": "Създай" |
|||
}, |
|||
"toast": { |
|||
"success": "Успех", |
|||
"saved": "Запазено", |
|||
"error": "Грешка" |
|||
}, |
|||
"form": { |
|||
"actions": "Действия", |
|||
"save": "Запази", |
|||
"revert": "Отмени промените", |
|||
"sectionGeneral": "Общи", |
|||
"sectionAdvanced": "Разширени", |
|||
"noItems": "Няма елементи", |
|||
"nullNoItems": "Няма елементи. Използва се глобалната конфигурация", |
|||
"add": "Добави" |
|||
}, |
|||
"admin": { |
|||
"general": { |
|||
"sessionTimeout": "Време на сесията", |
|||
"sessionTimeoutDesc": "Продължителност на сесията при „Запомни ме“ (в секунди)", |
|||
"metrics": "Метрики", |
|||
"metricsPassword": "Парола", |
|||
"metricsPasswordDesc": "Bearer парола за достъп до metrics ендпойнт (парола или argon2 хеш)", |
|||
"json": "JSON", |
|||
"jsonDesc": "Път за метрики в JSON формат", |
|||
"prometheus": "Prometheus", |
|||
"prometheusDesc": "Път за Prometheus метрики" |
|||
}, |
|||
"config": { |
|||
"connection": "Връзка", |
|||
"hostDesc": "Публично име/адрес за клиентите (инвалидира конфигурациите)", |
|||
"portDesc": "Публичен UDP порт за клиентите (инвалидира конфигурациите; вероятно искаш да смениш и порта на интерфейса)", |
|||
"allowedIpsDesc": "Разрешени IP-та за клиентите (глобална настройка)", |
|||
"dnsDesc": "DNS сървър за клиентите (глобална настройка)", |
|||
"mtuDesc": "MTU, който ще ползват клиентите (само за нови клиенти)", |
|||
"persistentKeepaliveDesc": "Интервал в секунди за keep-alive към сървъра. 0 = изключено (само за нови клиенти)", |
|||
"suggest": "Предложи", |
|||
"suggestDesc": "Избери IP адрес или хост за полето Host" |
|||
}, |
|||
"interface": { |
|||
"cidrSuccess": "CIDR променен", |
|||
"device": "Устройство", |
|||
"deviceDesc": "Мрежово устройство, през което да се препраща WireGuard трафикът", |
|||
"mtuDesc": "MTU, който ще ползва WireGuard", |
|||
"portDesc": "UDP порт, на който слуша WireGuard (вероятно искаш да смениш и порта в Config)", |
|||
"changeCidr": "Смени CIDR", |
|||
"restart": "Рестартирай интерфейс", |
|||
"restartDesc": "Рестартиране на WireGuard интерфейса", |
|||
"restartWarn": "Сигурен ли си, че искаш да рестартираш интерфейса? Всички клиенти ще бъдат изключени.", |
|||
"restartSuccess": "Интерфейсът е рестартиран" |
|||
}, |
|||
"introText": "Добре дошъл в административния панел.\n\nТук можеш да управляваш общите настройки, конфигурацията, настройките на интерфейса и hooks.\n\nЗапочни, като избереш някоя от секциите в страничното меню." |
|||
}, |
|||
"zod": { |
|||
"generic": { |
|||
"required": "{0} е задължително", |
|||
"validNumber": "{0} трябва да е валидно число", |
|||
"validString": "{0} трябва да е валиден текст", |
|||
"validBoolean": "{0} трябва да е валидна булева стойност", |
|||
"validArray": "{0} трябва да е валиден масив", |
|||
"stringMin": "{0} трябва да съдържа поне {1} символа", |
|||
"numberMin": "{0} трябва да е поне {1}" |
|||
}, |
|||
"client": { |
|||
"id": "ID на клиента", |
|||
"name": "Име", |
|||
"expiresAt": "Изтича на", |
|||
"address4": "IPv4 адрес", |
|||
"address6": "IPv6 адрес", |
|||
"serverAllowedIps": "Разрешени IP-та от сървъра" |
|||
}, |
|||
"user": { |
|||
"username": "Потребителско име", |
|||
"password": "Парола", |
|||
"remember": "Запомни", |
|||
"name": "Име", |
|||
"email": "Имейл", |
|||
"emailInvalid": "Имейлът трябва да е валиден", |
|||
"passwordMatch": "Паролите трябва да съвпадат", |
|||
"totpEnable": "Активиране на TOTP", |
|||
"totpEnableTrue": "TOTP Enable трябва да е true", |
|||
"totpCode": "TOTP код" |
|||
}, |
|||
"userConfig": { |
|||
"host": "Хост" |
|||
}, |
|||
"general": { |
|||
"sessionTimeout": "Време на сесията", |
|||
"metricsEnabled": "Метрики", |
|||
"metricsPassword": "Парола за метрики" |
|||
}, |
|||
"interface": { |
|||
"cidr": "CIDR", |
|||
"device": "Устройство", |
|||
"cidrValid": "CIDR трябва да е валиден" |
|||
}, |
|||
"otl": "Еднократен линк", |
|||
"stringMalformed": "Невалиден формат на низа", |
|||
"body": "Тялото трябва да е валиден обект", |
|||
"hook": "Hook", |
|||
"enabled": "Активиран", |
|||
"mtu": "MTU", |
|||
"port": "Порт", |
|||
"persistentKeepalive": "Постоянно поддържане на връзката", |
|||
"address": "IP адрес", |
|||
"dns": "DNS", |
|||
"allowedIps": "Разрешени IP-та", |
|||
"file": "Файл" |
|||
}, |
|||
"hooks": { |
|||
"preUp": "PreUp", |
|||
"postUp": "PostUp", |
|||
"preDown": "PreDown", |
|||
"postDown": "PostDown" |
|||
}, |
|||
"copy": { |
|||
"notSupported": "Копиране не се поддържа", |
|||
"copied": "Копирано!", |
|||
"failed": "Копирането неуспешно", |
|||
"copy": "Копирай" |
|||
}, |
|||
"awg": { |
|||
"jCLabel": "Брой junk пакети (Jc)", |
|||
"jCDescription": "Брой junk пакети за изпращане (1–128, препоръчително: 4–12)", |
|||
"jMinLabel": "Минимален размер на junk пакети (Jmin)", |
|||
"jMinDescription": "Минимален размер на junk пакетите (0–1279*, препоръчително: 8, трябва да е < Jmax)", |
|||
"jMaxLabel": "Максимален размер на junk пакети (Jmax)", |
|||
"jMaxDescription": "Максимален размер на junk пакетите (1–1280*, препоръчително: 80, трябва да е > Jmin)", |
|||
"s1Label": "Размер на junk в init пакета (S1)", |
|||
"s1Description": "Размер на junk в init пакета (0–1132 [1280*−148=1132], препоръчително: 15–150, S1+56 ≠ S2)", |
|||
"s2Label": "Размер на junk в отговорния пакет (S2)", |
|||
"s2Description": "Размер на junk в response пакета (0–1188 [1280*−92=1188], препоръчително: 15–150)", |
|||
"s3Label": "Размер на junk в cookie reply пакета (S3)", |
|||
"s3Description": "Размер на junk в cookie reply пакета", |
|||
"s4Label": "Размер на junk в транспортния пакет (S4)", |
|||
"s4Description": "Размер на junk в транспортния пакет", |
|||
"h1Label": "Init magic header (H1)", |
|||
"h1Description": "Стойност на хедера в init пакета (5–2147483647, уникална спрямо H2–H4)", |
|||
"h2Label": "Response magic header (H2)", |
|||
"h2Description": "Стойност на хедера в response пакета (5–2147483647, уникална спрямо H1, H3, H4)", |
|||
"h3Label": "Cookie reply magic header (H3)", |
|||
"h3Description": "Стойност на хедера в cookie reply пакета (5–2147483647, уникална спрямо H1, H2, H4)", |
|||
"h4Label": "Transport magic header (H4)", |
|||
"h4Description": "Стойност на хедера в транспортния пакет (5–2147483647, уникална спрямо H1–H3)", |
|||
"i1Label": "Специален junk пакет 1 (I1)", |
|||
"i1Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
|||
"i2Label": "Специален junk пакет 2 (I2)", |
|||
"i2Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
|||
"i3Label": "Специален junk пакет 3 (I3)", |
|||
"i3Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
|||
"i4Label": "Специален junk пакет 4 (I4)", |
|||
"i4Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
|||
"i5Label": "Специален junk пакет 5 (I5)", |
|||
"i5Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
|||
"mtuNote": "Стойностите зависят от MTU", |
|||
"obfuscationParameters": "Параметри за обфускация на AmneziaWG" |
|||
} |
|||
} |
|||
@ -0,0 +1,287 @@ |
|||
{ |
|||
"pages": { |
|||
"me": "Účet", |
|||
"clients": "Klienti", |
|||
"admin": { |
|||
"panel": "Administrace", |
|||
"general": "Obecné", |
|||
"config": "Konfigurace", |
|||
"interface": "Rozhraní", |
|||
"hooks": "Nastavení reakcí" |
|||
} |
|||
}, |
|||
"user": { |
|||
"email": "E-mail" |
|||
}, |
|||
"me": { |
|||
"currentPassword": "Aktuální heslo", |
|||
"enable2fa": "Zapnout dvoufázové ověření (2FA)", |
|||
"enable2faDesc": "Naskenujte QR kód ve své autentizační aplikaci nebo zadejte klíč ručně.", |
|||
"2faKey": "TOTP klíč", |
|||
"2faCodeDesc": "Zadejte kód z vaší autentizační aplikace.", |
|||
"disable2fa": "Vypnout dvoufázové ověření", |
|||
"disable2faDesc": "Pro vypnutí dvoufázového ověření zadejte své heslo." |
|||
}, |
|||
"general": { |
|||
"name": "Jméno", |
|||
"username": "Uživatelské jméno", |
|||
"password": "Heslo", |
|||
"newPassword": "Nové heslo", |
|||
"updatePassword": "Aktualizovat heslo", |
|||
"mtu": "MTU", |
|||
"allowedIps": "Povolené IP adresy", |
|||
"dns": "DNS", |
|||
"persistentKeepalive": "Persistent Keepalive", |
|||
"logout": "Odhlásit se", |
|||
"continue": "Pokračovat", |
|||
"host": "Hostitel", |
|||
"port": "Port", |
|||
"yes": "Ano", |
|||
"no": "Ne", |
|||
"confirmPassword": "Potvrdit heslo", |
|||
"loading": "Načítání...", |
|||
"2fa": "Dvoufázové ověření", |
|||
"2faCode": "TOTP kód" |
|||
}, |
|||
"setup": { |
|||
"welcome": "Vítejte u první instalace wg-easy", |
|||
"welcomeDesc": "Našli jste nejjednodušší způsob, jak instalovat a spravovat WireGuard na jakémkoliv Linuxovém hostiteli", |
|||
"existingSetup": "Máte již existující nastavení?", |
|||
"createAdminDesc": "Nejprve zadejte uživatelské jméno administrátora a silné heslo. Tyto údaje budou použity pro přihlášení do administrace.", |
|||
"setupConfigDesc": "Zadejte údaje o hostiteli a portu. Tyto informace budou použity pro konfiguraci klientů při nastavování WireGuard na jejich zařízeních.", |
|||
"setupMigrationDesc": "Pokud chcete migrovat data z předchozí verze wg-easy do nové instalace, nahrajte soubor se zálohou.", |
|||
"upload": "Nahrát", |
|||
"migration": "Obnovit zálohu:", |
|||
"createAccount": "Vytvořit účet", |
|||
"successful": "Nastavení bylo úspěšné", |
|||
"hostDesc": "Veřejný název hostitele, ke kterému se budou klienti připojovat", |
|||
"portDesc": "Veřejný UDP port, ke kterému se budou klienti připojovat a na kterém bude WireGuard naslouchat" |
|||
}, |
|||
"update": { |
|||
"updateAvailable": "Je k dispozici aktualizace!", |
|||
"update": "Aktualizovat" |
|||
}, |
|||
"theme": { |
|||
"dark": "Tmavý režim", |
|||
"light": "Světlý režim", |
|||
"system": "Systémové nastavení" |
|||
}, |
|||
"layout": { |
|||
"toggleCharts": "Zobrazit/skrýt grafy", |
|||
"donate": "Přispět" |
|||
}, |
|||
"login": { |
|||
"signIn": "Přihlásit se", |
|||
"rememberMe": "Zapamatovat si mě", |
|||
"rememberMeDesc": "Zůstat přihlášen i po zavření prohlížeče", |
|||
"insecure": "Nemůžete se přihlásit přes nezabezpečené připojení. Použijte HTTPS.", |
|||
"2faRequired": "Je vyžadováno dvoufázové ověření", |
|||
"2faWrong": "Neplatný kód dvoufázového ověření" |
|||
}, |
|||
"client": { |
|||
"empty": "Zatím zde nejsou žádní klienti.", |
|||
"newShort": "Nový", |
|||
"sort": "Seřadit", |
|||
"create": "Vytvořit klienta", |
|||
"created": "Klient vytvořen", |
|||
"new": "Nový klient", |
|||
"name": "Jméno", |
|||
"expireDate": "Datum vypršení", |
|||
"expireDateDesc": "Datum, kdy bude klient deaktivován. Ponechte prázdné pro trvalý přístup.", |
|||
"delete": "Smazat", |
|||
"deleteClient": "Smazat klienta", |
|||
"deleteDialog1": "Opravdu chcete smazat uživatele", |
|||
"deleteDialog2": "Tuto akci nelze vzít zpět.", |
|||
"enabled": "Aktivní", |
|||
"address": "Adresa", |
|||
"serverAllowedIps": "Povolené IP adresy serveru", |
|||
"otlDesc": "Generovat krátký jednorázový odkaz", |
|||
"permanent": "Trvalý", |
|||
"createdOn": "Vytvořeno dne ", |
|||
"lastSeen": "Naposledy viděn ", |
|||
"totalDownload": "Celkem staženo: ", |
|||
"totalUpload": "Celkem nahráno: ", |
|||
"newClient": "Nový klient", |
|||
"disableClient": "Deaktivovat klienta", |
|||
"enableClient": "Aktivovat klienta", |
|||
"noPrivKey": "Tento klient nemá známý soukromý klíč. Nelze vytvořit konfiguraci.", |
|||
"showQR": "Zobrazit QR kód", |
|||
"downloadConfig": "Stáhnout konfiguraci", |
|||
"allowedIpsDesc": "Které IP adresy budou směrovány přes VPN (přebíjí globální nastavení)", |
|||
"serverAllowedIpsDesc": "Které IP adresy bude server směrovat ke klientovi", |
|||
"mtuDesc": "Nastavuje maximální velikost přenášeného paketu (MTU) pro VPN tunel", |
|||
"persistentKeepaliveDesc": "Nastavuje interval (v sekundách) pro udržovací pakety. 0 pro vypnutí", |
|||
"hooks": "Hooky", |
|||
"hooksDescription": "Hooky fungují pouze s wg-quick", |
|||
"hooksLeaveEmpty": "Pouze pro wg-quick. Jinak ponechte prázdné", |
|||
"dnsDesc": "DNS server, který budou klienti používat (přebíjí globální nastavení)", |
|||
"notConnected": "Klient není připojen", |
|||
"endpoint": "Koncový bod", |
|||
"endpointDesc": "IP adresa klienta, ze které je navázáno spojení WireGuard", |
|||
"search": "Hledat klienty...", |
|||
"config": "Konfigurace", |
|||
"viewConfig": "Zobrazit konfiguraci" |
|||
}, |
|||
"dialog": { |
|||
"change": "Změnit", |
|||
"cancel": "Zrušit", |
|||
"create": "Vytvořit" |
|||
}, |
|||
"toast": { |
|||
"success": "Úspěch", |
|||
"saved": "Uloženo", |
|||
"error": "Chyba" |
|||
}, |
|||
"form": { |
|||
"actions": "Akce", |
|||
"save": "Uložit", |
|||
"revert": "Vrátit změny", |
|||
"sectionGeneral": "Obecné", |
|||
"sectionAdvanced": "Pokročilé", |
|||
"noItems": "Žádné položky", |
|||
"nullNoItems": "Žádné položky. Používá se globální konfigurace", |
|||
"add": "Přidat" |
|||
}, |
|||
"admin": { |
|||
"general": { |
|||
"sessionTimeout": "Vypršení relace", |
|||
"sessionTimeoutDesc": "Doba trvání relace pro 'Zapamatovat si mě' (sekundy)", |
|||
"metrics": "Metriky", |
|||
"metricsPassword": "Heslo", |
|||
"metricsPasswordDesc": "Bearer heslo pro koncový bod metrik (heslo nebo argon2 hash)", |
|||
"json": "JSON", |
|||
"jsonDesc": "Cesta pro metriky ve formátu JSON", |
|||
"prometheus": "Prometheus", |
|||
"prometheusDesc": "Cesta pro metriky Prometheus" |
|||
}, |
|||
"config": { |
|||
"connection": "Připojení", |
|||
"hostDesc": "Veřejný název hostitele, ke kterému se klienti připojují (zneplatní stávající konfigurace)", |
|||
"portDesc": "Veřejný UDP port, ke kterému se klienti připojují (zneplatní stávající konfigurace, pravděpodobně budete chtít změnit i Port rozhraní)", |
|||
"allowedIpsDesc": "Povolené IP adresy, které budou klienti používat (globální nastavení)", |
|||
"dnsDesc": "DNS server, který budou klienti používat (globální nastavení)", |
|||
"mtuDesc": "MTU, které budou klienti používat (pouze pro nové klienty)", |
|||
"persistentKeepaliveDesc": "Interval v sekundách pro odesílání udržovacích paketů na server. 0 = vypnuto (pouze pro nové klienty)", |
|||
"suggest": "Navrhnout", |
|||
"suggestDesc": "Vyberte IP adresu nebo název hostitele pro pole Hostitel" |
|||
}, |
|||
"interface": { |
|||
"cidrSuccess": "CIDR změněn", |
|||
"device": "Zařízení", |
|||
"deviceDesc": "Ethernetové zařízení, přes které má být provoz WireGuard přeposílán", |
|||
"mtuDesc": "MTU, které bude WireGuard používat", |
|||
"portDesc": "UDP port, na kterém bude WireGuard naslouchat (pravděpodobně budete chtít změnit i Konfigurační port)", |
|||
"changeCidr": "Změnit CIDR", |
|||
"restart": "Restartovat rozhraní", |
|||
"restartDesc": "Restartovat rozhraní WireGuard", |
|||
"restartWarn": "Opravdu chcete restartovat rozhraní? Dojde k odpojení všech klientů.", |
|||
"restartSuccess": "Rozhraní bylo restartováno" |
|||
}, |
|||
"introText": "Vítejte v administraci.\n\nZde můžete spravovat obecná nastavení, konfiguraci, nastavení rozhraní a hooky.\n\nZačněte výběrem jedné ze sekcí v bočním panelu." |
|||
}, |
|||
"zod": { |
|||
"generic": { |
|||
"required": "Pole {0} je povinné", |
|||
"validNumber": "{0} musí být platné číslo", |
|||
"validNumberRange": "{0} musí být platné číslo nebo rozsah čísel", |
|||
"validString": "{0} musí být platný řetězec", |
|||
"validBoolean": "{0} musí být platná logická hodnota", |
|||
"validArray": "{0} musí být platné pole", |
|||
"stringMin": "{0} musí mít alespoň {1} znak(ů)", |
|||
"numberMin": "{0} musí být alespoň {1}" |
|||
}, |
|||
"client": { |
|||
"id": "ID klienta", |
|||
"name": "Jméno", |
|||
"expiresAt": "Vyprší dne", |
|||
"address4": "IPv4 adresa", |
|||
"address6": "IPv6 adresa", |
|||
"serverAllowedIps": "Povolené IP adresy serveru" |
|||
}, |
|||
"user": { |
|||
"username": "Uživatelské jméno", |
|||
"password": "Heslo", |
|||
"remember": "Pamatovat si", |
|||
"name": "Jméno", |
|||
"email": "E-mail", |
|||
"emailInvalid": "E-mail musí být platná e-mailová adresa", |
|||
"passwordMatch": "Hesla se musí shodovat", |
|||
"totpEnable": "Zapnout TOTP", |
|||
"totpEnableTrue": "Zapnutí TOTP musí být potvrzeno", |
|||
"totpCode": "TOTP kód" |
|||
}, |
|||
"userConfig": { |
|||
"host": "Hostitel" |
|||
}, |
|||
"general": { |
|||
"sessionTimeout": "Vypršení relace", |
|||
"metricsEnabled": "Metriky", |
|||
"metricsPassword": "Heslo k metrikám" |
|||
}, |
|||
"interface": { |
|||
"cidr": "CIDR", |
|||
"device": "Zařízení", |
|||
"cidrValid": "CIDR musí být platný" |
|||
}, |
|||
"otl": "Jednorázový odkaz", |
|||
"stringMalformed": "Řetězec má nesprávný formát", |
|||
"body": "Tělo požadavku musí být platný objekt", |
|||
"hook": "Hook", |
|||
"enabled": "Aktivní", |
|||
"mtu": "MTU", |
|||
"port": "Port", |
|||
"persistentKeepalive": "Persistent Keepalive", |
|||
"address": "IP adresa", |
|||
"dns": "DNS", |
|||
"allowedIps": "Povolené IP adresy", |
|||
"file": "Soubor" |
|||
}, |
|||
"hooks": { |
|||
"preUp": "PreUp", |
|||
"postUp": "PostUp", |
|||
"preDown": "PreDown", |
|||
"postDown": "PostDown" |
|||
}, |
|||
"copy": { |
|||
"notSupported": "Kopírování není podporováno", |
|||
"copied": "Zkopírováno!", |
|||
"failed": "Kopírování selhalo", |
|||
"copy": "Kopírovat" |
|||
}, |
|||
"awg": { |
|||
"jCLabel": "Počet junk paketů (Jc)", |
|||
"jCDescription": "Počet odesílaných junk paketů (1-128, doporučeno: 4-12)", |
|||
"jMinLabel": "Min. velikost junk paketu (Jmin)", |
|||
"jMinDescription": "Minimální velikost junk paketů (0-1279*, doporučeno: 8, musí být < Jmax)", |
|||
"jMaxLabel": "Max. velikost junk paketu (Jmax)", |
|||
"jMaxDescription": "Maximální velikost junk paketů (1-1280*, doporučeno: 80, musí být > Jmin)", |
|||
"s1Label": "Velikost junk dat u Init paketu (S1)", |
|||
"s1Description": "Velikost junk dat u Init paketu (0-1132, doporučeno: 15-150, S1+56 ≠ S2)", |
|||
"s2Label": "Velikost junk dat u Response paketu (S2)", |
|||
"s2Description": "Velikost junk dat u Response paketu (0-1188, doporučeno: 15-150)", |
|||
"s3Label": "Velikost junk dat u Cookie reply paketu (S3)", |
|||
"s3Description": "Velikost junk dat u paketu s odpovědí na cookie", |
|||
"s4Label": "Velikost junk dat u Transport paketu (S4)", |
|||
"s4Description": "Velikost junk dat u transportního paketu", |
|||
"h1Label": "Init magic header (H1)", |
|||
"h1Description": "Hodnota nebo rozsah hlavičky Init paketu (X nebo X-Y, kde X<Y. Min 5, max 2147483647. Nesmí se překrývat s ostatními hlavičkami)", |
|||
"h2Label": "Response magic header (H2)", |
|||
"h2Description": "Hodnota nebo rozsah hlavičky Response paketu (X nebo X-Y, kde X<Y. Min 5, max 2147483647. Nesmí se překrývat s ostatními hlavičkami)", |
|||
"h3Label": "Cookie reply magic header (H3)", |
|||
"h3Description": "Hodnota nebo rozsah hlavičky Cookie reply paketu (X nebo X-Y, kde X<Y. Min 5, max 2147483647. Nesmí se překrývat s ostatními hlavičkami)", |
|||
"h4Label": "Transport magic header (H4)", |
|||
"h4Description": "Hodnota nebo rozsah hlavičky Transport paketu (X nebo X-Y, kde X<Y. Min 5, max 2147483647. Nesmí se překrývat s ostatními hlavičkami)", |
|||
"i1Label": "Speciální junk paket 1 (I1)", |
|||
"i1Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
|||
"i2Label": "Speciální junk paket 2 (I2)", |
|||
"i2Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
|||
"i3Label": "Speciální junk paket 3 (I3)", |
|||
"i3Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
|||
"i4Label": "Speciální junk paket 4 (I4)", |
|||
"i4Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
|||
"i5Label": "Speciální junk paket 5 (I5)", |
|||
"i5Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
|||
"mtuNote": "Hodnoty závisí na nastavení MTU", |
|||
"obfuscationParameters": "AmneziaWG parametry obfuskace" |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
{ |
|||
"pages": { |
|||
"me": "Conta", |
|||
"clients": "Clientes", |
|||
"admin": { |
|||
"panel": "Panel de administración", |
|||
"general": "Xeral", |
|||
"config": "Configuración", |
|||
"interface": "Interface", |
|||
"hooks": "Hooks" |
|||
} |
|||
}, |
|||
"user": { |
|||
"email": "Correo electrónico" |
|||
}, |
|||
"me": { |
|||
"currentPassword": "Contrasinal actual", |
|||
"enable2fa": "Activar a autenticación de dobre factor", |
|||
"enable2faDesc": "Escanea o código QR coa túa aplicación de autenticación ou introduce a chave manualmente.", |
|||
"2faKey": "Chave TOTP", |
|||
"2faCodeDesc": "Introduce o código da túa aplicación de autenticación.", |
|||
"disable2fa": "Desactivar a autenticación de dobre factor", |
|||
"disable2faDesc": "Introduce o teu contrasinal para desactivar a autenticación de dobre factor." |
|||
}, |
|||
"general": { |
|||
"name": "Nome", |
|||
"username": "Nome de usuario", |
|||
"password": "Contrasinal", |
|||
"newPassword": "Novo contrasinal", |
|||
"updatePassword": "Actualizar contrasinal", |
|||
"mtu": "MTU", |
|||
"allowedIps": "IP permitidas", |
|||
"dns": "DNS", |
|||
"persistentKeepalive": "Keepalive persistente", |
|||
"logout": "Pechar sesión", |
|||
"continue": "Continuar", |
|||
"host": "Host", |
|||
"port": "Porto", |
|||
"yes": "Si", |
|||
"no": "Non", |
|||
"confirmPassword": "Confirmar o contrasinal", |
|||
"loading": "Cargando...", |
|||
"2fa": "Autenticación de dobre factor", |
|||
"2faCode": "Código TOTP" |
|||
}, |
|||
"setup": { |
|||
"welcome": "Benvido á túa primeira configuración de wg-easy", |
|||
"welcomeDesc": "Atopaches a forma máis doada de instalar e xestionar WireGuard en calquera sistema Linux", |
|||
"existingSetup": "Tes unha configuración existente?", |
|||
"createAdminDesc": "Por favor, introduce primeiro un usuario administrador cun contrasinal seguro. Esta información empregarase para acceder ao panel de administración.", |
|||
"setupConfigDesc": "Por favor, introduce a información do host e do porto. Isto empregarase para a configuración dos clientes ao configurar WireGuard nos seus dispositivos.", |
|||
"setupMigrationDesc": "Por favor, fornece o ficheiro da copia de seguridade se queres migrar os datos da túa versión anterior de wg-easy á nova configuración.", |
|||
"upload": "Subir", |
|||
"migration": "Recuperar a copia de seguridade:", |
|||
"createAccount": "Crear conta", |
|||
"successful": "Configuración completada con éxito", |
|||
"hostDesc": "Nome de host público ao que se conectarán os clientes", |
|||
"portDesc": "Porto UDP público ao que se conectarán os clientes e no que escoitará WireGuard" |
|||
}, |
|||
"update": { |
|||
"updateAvailable": "Hai unha actualización dispoñible!", |
|||
"update": "Actualizar" |
|||
}, |
|||
"theme": { |
|||
"dark": "Tema escuro", |
|||
"light": "Tema claro", |
|||
"system": "Tema do sistema" |
|||
}, |
|||
"layout": { |
|||
"toggleCharts": "Amosar/Ocultar gráficas", |
|||
"donate": "Doar" |
|||
}, |
|||
"login": { |
|||
"signIn": "Iniciar sesión", |
|||
"rememberMe": "Lembrarme", |
|||
"rememberMeDesc": "Manter a sesión iniciada ao pechar o navegador", |
|||
"insecure": "Non podes iniciar sesión cunha conexión insegura. Usa HTTPS.", |
|||
"2faRequired": "É necesaria a autenticación de dobre factor", |
|||
"2faWrong": "A autenticación de dobre factor é incorrecta" |
|||
}, |
|||
"client": { |
|||
"empty": "Aínda non hai clientes.", |
|||
"newShort": "Novo", |
|||
"sort": "Ordenar", |
|||
"create": "Crear cliente", |
|||
"created": "Cliente creado", |
|||
"new": "Novo cliente", |
|||
"name": "Nome", |
|||
"expireDate": "Data de caducidade", |
|||
"expireDateDesc": "Data na que o cliente será desactivado. Déixao en branco para permanente", |
|||
"delete": "Eliminar", |
|||
"deleteClient": "Eliminar cliente", |
|||
"deleteDialog1": "Seguro que queres eliminar", |
|||
"deleteDialog2": "Esta acción non se pode desfacer.", |
|||
"enabled": "Activado", |
|||
"address": "Enderezo", |
|||
"serverAllowedIps": "IP permitidas polo servidor", |
|||
"otlDesc": "Xerar ligazón curta dun só uso", |
|||
"permanent": "Permanente", |
|||
"createdOn": "Creado o ", |
|||
"lastSeen": "Visto por última vez o ", |
|||
"totalDownload": "Descarga total: ", |
|||
"totalUpload": "Subida total: ", |
|||
"newClient": "Novo cliente", |
|||
"disableClient": "Desactivar cliente", |
|||
"enableClient": "Activar cliente", |
|||
"noPrivKey": "Este cliente non ten unha chave privada coñecida. Non se pode crear a configuración.", |
|||
"showQR": "Amosar código QR", |
|||
"downloadConfig": "Descargar configuración", |
|||
"allowedIpsDesc": "IP que se encamiñarán a través da VPN (sobrescribe a configuración global)", |
|||
"serverAllowedIpsDesc": "IP que o servidor encamiñará ao cliente", |
|||
"mtuDesc": "Define a unidade máxima de transmisión (tamaño do paquete) para o túnel VPN", |
|||
"persistentKeepaliveDesc": "Define o intervalo (en segundos) para os paquetes keep-alive. 0 desactívaos", |
|||
"hooks": "Hooks", |
|||
"hooksDescription": "Os hooks só funcionan con wg-quick", |
|||
"hooksLeaveEmpty": "Só para wg-quick. Noutro caso, déixao baleiro", |
|||
"dnsDesc": "Servidor DNS que empregarán os clientes (sobrescribe a configuración global)", |
|||
"notConnected": "Cliente non conectado", |
|||
"endpoint": "Punto final", |
|||
"endpointDesc": "IP do cliente desde a que se establece a conexión WireGuard", |
|||
"search": "Buscar clientes...", |
|||
"config": "Configuración", |
|||
"viewConfig": "Ver configuración" |
|||
}, |
|||
"dialog": { |
|||
"change": "Cambiar", |
|||
"cancel": "Cancelar", |
|||
"create": "Crear" |
|||
}, |
|||
"toast": { |
|||
"success": "Éxito", |
|||
"saved": "Gardado", |
|||
"error": "Erro" |
|||
}, |
|||
"form": { |
|||
"actions": "Accións", |
|||
"save": "Gardar", |
|||
"revert": "Reverter", |
|||
"sectionGeneral": "Xeral", |
|||
"sectionAdvanced": "Avanzado", |
|||
"noItems": "Sen elementos", |
|||
"nullNoItems": "Sen elementos. Usando a configuración global", |
|||
"add": "Engadir" |
|||
} |
|||
} |
|||
@ -0,0 +1,286 @@ |
|||
{ |
|||
"pages": { |
|||
"me": "Konto", |
|||
"clients": "Klienter", |
|||
"admin": { |
|||
"panel": "Adminpanel", |
|||
"general": "Generelt", |
|||
"config": "Oppsett", |
|||
"interface": "Grensesnitt", |
|||
"hooks": "Hooks" |
|||
} |
|||
}, |
|||
"user": { |
|||
"email": "E-post" |
|||
}, |
|||
"me": { |
|||
"currentPassword": "Nåværende passord", |
|||
"enable2fa": "Aktiver tofaktorautentisering", |
|||
"enable2faDesc": "Skann QR-koden med autentiseringsappen din eller skriv inn nøkkelen manuelt.", |
|||
"2faKey": "TOTP-nøkkel", |
|||
"2faCodeDesc": "Skriv inn koden fra autentiseringsappen din.", |
|||
"disable2fa": "Deaktiver tofaktorautentisering", |
|||
"disable2faDesc": "Skriv inn passordet ditt for å deaktivere tofaktorautentisering." |
|||
}, |
|||
"general": { |
|||
"name": "Navn", |
|||
"username": "Brukernavn", |
|||
"password": "Passord", |
|||
"newPassword": "Nytt passord", |
|||
"updatePassword": "Oppdater passord", |
|||
"mtu": "MTU", |
|||
"allowedIps": "Tillatte IP-er", |
|||
"dns": "DNS", |
|||
"persistentKeepalive": "Vedvarende keepalive", |
|||
"logout": "Logg ut", |
|||
"continue": "Fortsett", |
|||
"host": "Vert", |
|||
"port": "Port", |
|||
"yes": "Ja", |
|||
"no": "Nei", |
|||
"confirmPassword": "Bekreft passord", |
|||
"loading": "Laster...", |
|||
"2fa": "Tofaktorautentisering", |
|||
"2faCode": "TOTP-kode" |
|||
}, |
|||
"setup": { |
|||
"welcome": "Velkommen til ditt oppsett av wg-easy", |
|||
"welcomeDesc": "Du har funnet den enkleste måten å installere og administrere WireGuard på en hvilken som helst Linux-vert", |
|||
"existingSetup": "Har du et eksisterende oppsett?", |
|||
"createAdminDesc": "Skriv først inn et adminbrukernavn og et sterkt, sikkert passord. Denne informasjonen brukes til å logge inn i administrasjonspanelet.", |
|||
"setupConfigDesc": "Skriv inn vert- og portinformasjon. Dette brukes til klientkonfigurasjonen når du setter opp WireGuard på enhetene deres.", |
|||
"setupMigrationDesc": "Oppgi sikkerhetskopifilen hvis du vil migrere dataene dine fra forrige wg-easy-versjon til det nye oppsettet.", |
|||
"upload": "Last opp", |
|||
"migration": "Gjenopprett sikkerhetskopien:", |
|||
"createAccount": "Opprett konto", |
|||
"successful": "Oppsett vellykket", |
|||
"hostDesc": "Offentlig vertsnavn klienter vil koble seg til", |
|||
"portDesc": "Offentlig UDP-port klienter vil koble til og WireGuard vil lytte på" |
|||
}, |
|||
"update": { |
|||
"updateAvailable": "En oppdatering er tilgjengelig!", |
|||
"update": "Oppdater" |
|||
}, |
|||
"theme": { |
|||
"dark": "Mørkt tema", |
|||
"light": "Lyst tema", |
|||
"system": "Systemtema" |
|||
}, |
|||
"layout": { |
|||
"toggleCharts": "Vis/skjul diagrammer", |
|||
"donate": "Doner" |
|||
}, |
|||
"login": { |
|||
"signIn": "Logg inn", |
|||
"rememberMe": "Husk meg", |
|||
"rememberMeDesc": "Hold deg innlogget etter at nettleseren lukkes", |
|||
"insecure": "Du kan ikke logge inn med en usikker tilkobling. Bruk HTTPS.", |
|||
"2faRequired": "Tofaktorautentisering er påkrevd", |
|||
"2faWrong": "Tofaktorautentisering er feil" |
|||
}, |
|||
"client": { |
|||
"empty": "Det finnes ingen klienter ennå.", |
|||
"newShort": "Ny", |
|||
"sort": "Sorter", |
|||
"create": "Opprett klient", |
|||
"created": "Klient opprettet", |
|||
"new": "Ny klient", |
|||
"name": "Navn", |
|||
"expireDate": "Utløpsdato", |
|||
"expireDateDesc": "Datoen klienten blir deaktivert. Tomt for permanent", |
|||
"delete": "Slett", |
|||
"deleteClient": "Slett klient", |
|||
"deleteDialog1": "Er du sikker på at du vil slette", |
|||
"deleteDialog2": "Denne handlingen kan ikke angres.", |
|||
"enabled": "Aktivert", |
|||
"address": "Adresse", |
|||
"serverAllowedIps": "Server tillatte IP-er", |
|||
"otlDesc": "Generer kort engangslenke", |
|||
"permanent": "Permanent", |
|||
"createdOn": "Opprettet ", |
|||
"lastSeen": "Sist sett ", |
|||
"totalDownload": "Totalt nedlastet: ", |
|||
"totalUpload": "Totalt opplastet: ", |
|||
"newClient": "Ny klient", |
|||
"disableClient": "Deaktiver klient", |
|||
"enableClient": "Aktiver klient", |
|||
"noPrivKey": "Denne klienten har ingen kjent privat nøkkel. Kan ikke opprette konfigurasjon.", |
|||
"showQR": "Vis QR-kode", |
|||
"downloadConfig": "Last ned konfigurasjon", |
|||
"allowedIpsDesc": "Hvilke IP-er som rutes gjennom VPN (overstyrer global konfig)", |
|||
"serverAllowedIpsDesc": "Hvilke IP-er serveren ruter til klienten", |
|||
"mtuDesc": "Setter maksimal overføringsenhet (pakkestørrelse) for VPN-tunnelen", |
|||
"persistentKeepaliveDesc": "Setter intervallet (i sekunder) for keepalive-pakker. 0 deaktiverer det", |
|||
"hooks": "Hooks", |
|||
"hooksDescription": "Hooks fungerer bare med wg-quick", |
|||
"hooksLeaveEmpty": "Kun for wg-quick. Ellers la det være tomt", |
|||
"dnsDesc": "DNS-server klienter vil bruke (overstyrer global konfig)", |
|||
"notConnected": "Klient ikke tilkoblet", |
|||
"endpoint": "Endepunkt", |
|||
"endpointDesc": "IP-en til klienten som WireGuard-tilkoblingen etableres fra", |
|||
"search": "Søk etter klienter...", |
|||
"config": "Konfigurasjon", |
|||
"viewConfig": "Vis konfigurasjon" |
|||
}, |
|||
"dialog": { |
|||
"change": "Endre", |
|||
"cancel": "Avbryt", |
|||
"create": "Opprett" |
|||
}, |
|||
"toast": { |
|||
"success": "Vellykket", |
|||
"saved": "Lagret", |
|||
"error": "Feil" |
|||
}, |
|||
"form": { |
|||
"actions": "Handlinger", |
|||
"save": "Lagre", |
|||
"revert": "Tilbakestill", |
|||
"sectionGeneral": "Generelt", |
|||
"sectionAdvanced": "Avansert", |
|||
"noItems": "Ingen elementer", |
|||
"nullNoItems": "Ingen elementer. Bruker global konfig", |
|||
"add": "Legg til" |
|||
}, |
|||
"admin": { |
|||
"general": { |
|||
"sessionTimeout": "Øktutløp", |
|||
"sessionTimeoutDesc": "Øktvarighet for Husk meg (sekunder)", |
|||
"metrics": "Målinger", |
|||
"metricsPassword": "Passord", |
|||
"metricsPasswordDesc": "Bearer-passord for metrics-endepunktet (passord eller argon2-hash)", |
|||
"json": "JSON", |
|||
"jsonDesc": "Rute for metrics i JSON-format", |
|||
"prometheus": "Prometheus", |
|||
"prometheusDesc": "Rute for Prometheus-målinger" |
|||
}, |
|||
"config": { |
|||
"connection": "Tilkobling", |
|||
"hostDesc": "Offentlig vertsnavn klienter vil koble til (ugyldiggjør konfig)", |
|||
"portDesc": "Offentlig UDP-port klienter vil koble til (ugyldiggjør konfig, du vil sannsynligvis også endre Grensesnitt-port)", |
|||
"allowedIpsDesc": "Tillatte IP-er klienter vil bruke (global konfig)", |
|||
"dnsDesc": "DNS-server klienter vil bruke (global konfig)", |
|||
"mtuDesc": "MTU klienter vil bruke (kun for nye klienter)", |
|||
"persistentKeepaliveDesc": "Intervall i sekunder for å sende keepalives til serveren. 0 = deaktivert (kun for nye klienter)", |
|||
"suggest": "Foreslå", |
|||
"suggestDesc": "Velg en IP-adresse eller et vertsnavn for Vert-feltet" |
|||
}, |
|||
"interface": { |
|||
"cidrSuccess": "CIDR endret", |
|||
"device": "Enhet", |
|||
"deviceDesc": "Ethernet-enhet som WireGuard-trafikken skal videresendes gjennom", |
|||
"mtuDesc": "MTU WireGuard vil bruke", |
|||
"portDesc": "UDP-port WireGuard vil lytte på (du vil sannsynligvis også endre Konfig-port)", |
|||
"changeCidr": "Endre CIDR", |
|||
"restart": "Start grensesnitt på nytt", |
|||
"restartDesc": "Start WireGuard-grensesnittet på nytt", |
|||
"restartWarn": "Er du sikker på at du vil starte grensesnittet på nytt? Dette vil koble fra alle klienter.", |
|||
"restartSuccess": "Grensesnitt startet på nytt" |
|||
}, |
|||
"introText": "Velkommen til adminpanelet.\n\nHer kan du administrere de generelle innstillingene, konfigurasjonen, grensesnittinnstillingene og hooks.\n\nStart med å velge en av seksjonene i sidepanelet." |
|||
}, |
|||
"zod": { |
|||
"generic": { |
|||
"required": "{0} er påkrevd", |
|||
"validNumber": "{0} må være et gyldig tall", |
|||
"validString": "{0} må være en gyldig streng", |
|||
"validBoolean": "{0} må være en gyldig boolsk verdi", |
|||
"validArray": "{0} må være en gyldig liste", |
|||
"stringMin": "{0} må være minst {1} tegn", |
|||
"numberMin": "{0} må være minst {1}" |
|||
}, |
|||
"client": { |
|||
"id": "Klient-ID", |
|||
"name": "Navn", |
|||
"expiresAt": "Utløper", |
|||
"address4": "IPv4-adresse", |
|||
"address6": "IPv6-adresse", |
|||
"serverAllowedIps": "Server tillatte IP-er" |
|||
}, |
|||
"user": { |
|||
"username": "Brukernavn", |
|||
"password": "Passord", |
|||
"remember": "Husk", |
|||
"name": "Navn", |
|||
"email": "E-post", |
|||
"emailInvalid": "E-post må være en gyldig e-postadresse", |
|||
"passwordMatch": "Passord må være like", |
|||
"totpEnable": "TOTP aktivert", |
|||
"totpEnableTrue": "TOTP aktivert må være sant", |
|||
"totpCode": "TOTP-kode" |
|||
}, |
|||
"userConfig": { |
|||
"host": "Vert" |
|||
}, |
|||
"general": { |
|||
"sessionTimeout": "Øktutløp", |
|||
"metricsEnabled": "Målinger", |
|||
"metricsPassword": "Målingspassord" |
|||
}, |
|||
"interface": { |
|||
"cidr": "CIDR", |
|||
"device": "Enhet", |
|||
"cidrValid": "CIDR må være gyldig" |
|||
}, |
|||
"otl": "Engangslenke", |
|||
"stringMalformed": "Strengen er ugyldig", |
|||
"body": "Innholdet må være et gyldig objekt", |
|||
"hook": "Hook", |
|||
"enabled": "Aktivert", |
|||
"mtu": "MTU", |
|||
"port": "Port", |
|||
"persistentKeepalive": "Vedvarende keepalive", |
|||
"address": "IP-adresse", |
|||
"dns": "DNS", |
|||
"allowedIps": "Tillatte IP-er", |
|||
"file": "Fil" |
|||
}, |
|||
"hooks": { |
|||
"preUp": "PreUp", |
|||
"postUp": "PostUp", |
|||
"preDown": "PreDown", |
|||
"postDown": "PostDown" |
|||
}, |
|||
"copy": { |
|||
"notSupported": "Kopiering støttes ikke", |
|||
"copied": "Kopiert!", |
|||
"failed": "Kopiering mislyktes", |
|||
"copy": "Kopier" |
|||
}, |
|||
"awg": { |
|||
"jCLabel": "Antall junk-pakker (Jc)", |
|||
"jCDescription": "Antall junk-pakker som skal sendes (1-128, anbefalt: 4-12)", |
|||
"jMinLabel": "Min. størrelse på junk-pakker (Jmin)", |
|||
"jMinDescription": "Minimum størrelse på junk-pakker (0-1279*, anbefalt: 8, må være < Jmax)", |
|||
"jMaxLabel": "Maks. størrelse på junk-pakker (Jmax)", |
|||
"jMaxDescription": "Maksimal størrelse på junk-pakker (1-1280*, anbefalt: 80, må være > Jmin)", |
|||
"s1Label": "Init-pakke junk-størrelse (S1)", |
|||
"s1Description": "Init-pakke junk-størrelse (0-1132[1280* - 148 = 1132], anbefalt: 15-150, S1+56 ≠ S2)", |
|||
"s2Label": "Svarpakke junk-størrelse (S2)", |
|||
"s2Description": "Svarpakke junk-størrelse (0-1188[1280* - 92 = 1188], anbefalt: 15-150)", |
|||
"s3Label": "Cookie-svarpakke junk-størrelse (S3)", |
|||
"s3Description": "Cookie-svarpakke junk-størrelse", |
|||
"s4Label": "Transportpakke junk-størrelse (S4)", |
|||
"s4Description": "Transportpakke junk-størrelse", |
|||
"h1Label": "Init magisk header (H1)", |
|||
"h1Description": "Init-pakke header-verdi (5-2147483647, må være unik fra H2-H4)", |
|||
"h2Label": "Svar magisk header (H2)", |
|||
"h2Description": "Svarpakke header-verdi (5-2147483647, må være unik fra H1, H3, H4)", |
|||
"h3Label": "Cookie-svar magisk header (H3)", |
|||
"h3Description": "Cookie-svarpakke header-verdi (5-2147483647, må være unik fra H1, H2, H4)", |
|||
"h4Label": "Transport magisk header (H4)", |
|||
"h4Description": "Transportpakke header-verdi (5-2147483647, må være unik fra H1-H3)", |
|||
"i1Label": "Spesiell junk-pakke 1 (I1)", |
|||
"i1Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
|||
"i2Label": "Spesiell junk-pakke 2 (I2)", |
|||
"i2Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
|||
"i3Label": "Spesiell junk-pakke 3 (I3)", |
|||
"i3Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
|||
"i4Label": "Spesiell junk-pakke 4 (I4)", |
|||
"i4Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
|||
"i5Label": "Spesiell junk-pakke 5 (I5)", |
|||
"i5Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
|||
"mtuNote": "Verdier avhenger av MTU", |
|||
"obfuscationParameters": "AmneziaWG obfuskasjonsparametere" |
|||
} |
|||
} |
|||
@ -0,0 +1,297 @@ |
|||
{ |
|||
"pages": { |
|||
"me": "Account", |
|||
"clients": "Cliënten", |
|||
"admin": { |
|||
"panel": "Admin-paneel", |
|||
"general": "Algemeen", |
|||
"config": "Configuratie", |
|||
"interface": "Interface", |
|||
"hooks": "Hooks" |
|||
} |
|||
}, |
|||
"user": { |
|||
"email": "E-mail" |
|||
}, |
|||
"me": { |
|||
"currentPassword": "Huidig wachtwoord", |
|||
"enable2fa": "Twee-factor-authenticatie inschakelen", |
|||
"enable2faDesc": "Scan de QR-code met uw authenticator-app of voer de sleutel handmatig in.", |
|||
"2faKey": "TOTP-sleutel", |
|||
"2faCodeDesc": "Voer de code in van uw authenticator-app.", |
|||
"disable2fa": "Twee-factor-authenticatie uitschakelen", |
|||
"disable2faDesc": "Voer uw wachtwoord in om de twee-factor-authenticatie uit te schakelen." |
|||
}, |
|||
"general": { |
|||
"name": "Naam", |
|||
"username": "Gebruikersnaam", |
|||
"password": "Wachtwoord", |
|||
"newPassword": "Nieuw wachtwoord", |
|||
"updatePassword": "Wachtwoord bijwerken", |
|||
"mtu": "MTU", |
|||
"allowedIps": "Toegestane IP's", |
|||
"dns": "DNS", |
|||
"persistentKeepalive": "Aanhoudende verbinding", |
|||
"logout": "Uitloggen", |
|||
"continue": "Doorgaan", |
|||
"host": "Host", |
|||
"port": "Poort", |
|||
"yes": "Ja", |
|||
"no": "Nee", |
|||
"confirmPassword": "Wachtwoord bevestigen", |
|||
"loading": "Laden...", |
|||
"2fa": "Twee-factor-authenticatie uitschakelen", |
|||
"2faCode": "TOTP-code" |
|||
}, |
|||
"setup": { |
|||
"welcome": "Welkom bij uw eerste installatie van wg-easy", |
|||
"welcomeDesc": "U hebt de gemakkelijkste manier gevonden om WireGuard op elke Linux-host te installeren en te beheren", |
|||
"existingSetup": "Heeft u een bestaande installatie?", |
|||
"createAdminDesc": "Voer eerst een beheerdersgebruikersnaam en een sterk veilig wachtwoord in. Deze gegevens worden gebruikt om in te loggen op uw beheerderspaneel.", |
|||
"setupConfigDesc": "Voer alstublieft de host- en poortinformatie in. Dit wordt gebruikt voor de clientconfiguratie bij het instellen van WireGuard op hun apparaten.", |
|||
"setupMigrationDesc": "Geef alstublieft het back-upbestand als u uw gegevens van uw vorige wg-easy-versie naar uw nieuwe installatie wilt overzetten.", |
|||
"upload": "Uploaden", |
|||
"migration": "Herstel de back-up:", |
|||
"createAccount": "Account aanmaken", |
|||
"successful": "Installatie succesvol", |
|||
"hostDesc": "Publieke hostnaam waar clients verbinding mee maken", |
|||
"portDesc": "Publieke UDP-poort waarop clients verbinding maken en waarop WireGuard luistert" |
|||
}, |
|||
"update": { |
|||
"updateAvailable": "Er is een update beschikbaar!", |
|||
"update": "Bijwerken" |
|||
}, |
|||
"theme": { |
|||
"dark": "Donker thema", |
|||
"light": "Licht thema", |
|||
"system": "Systeem-thema" |
|||
}, |
|||
"layout": { |
|||
"toggleCharts": "Grafieken tonen/verbergen", |
|||
"donate": "Donatie" |
|||
}, |
|||
"login": { |
|||
"signIn": "Inloggen", |
|||
"rememberMe": "Onthoud mij", |
|||
"rememberMeDesc": "Ingelogd blijven na het sluiten van de browser", |
|||
"insecure": "U kunt niet inloggen via een onveilige verbinding. Gebruik HTTPS.", |
|||
"2faRequired": "Twee-factor-authenticatie is vereist", |
|||
"2faWrong": "Twee-factor-authenticatiecode is fout" |
|||
}, |
|||
"client": { |
|||
"empty": "Er zijn nog geen cliënten.", |
|||
"newShort": "Nieuw", |
|||
"sort": "Sortering", |
|||
"create": "Cliënt aanmaken", |
|||
"created": "Cliënt aangemaakt", |
|||
"new": "Nieuwe cliënt", |
|||
"name": "Naam", |
|||
"expireDate": "Verloopdatum", |
|||
"expireDateDesc": "Datum waarop de cliënt wordt uitgeschakeld. Laat leeg voor permanent.", |
|||
"delete": "Verwijderen", |
|||
"deleteClient": "Cliënt verwijderen", |
|||
"deleteDialog1": "Weet u zeker dat u wilt verwijderen", |
|||
"deleteDialog2": "Deze actie kan niet ongedaan worden gemaakt.", |
|||
"enabled": "Ingeschakeld", |
|||
"address": "Adres", |
|||
"serverAllowedIps": "Toegestane IP's van de server", |
|||
"otlDesc": "Korte eenmalige link genereren", |
|||
"permanent": "Permanent", |
|||
"createdOn": "Aangemaakt op ", |
|||
"lastSeen": "Laatst gezien op ", |
|||
"totalDownload": "Totaal gedownload: ", |
|||
"totalUpload": "Totaal geüpload: ", |
|||
"newClient": "Nieuwe cliënt", |
|||
"disableClient": "Cliënt uitschakelen", |
|||
"enableClient": "Cliënt inschakelen", |
|||
"noPrivKey": "Deze cliënt heeft geen bekende privésleutel. Kan de configuratie niet aanmaken.", |
|||
"showQR": "QR-code weergeven", |
|||
"downloadConfig": "Configuratie downloaden", |
|||
"allowedIpsDesc": "Welke IP's via de VPN worden geleid (overschrijft algemene instellingen)", |
|||
"serverAllowedIpsDesc": "Naar welke IP's de server het cliëntverkeer zal routeren", |
|||
"mtuDesc": "Stelt de maximale transmissie-eenheid (pakketgrootte) voor de VPN-tunnel in", |
|||
"persistentKeepaliveDesc": "Stelt het interval (seconden) in voor keep-alive-pakketten. 0 schakelt dit uit", |
|||
"hooks": "Hooks", |
|||
"hooksDescription": "Hooks functioneren alleen met wg-quick", |
|||
"hooksLeaveEmpty": "Alleen voor wg-quick. Anders leeg laten", |
|||
"dnsDesc": "DNS-serverclients zullen gebruiken (overschrijft algemene instellingen)", |
|||
"notConnected": "Cliënt niet verbonden", |
|||
"endpoint": "Eindpunt", |
|||
"endpointDesc": "IP van de cliënt vanaf welke de WireGuard-verbinding tot stand wordt gebracht", |
|||
"search": "Cliënten zoeken...", |
|||
"config": "Configuratie", |
|||
"viewConfig": "Configuratie weergeven", |
|||
"firewallIps": "Firewall-toegestane IPs", |
|||
"firewallIpsDesc": "Bestemmings-IP's/CIDR's waartoe deze client toegang heeft (handhaving aan de serverzijde). Laat leeg om Toegestane IP's te gebruiken. Ondersteunt optionele poort - en protocolfiltering. Zie documentatie voor syntaxis.", |
|||
"downloadPng": "PNG downloaden", |
|||
"copyPng": "PNG kopiëren" |
|||
}, |
|||
"dialog": { |
|||
"change": "Wijzigen", |
|||
"cancel": "Annuleren", |
|||
"create": "Aanmaken" |
|||
}, |
|||
"toast": { |
|||
"success": "Succes", |
|||
"saved": "Opgeslagen", |
|||
"error": "Fout", |
|||
"unknown": "Onbekende fout. Zie console voor meer details" |
|||
}, |
|||
"form": { |
|||
"actions": "Acties", |
|||
"save": "Opslaan", |
|||
"revert": "Terugzetten", |
|||
"sectionGeneral": "Algemeen", |
|||
"sectionAdvanced": "Geavanceerd", |
|||
"noItems": "Geen items", |
|||
"nullNoItems": "Geen items. Globale configuratie gebruiken", |
|||
"add": "Toevoegen" |
|||
}, |
|||
"admin": { |
|||
"general": { |
|||
"sessionTimeout": "Sessie verlopen", |
|||
"sessionTimeoutDesc": "Sessieduur voor Onthoud mij (sec.)", |
|||
"metrics": "Metrics", |
|||
"metricsPassword": "Wachtwoord", |
|||
"metricsPasswordDesc": "Bearer-wachtwoord voor het metrics-eindpunt (wachtwoord of argon2-hash)", |
|||
"json": "JSON", |
|||
"jsonDesc": "Route voor metrics in JSON-formaat", |
|||
"prometheus": "Prometheus", |
|||
"prometheusDesc": "Route voor Prometheus-metrics" |
|||
}, |
|||
"config": { |
|||
"connection": "Verbinding", |
|||
"hostDesc": "Publieke hostnaam waarmee cliënten verbinding maken (maakt configuratie ongedaan)", |
|||
"portDesc": "Publieke UDP-poort waarmee clients verbinding maken (maakt configuratie ongedaan; u dient waarschijnlijk ook de interfacepoort te wijzigen)", |
|||
"allowedIpsDesc": "Toegestane IP's die cliënten zullen gebruiken (algemene configuratie)", |
|||
"dnsDesc": "DNS-server die cliënten zullen gebruiken (algemene configuratie)", |
|||
"mtuDesc": "MTU die cliënten zullen gebruiken (alleen voor nieuwe cliënten)", |
|||
"persistentKeepaliveDesc": "Interval in seconden om keepalives naar de server te sturen. 0 = uitgeschakeld (alleen voor nieuwe cliënten)", |
|||
"suggest": "Voorstellen", |
|||
"suggestDesc": "Kies een IP-adres of hostnaam voor het veld Host" |
|||
}, |
|||
"interface": { |
|||
"cidrSuccess": "CIDR gewijzigd", |
|||
"device": "Apparaat", |
|||
"deviceDesc": "Ethernet-apparaat waar het WireGuard-verkeer doorheen moet worden doorgestuurd", |
|||
"mtuDesc": "MTU die WireGuard zal toepassen", |
|||
"portDesc": "UDP-poort waarop WireGuard zal luisteren (u dient waarschijnlijk ook de Config-poort te wijzigen)", |
|||
"changeCidr": "CIDR wijzigen", |
|||
"restart": "Interface opnieuw starten", |
|||
"restartDesc": "WireGuard-interface opnieuw starten", |
|||
"restartWarn": "Weet u zeker dat u de interface wilt herstarten? Dit zal alle cliënten loskoppelen.", |
|||
"restartSuccess": "Interface opnieuw gestart", |
|||
"firewall": "Traffic-filtering", |
|||
"firewallEnabled": "Per-Client Firewall inschakelen", |
|||
"firewallEnabledDesc": "Beperk het cliëntverkeer tot specifieke bestemmings-IP's met behulp van iptables. Indien ingeschakeld, kan elke cliënt worden geconfigureerd met toegestane bestemmingen." |
|||
}, |
|||
"introText": "Welkom bij het Admin-paneel.\n\nHier kunt u de algemene instellingen, de configuratie, de interface-instellingen en de hooks beheren.\n\nBegin met het kiezen van een van de secties in de zijbalk." |
|||
}, |
|||
"zod": { |
|||
"generic": { |
|||
"required": "{0} is vereist", |
|||
"validNumber": "{0} moet een geldig getal zijn", |
|||
"validNumberRange": "{0} moet een geldig getal of bereik zijn", |
|||
"validString": "{0} moet een geldige tekenreeks zijn", |
|||
"validBoolean": "{0} moet een geldige boolean zijn", |
|||
"validArray": "{0} moet een geldige array zijn", |
|||
"stringMin": "{0} moet minstens {1} teken bevatten", |
|||
"numberMin": "{0} moet minstens {1} zijn" |
|||
}, |
|||
"client": { |
|||
"id": "Cliënt-ID", |
|||
"name": "Naam", |
|||
"expiresAt": "Verloopt op", |
|||
"address4": "IPv4-adres", |
|||
"address6": "IPv6-adres", |
|||
"serverAllowedIps": "Toegestane IP's van de server", |
|||
"firewallIps": "Firewall-toegestane IPs", |
|||
"firewallIpsInvalid": "Ongeldige IP-invoer van firewall. Zie documentatie voor ondersteunde syntaxis." |
|||
}, |
|||
"user": { |
|||
"username": "Gebruikersnaam", |
|||
"password": "Wachtwoord", |
|||
"remember": "Onthouden", |
|||
"name": "Naam", |
|||
"email": "E-mail", |
|||
"emailInvalid": "E-mail moet een geldig e-mailadres zijn", |
|||
"passwordMatch": "Wachtwoorden moeten overeenkomen", |
|||
"totpEnable": "TOTP inschakelen", |
|||
"totpEnableTrue": "TOTP inschakelen moet waar zijn", |
|||
"totpCode": "TOTP-code" |
|||
}, |
|||
"userConfig": { |
|||
"host": "Host" |
|||
}, |
|||
"general": { |
|||
"sessionTimeout": "Sessie-verlooptijd", |
|||
"metricsEnabled": "Metrics", |
|||
"metricsPassword": "Metrics-wachtwoord" |
|||
}, |
|||
"interface": { |
|||
"cidr": "CIDR", |
|||
"device": "Apparaat", |
|||
"cidrValid": "CIDR moet geldig zijn" |
|||
}, |
|||
"otl": "Eenmalige link", |
|||
"stringMalformed": "Tekenreeks is beschadigd", |
|||
"body": "Body moet een geldig object bevatten", |
|||
"hook": "Hook", |
|||
"enabled": "Ingeschakeld", |
|||
"mtu": "MTU", |
|||
"port": "Poort", |
|||
"persistentKeepalive": "Aanhoudende verbinding", |
|||
"address": "IP-adres", |
|||
"dns": "DNS", |
|||
"allowedIps": "Toegestane IP's", |
|||
"file": "Bestand" |
|||
}, |
|||
"hooks": { |
|||
"preUp": "Pre-Up", |
|||
"postUp": "Post-Up", |
|||
"preDown": "Pre-Down", |
|||
"postDown": "Post-Down" |
|||
}, |
|||
"copy": { |
|||
"notSupported": "Kopiëren wordt niet ondersteund", |
|||
"copied": "Gekopieerd!", |
|||
"failed": "Kopiëren is mislukt", |
|||
"copy": "Kopiëren" |
|||
}, |
|||
"awg": { |
|||
"jCLabel": "Junk packet count (Jc)", |
|||
"jCDescription": "Aantal te verzenden junk packets (1-128, aanbevolen: 4-12)", |
|||
"jMinLabel": "Junk packet min size (Jmin)", |
|||
"jMinDescription": "Minimale grootte van junk packets (0-1279*, aanbevolen: 8, moet zijn < Jmax)", |
|||
"jMaxLabel": "Junk packet max size (Jmax)", |
|||
"jMaxDescription": "Maximale grootte van junk packets (1-1280*, aanbevolen: 80, moet zijn > Jmin)", |
|||
"s1Label": "Init packet junk size (S1)", |
|||
"s1Description": "Grootte Init packet junk (0-1132[1280* - 148 = 1132], aanbevolen: 15-150, S1+56 ≠ S2)", |
|||
"s2Label": "Response packet junk size (S2)", |
|||
"s2Description": "Grootte Response packet junk (0-1188[1280* - 92 = 1188], aanbevolen: 15-150)", |
|||
"s3Label": "Cookie reply packet junk size (S3)", |
|||
"s3Description": "Grootte Cookie reply packet junk", |
|||
"s4Label": "Transport packet junk size (S4)", |
|||
"s4Description": "Grootte Transport packet junk", |
|||
"h1Label": "Init magic header (H1)", |
|||
"h1Description": "Waarde of bereik Init packet header (X of X-Y, waarbij X<Y. Min. 5, max. 2147483647. Waarde of bereik mag niet overlappen met andere headers)", |
|||
"h2Label": "Response magic header (H2)", |
|||
"h2Description": "Waarde of bereik Response packet header (X of X-Y, waarbij X<Y. Min. 5, max. 2147483647. Waarde of bereik mag niet overlappen met andere headers)", |
|||
"h3Label": "Cookie reply magic header (H3)", |
|||
"h3Description": "Waarde of bereik Cookie reply packet header (X of X-Y, waarbij X<Y. Min. 5, max. 2147483647. Waarde of bereik mag niet overlappen met andere headers)", |
|||
"h4Label": "Transport magic header (H4)", |
|||
"h4Description": "Waarde of bereik Transport packet header (X of X-Y, waarbij X<Y. Min. 5, max. 2147483647. Waarde of bereik mag niet overlappen met andere headers)", |
|||
"i1Label": "Special junk packet 1 (I1)", |
|||
"i1Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
|||
"i2Label": "Special junk packet 2 (I2)", |
|||
"i2Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
|||
"i3Label": "Special junk packet 3 (I3)", |
|||
"i3Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
|||
"i4Label": "Special junk packet 4 (I4)", |
|||
"i4Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
|||
"i5Label": "Special junk packet 5 (I5)", |
|||
"i5Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
|||
"mtuNote": "Waarden zijn afhankelijk van de MTU", |
|||
"obfuscationParameters": "AmneziaWG Obfuscation Parameters" |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,36 @@ |
|||
PRAGMA foreign_keys=OFF;--> statement-breakpoint |
|||
CREATE TABLE `__new_interfaces_table` ( |
|||
`name` text PRIMARY KEY NOT NULL, |
|||
`device` text NOT NULL, |
|||
`port` integer NOT NULL, |
|||
`private_key` text NOT NULL, |
|||
`public_key` text NOT NULL, |
|||
`ipv4_cidr` text NOT NULL, |
|||
`ipv6_cidr` text NOT NULL, |
|||
`mtu` integer NOT NULL, |
|||
`j_c` integer DEFAULT 7, |
|||
`j_min` integer DEFAULT 10, |
|||
`j_max` integer DEFAULT 1000, |
|||
`s1` integer DEFAULT 128, |
|||
`s2` integer DEFAULT 56, |
|||
`s3` integer, |
|||
`s4` integer, |
|||
`h1` text, |
|||
`h2` text, |
|||
`h3` text, |
|||
`h4` text, |
|||
`i1` text, |
|||
`i2` text, |
|||
`i3` text, |
|||
`i4` text, |
|||
`i5` text, |
|||
`enabled` integer NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
INSERT INTO `__new_interfaces_table`("name", "device", "port", "private_key", "public_key", "ipv4_cidr", "ipv6_cidr", "mtu", "j_c", "j_min", "j_max", "s1", "s2", "s3", "s4", "h1", "h2", "h3", "h4", "i1", "i2", "i3", "i4", "i5", "enabled", "created_at", "updated_at") SELECT "name", "device", "port", "private_key", "public_key", "ipv4_cidr", "ipv6_cidr", "mtu", "j_c", "j_min", "j_max", "s1", "s2", "s3", "s4", "h1", "h2", "h3", "h4", "i1", "i2", "i3", "i4", "i5", "enabled", "created_at", "updated_at" FROM `interfaces_table`;--> statement-breakpoint |
|||
DROP TABLE `interfaces_table`;--> statement-breakpoint |
|||
ALTER TABLE `__new_interfaces_table` RENAME TO `interfaces_table`;--> statement-breakpoint |
|||
PRAGMA foreign_keys=ON;--> statement-breakpoint |
|||
CREATE UNIQUE INDEX `interfaces_table_port_unique` ON `interfaces_table` (`port`); |
|||
@ -0,0 +1,2 @@ |
|||
ALTER TABLE `clients_table` ADD `firewall_ips` text;--> statement-breakpoint |
|||
ALTER TABLE `interfaces_table` ADD `firewall_enabled` integer DEFAULT false NOT NULL; |
|||
@ -0,0 +1,972 @@ |
|||
{ |
|||
"version": "6", |
|||
"dialect": "sqlite", |
|||
"id": "68c43b7a-772d-4c34-8278-e9fce5b53df1", |
|||
"prevId": "e09bc17a-dab6-45a3-a09c-57af222b08fb", |
|||
"tables": { |
|||
"clients_table": { |
|||
"name": "clients_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": true |
|||
}, |
|||
"user_id": { |
|||
"name": "user_id", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"interface_id": { |
|||
"name": "interface_id", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"name": { |
|||
"name": "name", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"ipv4_address": { |
|||
"name": "ipv4_address", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"ipv6_address": { |
|||
"name": "ipv6_address", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"pre_up": { |
|||
"name": "pre_up", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "''" |
|||
}, |
|||
"post_up": { |
|||
"name": "post_up", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "''" |
|||
}, |
|||
"pre_down": { |
|||
"name": "pre_down", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "''" |
|||
}, |
|||
"post_down": { |
|||
"name": "post_down", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "''" |
|||
}, |
|||
"private_key": { |
|||
"name": "private_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"public_key": { |
|||
"name": "public_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"pre_shared_key": { |
|||
"name": "pre_shared_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"expires_at": { |
|||
"name": "expires_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"allowed_ips": { |
|||
"name": "allowed_ips", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"server_allowed_ips": { |
|||
"name": "server_allowed_ips", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"persistent_keepalive": { |
|||
"name": "persistent_keepalive", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"mtu": { |
|||
"name": "mtu", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"j_c": { |
|||
"name": "j_c", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"j_min": { |
|||
"name": "j_min", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"j_max": { |
|||
"name": "j_max", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i1": { |
|||
"name": "i1", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i2": { |
|||
"name": "i2", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i3": { |
|||
"name": "i3", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i4": { |
|||
"name": "i4", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i5": { |
|||
"name": "i5", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"dns": { |
|||
"name": "dns", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"server_endpoint": { |
|||
"name": "server_endpoint", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"enabled": { |
|||
"name": "enabled", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"clients_table_ipv4_address_unique": { |
|||
"name": "clients_table_ipv4_address_unique", |
|||
"columns": [ |
|||
"ipv4_address" |
|||
], |
|||
"isUnique": true |
|||
}, |
|||
"clients_table_ipv6_address_unique": { |
|||
"name": "clients_table_ipv6_address_unique", |
|||
"columns": [ |
|||
"ipv6_address" |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"clients_table_user_id_users_table_id_fk": { |
|||
"name": "clients_table_user_id_users_table_id_fk", |
|||
"tableFrom": "clients_table", |
|||
"tableTo": "users_table", |
|||
"columnsFrom": [ |
|||
"user_id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "restrict", |
|||
"onUpdate": "cascade" |
|||
}, |
|||
"clients_table_interface_id_interfaces_table_name_fk": { |
|||
"name": "clients_table_interface_id_interfaces_table_name_fk", |
|||
"tableFrom": "clients_table", |
|||
"tableTo": "interfaces_table", |
|||
"columnsFrom": [ |
|||
"interface_id" |
|||
], |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"general_table": { |
|||
"name": "general_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": 1 |
|||
}, |
|||
"setup_step": { |
|||
"name": "setup_step", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"session_password": { |
|||
"name": "session_password", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"session_timeout": { |
|||
"name": "session_timeout", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"metrics_prometheus": { |
|||
"name": "metrics_prometheus", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"metrics_json": { |
|||
"name": "metrics_json", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"metrics_password": { |
|||
"name": "metrics_password", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"hooks_table": { |
|||
"name": "hooks_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"pre_up": { |
|||
"name": "pre_up", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"post_up": { |
|||
"name": "post_up", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"pre_down": { |
|||
"name": "pre_down", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"post_down": { |
|||
"name": "post_down", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": { |
|||
"hooks_table_id_interfaces_table_name_fk": { |
|||
"name": "hooks_table_id_interfaces_table_name_fk", |
|||
"tableFrom": "hooks_table", |
|||
"tableTo": "interfaces_table", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"interfaces_table": { |
|||
"name": "interfaces_table", |
|||
"columns": { |
|||
"name": { |
|||
"name": "name", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"device": { |
|||
"name": "device", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"port": { |
|||
"name": "port", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"private_key": { |
|||
"name": "private_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"public_key": { |
|||
"name": "public_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"ipv4_cidr": { |
|||
"name": "ipv4_cidr", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"ipv6_cidr": { |
|||
"name": "ipv6_cidr", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"mtu": { |
|||
"name": "mtu", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"j_c": { |
|||
"name": "j_c", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 7 |
|||
}, |
|||
"j_min": { |
|||
"name": "j_min", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 10 |
|||
}, |
|||
"j_max": { |
|||
"name": "j_max", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 1000 |
|||
}, |
|||
"s1": { |
|||
"name": "s1", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 128 |
|||
}, |
|||
"s2": { |
|||
"name": "s2", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 56 |
|||
}, |
|||
"s3": { |
|||
"name": "s3", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"s4": { |
|||
"name": "s4", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"h1": { |
|||
"name": "h1", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"h2": { |
|||
"name": "h2", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"h3": { |
|||
"name": "h3", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"h4": { |
|||
"name": "h4", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i1": { |
|||
"name": "i1", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i2": { |
|||
"name": "i2", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i3": { |
|||
"name": "i3", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i4": { |
|||
"name": "i4", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i5": { |
|||
"name": "i5", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"enabled": { |
|||
"name": "enabled", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"interfaces_table_port_unique": { |
|||
"name": "interfaces_table_port_unique", |
|||
"columns": [ |
|||
"port" |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"one_time_links_table": { |
|||
"name": "one_time_links_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"one_time_link": { |
|||
"name": "one_time_link", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"expires_at": { |
|||
"name": "expires_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"one_time_links_table_one_time_link_unique": { |
|||
"name": "one_time_links_table_one_time_link_unique", |
|||
"columns": [ |
|||
"one_time_link" |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"one_time_links_table_id_clients_table_id_fk": { |
|||
"name": "one_time_links_table_id_clients_table_id_fk", |
|||
"tableFrom": "one_time_links_table", |
|||
"tableTo": "clients_table", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"users_table": { |
|||
"name": "users_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": true |
|||
}, |
|||
"username": { |
|||
"name": "username", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"password": { |
|||
"name": "password", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"email": { |
|||
"name": "email", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"name": { |
|||
"name": "name", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"role": { |
|||
"name": "role", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"totp_key": { |
|||
"name": "totp_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"totp_verified": { |
|||
"name": "totp_verified", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"enabled": { |
|||
"name": "enabled", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"users_table_username_unique": { |
|||
"name": "users_table_username_unique", |
|||
"columns": [ |
|||
"username" |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"user_configs_table": { |
|||
"name": "user_configs_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_mtu": { |
|||
"name": "default_mtu", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_persistent_keepalive": { |
|||
"name": "default_persistent_keepalive", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_dns": { |
|||
"name": "default_dns", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_allowed_ips": { |
|||
"name": "default_allowed_ips", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_j_c": { |
|||
"name": "default_j_c", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 7 |
|||
}, |
|||
"default_j_min": { |
|||
"name": "default_j_min", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 10 |
|||
}, |
|||
"default_j_max": { |
|||
"name": "default_j_max", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 1000 |
|||
}, |
|||
"default_i1": { |
|||
"name": "default_i1", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"default_i2": { |
|||
"name": "default_i2", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"default_i3": { |
|||
"name": "default_i3", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"default_i4": { |
|||
"name": "default_i4", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"default_i5": { |
|||
"name": "default_i5", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"host": { |
|||
"name": "host", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"port": { |
|||
"name": "port", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": { |
|||
"user_configs_table_id_interfaces_table_name_fk": { |
|||
"name": "user_configs_table_id_interfaces_table_name_fk", |
|||
"tableFrom": "user_configs_table", |
|||
"tableTo": "interfaces_table", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
} |
|||
}, |
|||
"views": {}, |
|||
"enums": {}, |
|||
"_meta": { |
|||
"schemas": {}, |
|||
"tables": {}, |
|||
"columns": {} |
|||
}, |
|||
"internal": { |
|||
"indexes": {} |
|||
} |
|||
} |
|||
@ -0,0 +1,987 @@ |
|||
{ |
|||
"version": "6", |
|||
"dialect": "sqlite", |
|||
"id": "0f072f91-cd10-4702-ae7b-245255d69d1e", |
|||
"prevId": "68c43b7a-772d-4c34-8278-e9fce5b53df1", |
|||
"tables": { |
|||
"clients_table": { |
|||
"name": "clients_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": true |
|||
}, |
|||
"user_id": { |
|||
"name": "user_id", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"interface_id": { |
|||
"name": "interface_id", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"name": { |
|||
"name": "name", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"ipv4_address": { |
|||
"name": "ipv4_address", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"ipv6_address": { |
|||
"name": "ipv6_address", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"pre_up": { |
|||
"name": "pre_up", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "''" |
|||
}, |
|||
"post_up": { |
|||
"name": "post_up", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "''" |
|||
}, |
|||
"pre_down": { |
|||
"name": "pre_down", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "''" |
|||
}, |
|||
"post_down": { |
|||
"name": "post_down", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "''" |
|||
}, |
|||
"private_key": { |
|||
"name": "private_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"public_key": { |
|||
"name": "public_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"pre_shared_key": { |
|||
"name": "pre_shared_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"expires_at": { |
|||
"name": "expires_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"allowed_ips": { |
|||
"name": "allowed_ips", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"server_allowed_ips": { |
|||
"name": "server_allowed_ips", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"firewall_ips": { |
|||
"name": "firewall_ips", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"persistent_keepalive": { |
|||
"name": "persistent_keepalive", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"mtu": { |
|||
"name": "mtu", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"j_c": { |
|||
"name": "j_c", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"j_min": { |
|||
"name": "j_min", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"j_max": { |
|||
"name": "j_max", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i1": { |
|||
"name": "i1", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i2": { |
|||
"name": "i2", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i3": { |
|||
"name": "i3", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i4": { |
|||
"name": "i4", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i5": { |
|||
"name": "i5", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"dns": { |
|||
"name": "dns", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"server_endpoint": { |
|||
"name": "server_endpoint", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"enabled": { |
|||
"name": "enabled", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"clients_table_ipv4_address_unique": { |
|||
"name": "clients_table_ipv4_address_unique", |
|||
"columns": [ |
|||
"ipv4_address" |
|||
], |
|||
"isUnique": true |
|||
}, |
|||
"clients_table_ipv6_address_unique": { |
|||
"name": "clients_table_ipv6_address_unique", |
|||
"columns": [ |
|||
"ipv6_address" |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"clients_table_user_id_users_table_id_fk": { |
|||
"name": "clients_table_user_id_users_table_id_fk", |
|||
"tableFrom": "clients_table", |
|||
"tableTo": "users_table", |
|||
"columnsFrom": [ |
|||
"user_id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "restrict", |
|||
"onUpdate": "cascade" |
|||
}, |
|||
"clients_table_interface_id_interfaces_table_name_fk": { |
|||
"name": "clients_table_interface_id_interfaces_table_name_fk", |
|||
"tableFrom": "clients_table", |
|||
"tableTo": "interfaces_table", |
|||
"columnsFrom": [ |
|||
"interface_id" |
|||
], |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"general_table": { |
|||
"name": "general_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": 1 |
|||
}, |
|||
"setup_step": { |
|||
"name": "setup_step", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"session_password": { |
|||
"name": "session_password", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"session_timeout": { |
|||
"name": "session_timeout", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"metrics_prometheus": { |
|||
"name": "metrics_prometheus", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"metrics_json": { |
|||
"name": "metrics_json", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"metrics_password": { |
|||
"name": "metrics_password", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"hooks_table": { |
|||
"name": "hooks_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"pre_up": { |
|||
"name": "pre_up", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"post_up": { |
|||
"name": "post_up", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"pre_down": { |
|||
"name": "pre_down", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"post_down": { |
|||
"name": "post_down", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": { |
|||
"hooks_table_id_interfaces_table_name_fk": { |
|||
"name": "hooks_table_id_interfaces_table_name_fk", |
|||
"tableFrom": "hooks_table", |
|||
"tableTo": "interfaces_table", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"interfaces_table": { |
|||
"name": "interfaces_table", |
|||
"columns": { |
|||
"name": { |
|||
"name": "name", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"device": { |
|||
"name": "device", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"port": { |
|||
"name": "port", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"private_key": { |
|||
"name": "private_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"public_key": { |
|||
"name": "public_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"ipv4_cidr": { |
|||
"name": "ipv4_cidr", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"ipv6_cidr": { |
|||
"name": "ipv6_cidr", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"mtu": { |
|||
"name": "mtu", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"j_c": { |
|||
"name": "j_c", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 7 |
|||
}, |
|||
"j_min": { |
|||
"name": "j_min", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 10 |
|||
}, |
|||
"j_max": { |
|||
"name": "j_max", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 1000 |
|||
}, |
|||
"s1": { |
|||
"name": "s1", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 128 |
|||
}, |
|||
"s2": { |
|||
"name": "s2", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 56 |
|||
}, |
|||
"s3": { |
|||
"name": "s3", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"s4": { |
|||
"name": "s4", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"h1": { |
|||
"name": "h1", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"h2": { |
|||
"name": "h2", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"h3": { |
|||
"name": "h3", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"h4": { |
|||
"name": "h4", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i1": { |
|||
"name": "i1", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i2": { |
|||
"name": "i2", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i3": { |
|||
"name": "i3", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i4": { |
|||
"name": "i4", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"i5": { |
|||
"name": "i5", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"enabled": { |
|||
"name": "enabled", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"firewall_enabled": { |
|||
"name": "firewall_enabled", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"interfaces_table_port_unique": { |
|||
"name": "interfaces_table_port_unique", |
|||
"columns": [ |
|||
"port" |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"one_time_links_table": { |
|||
"name": "one_time_links_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"one_time_link": { |
|||
"name": "one_time_link", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"expires_at": { |
|||
"name": "expires_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"one_time_links_table_one_time_link_unique": { |
|||
"name": "one_time_links_table_one_time_link_unique", |
|||
"columns": [ |
|||
"one_time_link" |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"foreignKeys": { |
|||
"one_time_links_table_id_clients_table_id_fk": { |
|||
"name": "one_time_links_table_id_clients_table_id_fk", |
|||
"tableFrom": "one_time_links_table", |
|||
"tableTo": "clients_table", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"users_table": { |
|||
"name": "users_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": true |
|||
}, |
|||
"username": { |
|||
"name": "username", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"password": { |
|||
"name": "password", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"email": { |
|||
"name": "email", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"name": { |
|||
"name": "name", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"role": { |
|||
"name": "role", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"totp_key": { |
|||
"name": "totp_key", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"totp_verified": { |
|||
"name": "totp_verified", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"enabled": { |
|||
"name": "enabled", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": { |
|||
"users_table_username_unique": { |
|||
"name": "users_table_username_unique", |
|||
"columns": [ |
|||
"username" |
|||
], |
|||
"isUnique": true |
|||
} |
|||
}, |
|||
"foreignKeys": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"user_configs_table": { |
|||
"name": "user_configs_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_mtu": { |
|||
"name": "default_mtu", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_persistent_keepalive": { |
|||
"name": "default_persistent_keepalive", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_dns": { |
|||
"name": "default_dns", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_allowed_ips": { |
|||
"name": "default_allowed_ips", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"default_j_c": { |
|||
"name": "default_j_c", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 7 |
|||
}, |
|||
"default_j_min": { |
|||
"name": "default_j_min", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 10 |
|||
}, |
|||
"default_j_max": { |
|||
"name": "default_j_max", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false, |
|||
"default": 1000 |
|||
}, |
|||
"default_i1": { |
|||
"name": "default_i1", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"default_i2": { |
|||
"name": "default_i2", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"default_i3": { |
|||
"name": "default_i3", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"default_i4": { |
|||
"name": "default_i4", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"default_i5": { |
|||
"name": "default_i5", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": false, |
|||
"autoincrement": false |
|||
}, |
|||
"host": { |
|||
"name": "host", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"port": { |
|||
"name": "port", |
|||
"type": "integer", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"created_at": { |
|||
"name": "created_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
}, |
|||
"updated_at": { |
|||
"name": "updated_at", |
|||
"type": "text", |
|||
"primaryKey": false, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": "(CURRENT_TIMESTAMP)" |
|||
} |
|||
}, |
|||
"indexes": {}, |
|||
"foreignKeys": { |
|||
"user_configs_table_id_interfaces_table_name_fk": { |
|||
"name": "user_configs_table_id_interfaces_table_name_fk", |
|||
"tableFrom": "user_configs_table", |
|||
"tableTo": "interfaces_table", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
} |
|||
}, |
|||
"views": {}, |
|||
"enums": {}, |
|||
"_meta": { |
|||
"schemas": {}, |
|||
"tables": {}, |
|||
"columns": {} |
|||
}, |
|||
"internal": { |
|||
"indexes": {} |
|||
} |
|||
} |
|||
@ -0,0 +1,363 @@ |
|||
import debug from 'debug'; |
|||
import { isIPv6 } from 'is-ip'; |
|||
|
|||
import type { ClientType } from '#db/repositories/client/types'; |
|||
import type { InterfaceType } from '#db/repositories/interface/types'; |
|||
import type { UserConfigType } from '#db/repositories/userConfig/types'; |
|||
|
|||
const FW_DEBUG = debug('Firewall'); |
|||
const CHAIN_NAME = 'WG_CLIENTS'; |
|||
|
|||
// Mutex to prevent concurrent rule rebuilds
|
|||
let rebuildInProgress = false; |
|||
let rebuildQueued = false; |
|||
|
|||
// Cache iptables availability check result
|
|||
let iptablesAvailable: boolean | null = null; |
|||
|
|||
type ParsedEntry = { |
|||
ip: string; |
|||
port?: number; |
|||
proto?: 'tcp' | 'udp' | 'both'; |
|||
}; |
|||
|
|||
type FirewallClient = Pick< |
|||
ClientType, |
|||
| 'id' |
|||
| 'name' |
|||
| 'ipv4Address' |
|||
| 'ipv6Address' |
|||
| 'allowedIps' |
|||
| 'firewallIps' |
|||
| 'enabled' |
|||
>; |
|||
|
|||
/** |
|||
* Sanitize a client identifier for use in an iptables comment. |
|||
* Strips all characters except ASCII alphanumeric, space, underscore, hyphen, and dot. |
|||
* Combines with client ID for a safe, identifiable comment. |
|||
* Truncates to 256 bytes (iptables comment module limit). |
|||
*/ |
|||
function sanitizeComment(clientId: number, clientName: string): string { |
|||
const safe = clientName.replace(/[^a-zA-Z0-9 _.-]/g, ''); |
|||
const comment = `client ${clientId}: ${safe}`; |
|||
return comment.slice(0, 256); |
|||
} |
|||
|
|||
/** |
|||
* Parse a firewall entry string into its components. |
|||
* Supports formats: |
|||
* - IP: "10.0.0.1" or "2001:db8::1" |
|||
* - CIDR: "10.0.0.0/24" or "2001:db8::/32" |
|||
* - IP:port: "10.0.0.1:443" or "[2001:db8::1]:443" |
|||
* - IP:port/proto: "10.0.0.1:443/tcp" or "10.0.0.1:53/udp" |
|||
* - CIDR:port: "10.0.0.0/24:443" |
|||
* - CIDR:port/proto: "10.0.0.0/24:443/tcp" or "10.0.0.0/24:53/udp" |
|||
* |
|||
* Note: Protocol (/tcp or /udp) requires a port. "IP/tcp" or "CIDR/tcp" without |
|||
* a port is invalid and will throw an error. |
|||
* |
|||
* @throws {Error} If protocol is specified without a port |
|||
*/ |
|||
function parseFirewallEntry(entry: string): ParsedEntry { |
|||
// Extract protocol suffix first: /tcp or /udp
|
|||
let proto: 'tcp' | 'udp' | 'both' | undefined; |
|||
let remaining = entry; |
|||
|
|||
if (entry.endsWith('/tcp')) { |
|||
proto = 'tcp'; |
|||
remaining = entry.slice(0, -4); |
|||
} else if (entry.endsWith('/udp')) { |
|||
proto = 'udp'; |
|||
remaining = entry.slice(0, -4); |
|||
} |
|||
|
|||
// Handle IPv6 with port: [2001:db8::1]:443
|
|||
if (remaining.startsWith('[')) { |
|||
const match = remaining.match(/^\[(.+)\]:(\d+)$/); |
|||
if (match && match[1] && match[2]) { |
|||
return { |
|||
ip: match[1], |
|||
port: parseInt(match[2], 10), |
|||
proto: proto ?? 'both', |
|||
}; |
|||
} |
|||
// Just bracketed IPv6 without port
|
|||
const ipMatch = remaining.match(/^\[(.+)\]$/); |
|||
if (ipMatch && ipMatch[1]) { |
|||
if (proto) { |
|||
throw new Error( |
|||
`Invalid firewall entry "${entry}": Protocol (/${proto}) requires a port. Use format like "[${ipMatch[1]}]:443/${proto}"` |
|||
); |
|||
} |
|||
return { ip: ipMatch[1] }; |
|||
} |
|||
if (proto) { |
|||
throw new Error( |
|||
`Invalid firewall entry "${entry}": Protocol (/${proto}) requires a port` |
|||
); |
|||
} |
|||
return { ip: remaining }; |
|||
} |
|||
|
|||
// Handle IPv4 with port or CIDR with port
|
|||
// Count colons to distinguish IPv6 from IPv4:port
|
|||
const colonCount = (remaining.match(/:/g) || []).length; |
|||
|
|||
if (colonCount === 1) { |
|||
// Could be IPv4:port or CIDR:port
|
|||
const lastColon = remaining.lastIndexOf(':'); |
|||
const possiblePort = remaining.slice(lastColon + 1); |
|||
if (/^\d+$/.test(possiblePort)) { |
|||
return { |
|||
ip: remaining.slice(0, lastColon), |
|||
port: parseInt(possiblePort, 10), |
|||
proto: proto ?? 'both', |
|||
}; |
|||
} |
|||
} |
|||
|
|||
// Plain IP or CIDR (IPv4 or IPv6)
|
|||
if (proto) { |
|||
throw new Error( |
|||
`Invalid firewall entry "${entry}": Protocol (/${proto}) requires a port. Use format like "${remaining}:443/${proto}"` |
|||
); |
|||
} |
|||
return { ip: remaining }; |
|||
} |
|||
|
|||
/** |
|||
* Generate iptables rule arguments for a single firewall entry |
|||
*/ |
|||
function generateRuleArgs( |
|||
clientIp: string, |
|||
entry: ParsedEntry, |
|||
comment?: string, |
|||
action: 'A' | 'D' = 'A' |
|||
): string[] { |
|||
const rules: string[] = []; |
|||
const commentArg = comment ? ` -m comment --comment "${comment}"` : ''; |
|||
const baseArgs = `-${action} ${CHAIN_NAME} -s ${clientIp} -d ${entry.ip}`; |
|||
|
|||
if (entry.port) { |
|||
// Port-specific rules
|
|||
if (entry.proto === 'tcp' || entry.proto === 'both') { |
|||
rules.push( |
|||
`${baseArgs} -p tcp --dport ${entry.port}${commentArg} -j ACCEPT` |
|||
); |
|||
} |
|||
if (entry.proto === 'udp' || entry.proto === 'both') { |
|||
rules.push( |
|||
`${baseArgs} -p udp --dport ${entry.port}${commentArg} -j ACCEPT` |
|||
); |
|||
} |
|||
} else { |
|||
// No port - allow all traffic to destination
|
|||
rules.push(`${baseArgs}${commentArg} -j ACCEPT`); |
|||
} |
|||
|
|||
return rules; |
|||
} |
|||
|
|||
export const firewall = { |
|||
/** |
|||
* Initialize the custom chain if it doesn't exist |
|||
*/ |
|||
async initChain(interfaceName: string): Promise<void> { |
|||
FW_DEBUG( |
|||
`Initializing firewall chain ${CHAIN_NAME} for interface ${interfaceName}` |
|||
); |
|||
|
|||
// Create chain if not exists (iptables returns error if exists, so we ignore)
|
|||
await exec(`iptables -N ${CHAIN_NAME} 2>/dev/null || true`); |
|||
await exec(`ip6tables -N ${CHAIN_NAME} 2>/dev/null || true`); |
|||
|
|||
// Ensure chain is referenced from FORWARD (if not already)
|
|||
// Insert at position 1 to process before generic ACCEPT rules
|
|||
await exec( |
|||
`iptables -C FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || iptables -I FORWARD 1 -i ${interfaceName} -j ${CHAIN_NAME}` |
|||
); |
|||
await exec( |
|||
`ip6tables -C FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || ip6tables -I FORWARD 1 -i ${interfaceName} -j ${CHAIN_NAME}` |
|||
); |
|||
}, |
|||
|
|||
/** |
|||
* Flush all rules in the custom chain |
|||
*/ |
|||
async flushChain(): Promise<void> { |
|||
FW_DEBUG(`Flushing firewall chain ${CHAIN_NAME}`); |
|||
await exec(`iptables -F ${CHAIN_NAME} 2>/dev/null || true`); |
|||
await exec(`ip6tables -F ${CHAIN_NAME} 2>/dev/null || true`); |
|||
}, |
|||
|
|||
/** |
|||
* Apply firewall rules for a single client |
|||
*/ |
|||
async applyClientRules( |
|||
client: FirewallClient, |
|||
defaultAllowedIps: string[], |
|||
enableIpv6: boolean |
|||
): Promise<void> { |
|||
// Determine which IPs to use for firewall rules
|
|||
// Priority: firewallIps > allowedIps > defaultAllowedIps
|
|||
const effectiveIps = |
|||
client.firewallIps && client.firewallIps.length > 0 |
|||
? client.firewallIps |
|||
: (client.allowedIps ?? defaultAllowedIps); |
|||
|
|||
FW_DEBUG( |
|||
`Applying firewall rules for client ${client.name} (${client.id}): ${effectiveIps.join(', ')}` |
|||
); |
|||
|
|||
const comment = sanitizeComment(client.id, client.name); |
|||
|
|||
for (const ipEntry of effectiveIps) { |
|||
const parsed = parseFirewallEntry(ipEntry); |
|||
const baseIp = parsed.ip.split('/')[0] ?? parsed.ip; // Handle CIDR by checking base IP
|
|||
const destIsIpv6 = isIPv6(baseIp); |
|||
|
|||
if (destIsIpv6) { |
|||
if (enableIpv6) { |
|||
const rules = generateRuleArgs(client.ipv6Address, parsed, comment); |
|||
for (const rule of rules) { |
|||
await exec(`ip6tables ${rule}`); |
|||
} |
|||
} |
|||
} else { |
|||
const rules = generateRuleArgs(client.ipv4Address, parsed, comment); |
|||
for (const rule of rules) { |
|||
await exec(`iptables ${rule}`); |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Full rebuild of firewall rules from database state |
|||
*/ |
|||
async rebuildRules( |
|||
wgInterface: InterfaceType, |
|||
clients: FirewallClient[], |
|||
userConfig: UserConfigType, |
|||
enableIpv6: boolean |
|||
): Promise<void> { |
|||
if (!wgInterface.firewallEnabled) { |
|||
FW_DEBUG('Firewall filtering disabled, removing any existing rules'); |
|||
await this.removeFiltering(wgInterface.name); |
|||
return; |
|||
} |
|||
|
|||
// Handle concurrent rebuilds with queue
|
|||
if (rebuildInProgress) { |
|||
FW_DEBUG('Rebuild already in progress, queuing'); |
|||
rebuildQueued = true; |
|||
return; |
|||
} |
|||
|
|||
rebuildInProgress = true; |
|||
|
|||
try { |
|||
FW_DEBUG('Rebuilding firewall rules...'); |
|||
|
|||
// Initialize chain structure
|
|||
await this.initChain(wgInterface.name); |
|||
|
|||
// Flush existing rules
|
|||
await this.flushChain(); |
|||
|
|||
// Apply rules for each enabled client
|
|||
for (const client of clients) { |
|||
if (!client.enabled) continue; |
|||
await this.applyClientRules( |
|||
client, |
|||
userConfig.defaultAllowedIps, |
|||
enableIpv6 |
|||
); |
|||
} |
|||
|
|||
// Add final DROP for any traffic not explicitly allowed
|
|||
await exec(`iptables -A ${CHAIN_NAME} -j DROP`); |
|||
if (enableIpv6) { |
|||
await exec(`ip6tables -A ${CHAIN_NAME} -j DROP`); |
|||
} |
|||
|
|||
FW_DEBUG('Firewall rules rebuilt successfully'); |
|||
} finally { |
|||
rebuildInProgress = false; |
|||
|
|||
// If another rebuild was queued, run it now
|
|||
if (rebuildQueued) { |
|||
rebuildQueued = false; |
|||
FW_DEBUG('Processing queued rebuild'); |
|||
await this.rebuildRules(wgInterface, clients, userConfig, enableIpv6); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Remove all firewall filtering (when feature is disabled) |
|||
*/ |
|||
async removeFiltering(interfaceName: string): Promise<void> { |
|||
FW_DEBUG(`Removing firewall filtering for interface ${interfaceName}`); |
|||
|
|||
// Remove jump rules from FORWARD chain
|
|||
await exec( |
|||
`iptables -D FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || true` |
|||
); |
|||
await exec( |
|||
`ip6tables -D FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || true` |
|||
); |
|||
|
|||
// Flush and delete the chain
|
|||
await exec(`iptables -F ${CHAIN_NAME} 2>/dev/null || true`); |
|||
await exec(`ip6tables -F ${CHAIN_NAME} 2>/dev/null || true`); |
|||
await exec(`iptables -X ${CHAIN_NAME} 2>/dev/null || true`); |
|||
await exec(`ip6tables -X ${CHAIN_NAME} 2>/dev/null || true`); |
|||
}, |
|||
|
|||
/** |
|||
* Check if iptables (and optionally ip6tables) are available on the system. |
|||
* @param enableIpv6 - If true, also check for ip6tables. Defaults to true. |
|||
*/ |
|||
async isAvailable(enableIpv6: boolean = true): Promise<boolean> { |
|||
// Return cached result if we've already checked
|
|||
if (iptablesAvailable !== null) { |
|||
return iptablesAvailable; |
|||
} |
|||
|
|||
try { |
|||
// Check for iptables (always required)
|
|||
await exec('iptables --version'); |
|||
FW_DEBUG('iptables is available'); |
|||
|
|||
// Check for ip6tables (only if IPv6 is enabled)
|
|||
if (enableIpv6) { |
|||
await exec('ip6tables --version'); |
|||
FW_DEBUG('ip6tables is available'); |
|||
} else { |
|||
FW_DEBUG('IPv6 disabled, skipping ip6tables check'); |
|||
} |
|||
|
|||
iptablesAvailable = true; |
|||
return true; |
|||
} catch (error) { |
|||
iptablesAvailable = false; |
|||
FW_DEBUG('iptables/ip6tables is not available:', error); |
|||
return false; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* Clear the availability cache to force a re-check |
|||
*/ |
|||
clearAvailabilityCache(): void { |
|||
iptablesAvailable = null; |
|||
}, |
|||
}; |
|||
|
|||
export const firewallTestExports = { |
|||
parseFirewallEntry, |
|||
generateRuleArgs, |
|||
sanitizeComment, |
|||
}; |
|||
@ -0,0 +1,41 @@ |
|||
// ! Auto Imports are not supported in this file
|
|||
|
|||
import type { ErrorCorrection } from 'qr'; |
|||
import { encodeQR } from 'qr'; |
|||
|
|||
export function encodeQRCode(config: string): string { |
|||
return tryECCModes((ecc) => { |
|||
return encodeQR(config, 'svg', { |
|||
ecc, |
|||
scale: 2, |
|||
encoding: 'byte', |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
export function encodeQRCodeTerm(config: string): string { |
|||
return tryECCModes((ecc) => { |
|||
return encodeQR(config, 'term', { |
|||
ecc, |
|||
encoding: 'byte', |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function tryECCModes<T>(callback: (ecc: ErrorCorrection) => T): T { |
|||
// defined manually, as qr's ECMode is in wrong order
|
|||
const ECMode = ['high', 'quartile', 'medium', 'low'] as const; |
|||
for (const ecc of ECMode) { |
|||
try { |
|||
return callback(ecc); |
|||
} catch (err) { |
|||
if (!(err instanceof Error && err.message === 'Capacity overflow')) { |
|||
throw err; |
|||
} |
|||
// retry with lower ecc
|
|||
} |
|||
} |
|||
throw new Error( |
|||
'Failed to generate QR code: Capacity overflow at all ECC levels' |
|||
); |
|||
} |
|||
@ -0,0 +1,294 @@ |
|||
import { describe, expect, test } from 'vitest'; |
|||
import { firewallTestExports } from '../../server/utils/firewall'; |
|||
import { typesTestExports } from '../../server/utils/types'; |
|||
|
|||
describe('firewall', () => { |
|||
describe('isValidFirewallEntry', () => { |
|||
test('invalid ips', () => { |
|||
expect(() => typesTestExports.FirewallIpEntrySchema.parse('')).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('255.255.255.256') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.256') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1.1') |
|||
).toThrow(); |
|||
|
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('[]:443/udp') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('[]:443') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('[::1]/32') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('[1.1.1.1]/32') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('[::g]/32') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('2001:dbx::1') |
|||
).toThrow(); |
|||
}); |
|||
test('invalid port, protocol or cidr', () => { |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:80/tcpp') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:65536') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:0') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/33') |
|||
).toThrow(); |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/32:0') |
|||
).toThrow(); |
|||
}); |
|||
test('protocol without port', () => { |
|||
expect(() => |
|||
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/tcp') |
|||
).toThrow(); |
|||
}); |
|||
test('valid entries', () => { |
|||
expect(typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1')).toBe( |
|||
'1.1.1.1' |
|||
); |
|||
expect(typesTestExports.FirewallIpEntrySchema.parse('::/0')).toBe('::/0'); |
|||
expect(typesTestExports.FirewallIpEntrySchema.parse('::0/0')).toBe( |
|||
'::0/0' |
|||
); |
|||
expect(typesTestExports.FirewallIpEntrySchema.parse('2001:db8::1')).toBe( |
|||
'2001:db8::1' |
|||
); |
|||
expect(typesTestExports.FirewallIpEntrySchema.parse('::1')).toBe('::1'); |
|||
expect( |
|||
typesTestExports.FirewallIpEntrySchema.parse('2001:db8::1/32') |
|||
).toBe('2001:db8::1/32'); |
|||
expect(typesTestExports.FirewallIpEntrySchema.parse('[::1]')).toBe( |
|||
'[::1]' |
|||
); |
|||
expect(typesTestExports.FirewallIpEntrySchema.parse('[::1/32]')).toBe( |
|||
'[::1/32]' |
|||
); |
|||
}); |
|||
}); |
|||
describe('parseFirewallEntry', () => { |
|||
test('IPv4', () => { |
|||
expect(firewallTestExports.parseFirewallEntry('1.1.1.1')).toEqual({ |
|||
ip: '1.1.1.1', |
|||
}); |
|||
}); |
|||
test('IPv4 with Protocol', () => { |
|||
expect(() => |
|||
firewallTestExports.parseFirewallEntry('1.1.1.1/tcp') |
|||
).toThrow(); |
|||
expect(() => |
|||
firewallTestExports.parseFirewallEntry('1.1.1.1/udp') |
|||
).toThrow(); |
|||
}); |
|||
test('IPv4 with CIDR', () => { |
|||
expect(firewallTestExports.parseFirewallEntry('1.1.1.1/32')).toEqual({ |
|||
ip: '1.1.1.1/32', |
|||
}); |
|||
}); |
|||
test('IPv4 with CIDR and Protocol', () => { |
|||
expect(() => |
|||
firewallTestExports.parseFirewallEntry('1.1.1.1/32/tcp') |
|||
).toThrow(); |
|||
}); |
|||
test('IPv4 with Port', () => { |
|||
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80')).toEqual({ |
|||
ip: '1.1.1.1', |
|||
port: 80, |
|||
proto: 'both', |
|||
}); |
|||
}); |
|||
test('IPv4 with Port and Protocol', () => { |
|||
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80/tcp')).toEqual({ |
|||
ip: '1.1.1.1', |
|||
port: 80, |
|||
proto: 'tcp', |
|||
}); |
|||
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80/udp')).toEqual({ |
|||
ip: '1.1.1.1', |
|||
port: 80, |
|||
proto: 'udp', |
|||
}); |
|||
}); |
|||
test('IPv4 with CIDR and Port', () => { |
|||
expect( |
|||
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443') |
|||
).toEqual({ |
|||
ip: '10.10.0.0/24', |
|||
port: 443, |
|||
proto: 'both', |
|||
}); |
|||
}); |
|||
test('IPv4 with CIDR, Port and Protocol', () => { |
|||
expect( |
|||
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443/tcp') |
|||
).toEqual({ |
|||
ip: '10.10.0.0/24', |
|||
port: 443, |
|||
proto: 'tcp', |
|||
}); |
|||
expect( |
|||
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443/udp') |
|||
).toEqual({ |
|||
ip: '10.10.0.0/24', |
|||
port: 443, |
|||
proto: 'udp', |
|||
}); |
|||
}); |
|||
test('IPv6', () => { |
|||
expect(firewallTestExports.parseFirewallEntry('[2001:db8::1]')).toEqual({ |
|||
ip: '2001:db8::1', |
|||
}); |
|||
expect(firewallTestExports.parseFirewallEntry('2001:db8::1')).toEqual({ |
|||
ip: '2001:db8::1', |
|||
}); |
|||
}); |
|||
test('IPv6 with Protocol', () => { |
|||
expect(() => |
|||
firewallTestExports.parseFirewallEntry('[2001:db8::1]/tcp') |
|||
).toThrow(); |
|||
expect(() => |
|||
firewallTestExports.parseFirewallEntry('2001:db8::1/udp') |
|||
).toThrow(); |
|||
}); |
|||
test('IPv6 with CIDR', () => { |
|||
expect(firewallTestExports.parseFirewallEntry('::0/0')).toEqual({ |
|||
ip: '::0/0', |
|||
}); |
|||
expect(firewallTestExports.parseFirewallEntry('[::0/0]')).toEqual({ |
|||
ip: '::0/0', |
|||
}); |
|||
}); |
|||
test('IPv6 with CIDR and Protocol', () => { |
|||
expect(() => |
|||
firewallTestExports.parseFirewallEntry('::0/0/tcp') |
|||
).toThrow(); |
|||
}); |
|||
test('IPv6 with Port', () => { |
|||
expect( |
|||
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443') |
|||
).toEqual({ |
|||
ip: '2001:db8::1', |
|||
port: 443, |
|||
proto: 'both', |
|||
}); |
|||
}); |
|||
test('IPv6 with Port and Protocol', () => { |
|||
expect( |
|||
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/tcp') |
|||
).toEqual({ |
|||
ip: '2001:db8::1', |
|||
port: 443, |
|||
proto: 'tcp', |
|||
}); |
|||
expect( |
|||
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/udp') |
|||
).toEqual({ |
|||
ip: '2001:db8::1', |
|||
port: 443, |
|||
proto: 'udp', |
|||
}); |
|||
}); |
|||
test('IPv6 with CIDR and Port', () => { |
|||
expect( |
|||
firewallTestExports.parseFirewallEntry('[2001:db8::/32]:443') |
|||
).toEqual({ |
|||
ip: '2001:db8::/32', |
|||
port: 443, |
|||
proto: 'both', |
|||
}); |
|||
}); |
|||
test('IPv6 with CIDR, Port and Protocol', () => { |
|||
expect( |
|||
firewallTestExports.parseFirewallEntry('[2001:db8::/32]:443/tcp') |
|||
).toEqual({ |
|||
ip: '2001:db8::/32', |
|||
port: 443, |
|||
proto: 'tcp', |
|||
}); |
|||
}); |
|||
}); |
|||
describe('sanitizeComment', () => { |
|||
test('basic ASCII name', () => { |
|||
expect(firewallTestExports.sanitizeComment(1, 'My Laptop')).toBe( |
|||
'client 1: My Laptop' |
|||
); |
|||
}); |
|||
test('strips non-ASCII and shell metacharacters', () => { |
|||
expect(firewallTestExports.sanitizeComment(42, 'café')).toBe( |
|||
'client 42: caf' |
|||
); |
|||
expect(firewallTestExports.sanitizeComment(5, 'a"; rm -rf /')).toBe( |
|||
'client 5: a rm -rf ' |
|||
); |
|||
expect(firewallTestExports.sanitizeComment(7, 'test$(cmd)`id`')).toBe( |
|||
'client 7: testcmdid' |
|||
); |
|||
}); |
|||
test('preserves allowed punctuation', () => { |
|||
expect(firewallTestExports.sanitizeComment(3, 'phone-2.lan_home')).toBe( |
|||
'client 3: phone-2.lan_home' |
|||
); |
|||
}); |
|||
test('truncates to 256 bytes', () => { |
|||
const longName = 'a'.repeat(300); |
|||
const result = firewallTestExports.sanitizeComment(1, longName); |
|||
expect(result.length).toBeLessThanOrEqual(256); |
|||
expect(result).toBe('client 1: ' + 'a'.repeat(246)); |
|||
}); |
|||
}); |
|||
describe('generateRuleArgs', () => { |
|||
test('includes comment when provided', () => { |
|||
const rules = firewallTestExports.generateRuleArgs( |
|||
'10.8.0.2', |
|||
{ ip: '10.0.0.1' }, |
|||
'client 1: test' |
|||
); |
|||
expect(rules).toEqual([ |
|||
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -m comment --comment "client 1: test" -j ACCEPT', |
|||
]); |
|||
}); |
|||
test('omits comment when not provided', () => { |
|||
const rulesTcp = firewallTestExports.generateRuleArgs('10.8.0.2', { |
|||
ip: '10.0.0.1', |
|||
port: 80, |
|||
proto: 'tcp', |
|||
}); |
|||
expect(rulesTcp).toEqual([ |
|||
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p tcp --dport 80 -j ACCEPT', |
|||
]); |
|||
const rulesUdp = firewallTestExports.generateRuleArgs('10.8.0.2', { |
|||
ip: '10.0.0.1', |
|||
port: 80, |
|||
proto: 'udp', |
|||
}); |
|||
expect(rulesUdp).toEqual([ |
|||
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p udp --dport 80 -j ACCEPT', |
|||
]); |
|||
}); |
|||
test('comment with port generates two rules for both proto', () => { |
|||
const rules = firewallTestExports.generateRuleArgs( |
|||
'10.8.0.2', |
|||
{ ip: '10.0.0.1', port: 443, proto: 'both' }, |
|||
'client 2: phone' |
|||
); |
|||
expect(rules).toEqual([ |
|||
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p tcp --dport 443 -m comment --comment "client 2: phone" -j ACCEPT', |
|||
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p udp --dport 443 -m comment --comment "client 2: phone" -j ACCEPT', |
|||
]); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,20 @@ |
|||
import { expect, test, describe } from 'vitest'; |
|||
import { |
|||
hashPassword, |
|||
isPasswordValid, |
|||
isValidPasswordHash, |
|||
} from '../../server/utils/password'; |
|||
|
|||
describe('password', () => { |
|||
test('password', async () => { |
|||
const hash = await hashPassword('password'); |
|||
|
|||
await expect(isPasswordValid('password', hash)).resolves.toBe(true); |
|||
await expect(isPasswordValid('wrong', hash)).resolves.toBe(false); |
|||
|
|||
expect(isValidPasswordHash('not a hash')).toBe(false); |
|||
expect(isValidPasswordHash(hash)).toBe(true); |
|||
|
|||
expect(isValidPasswordHash(hash.replace('argon2', 'argon3'))).toBe(false); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,18 @@ |
|||
import { defineConfig } from 'vitest/config'; |
|||
|
|||
export default defineConfig({ |
|||
test: { |
|||
projects: [ |
|||
{ |
|||
test: { |
|||
name: 'unit', |
|||
include: ['test/unit/*.{test,spec}.ts'], |
|||
environment: 'node', |
|||
}, |
|||
}, |
|||
], |
|||
coverage: { |
|||
enabled: true, |
|||
}, |
|||
}, |
|||
}); |
|||
Loading…
Reference in new issue