From 22ccd4d51b1557d856c77a9805dd45ce7443a37e Mon Sep 17 00:00:00 2001
From: Bernd Storath <999999bst@gmail.com>
Date: Sun, 19 Jan 2025 16:03:14 +0100
Subject: [PATCH] Migrate to sqlite
---
src/app/components/admin/CidrDialog.vue | 14 +++---
src/app/pages/admin/config.vue | 49 +++++--------------
src/app/pages/admin/interface.vue | 34 +++++++++++++
src/eslint.config.mjs | 2 +
src/i18n/locales/en.json | 42 +++++++++-------
src/server/api/admin/general.get.ts | 2 +-
src/server/api/admin/general.post.ts | 19 ++++---
src/server/api/admin/hooks.get.ts | 2 +-
src/server/api/admin/hooks.post.ts | 23 +++++----
src/server/api/admin/interface.get.ts | 7 ---
src/server/api/admin/interface.post.ts | 9 ----
src/server/api/admin/interface/cidr.post.ts | 15 ++++++
src/server/api/admin/interface/index.get.ts | 12 +++++
src/server/api/admin/interface/index.post.ts | 14 ++++++
src/server/api/admin/userconfig.get.ts | 7 +++
src/server/api/admin/userconfig.post.ts | 14 ++++++
src/server/api/admin/userconfig/cidr.post.ts | 9 ----
src/server/api/admin/userconfig/index.get.ts | 4 --
src/server/api/admin/userconfig/index.post.ts | 9 ----
src/server/api/setup/4.post.ts | 10 +---
src/server/api/setup/5.post.ts | 10 +---
src/server/api/setup/migrate.post.ts | 9 +---
.../database/repositories/client/service.ts | 4 +-
.../database/repositories/client/types.ts | 44 +++--------------
.../database/repositories/general/types.ts | 2 +
.../database/repositories/hooks/service.ts | 15 +++++-
.../database/repositories/hooks/types.ts | 14 ++++++
.../repositories/interface/service.ts | 46 +++++++++++++++++
.../database/repositories/interface/types.ts | 45 +++++++++++++++++
.../repositories/userConfig/service.ts | 21 ++++++--
.../database/repositories/userConfig/types.ts | 23 ++++++---
src/server/database/schema.ts | 1 +
src/server/utils/handler.ts | 32 ++++++++++++
src/server/utils/types.ts | 37 ++++++++++++++
src/shared/utils/permissions.ts | 2 +-
35 files changed, 401 insertions(+), 201 deletions(-)
delete mode 100644 src/server/api/admin/interface.get.ts
delete mode 100644 src/server/api/admin/interface.post.ts
create mode 100644 src/server/api/admin/interface/cidr.post.ts
create mode 100644 src/server/api/admin/interface/index.get.ts
create mode 100644 src/server/api/admin/interface/index.post.ts
create mode 100644 src/server/api/admin/userconfig.get.ts
create mode 100644 src/server/api/admin/userconfig.post.ts
delete mode 100644 src/server/api/admin/userconfig/cidr.post.ts
delete mode 100644 src/server/api/admin/userconfig/index.get.ts
delete mode 100644 src/server/api/admin/userconfig/index.post.ts
diff --git a/src/app/components/admin/CidrDialog.vue b/src/app/components/admin/CidrDialog.vue
index a78bf180..bcf32809 100644
--- a/src/app/components/admin/CidrDialog.vue
+++ b/src/app/components/admin/CidrDialog.vue
@@ -17,8 +17,8 @@
class="mb-5 mt-2 text-sm leading-normal text-gray-500 dark:text-neutral-300"
>
-
-
+
+
@@ -26,7 +26,7 @@
{{ $t('cancel') }}
- Change
@@ -40,10 +40,10 @@
defineEmits(['change']);
const props = defineProps<{
triggerClass?: string;
- address4: string;
- address6: string;
+ ipv4Cidr: string;
+ ipv6Cidr: string;
}>();
-const address4 = ref(props.address4);
-const address6 = ref(props.address6);
+const ipv4Cidr = ref(props.ipv4Cidr);
+const ipv6Cidr = ref(props.ipv6Cidr);
diff --git a/src/app/pages/admin/config.vue b/src/app/pages/admin/config.vue
index ca399d77..c66d2b5b 100644
--- a/src/app/pages/admin/config.vue
+++ b/src/app/pages/admin/config.vue
@@ -8,7 +8,10 @@
Allowed IPs
-
+
DNS
@@ -16,10 +19,14 @@
Advanced
-
+
@@ -27,14 +34,6 @@
Actions
-
-
-
@@ -79,30 +78,4 @@ async function revert() {
await refresh();
data.value = toRef(_data.value).value;
}
-
-async function changeCidr(address4: string, address6: string) {
- try {
- const res = await $fetch(`/api/admin/userconfig/cidr`, {
- method: 'post',
- body: { address4, address6 },
- });
- toast.showToast({
- type: 'success',
- title: 'Success',
- message: 'Changed CIDR',
- });
- if (!res.success) {
- throw new Error('Failed to change CIDR');
- }
- await refreshNuxtData();
- } catch (e) {
- if (e instanceof Error) {
- toast.showToast({
- type: 'error',
- title: 'Error',
- message: e.message,
- });
- }
- }
-}
diff --git a/src/app/pages/admin/interface.vue b/src/app/pages/admin/interface.vue
index def6fc4a..7984c760 100644
--- a/src/app/pages/admin/interface.vue
+++ b/src/app/pages/admin/interface.vue
@@ -11,6 +11,14 @@
Actions
+
+
+
@@ -55,4 +63,30 @@ async function revert() {
await refresh();
data.value = toRef(_data.value).value;
}
+
+async function changeCidr(ipv4Cidr: string, ipv6Cidr: string) {
+ try {
+ const res = await $fetch(`/api/admin/interface/cidr`, {
+ method: 'post',
+ body: { ipv4Cidr, ipv6Cidr },
+ });
+ toast.showToast({
+ type: 'success',
+ title: 'Success',
+ message: 'Changed CIDR',
+ });
+ if (!res.success) {
+ throw new Error('Failed to change CIDR');
+ }
+ await refreshNuxtData();
+ } catch (e) {
+ if (e instanceof Error) {
+ toast.showToast({
+ type: 'error',
+ title: 'Error',
+ message: e.message,
+ });
+ }
+ }
+}
diff --git a/src/eslint.config.mjs b/src/eslint.config.mjs
index ad6fe8e4..07172140 100644
--- a/src/eslint.config.mjs
+++ b/src/eslint.config.mjs
@@ -2,3 +2,5 @@ import { createConfigForNuxt } from '@nuxt/eslint-config/flat';
import eslintConfigPrettier from 'eslint-config-prettier';
export default createConfigForNuxt().append(eslintConfigPrettier);
+
+// TODO: add typescript-eslint, import/order, ban raw defineEventHandler
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 09d4e598..d0cd03df 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -54,30 +54,17 @@
"body": "Body must be a valid object",
"device": "Device must be a valid string",
"deviceMin": "Device must be at least 1 Character",
- "hook": "Hook must be a valid string",
"client": {
"id": "Client ID must be a valid number",
"name": "Name must be a valid string",
"nameMin": "Name must be at least 1 Character",
- "mtu": "MTU must be a valid number",
- "mtuMin": "MTU must be at least 1280",
- "mtuMax": "MTU must be at most 9000",
- "persistentKeepalive": "Persistent Keepalive must be a valid number",
- "persistentKeepaliveMin": "Persistent Keepalive must be at least 0",
- "persistentKeepaliveMax": "Persistent Keepalive must be at most 65535",
"expireDate": "expiredDate must be a valid string",
"expireDateMin": "expiredDate must be at least 1 Character",
- "address": "IP Address must be a valid string",
- "addressMin": "IP Address must be a be at least 1 Character",
"address4": "IPv4 Address must be a valid string",
"address4Min": "IPv4 Address must be a be at least 1 Character",
"address6": "IPv6 Address must be a valid string",
"address6Min": "IPv6 Address must be a be at least 1 Character",
- "allowedIps": "Allowed IPs must be a valid array of strings",
- "allowedIpsMin": "Allowed IPs must have at least 1 item",
- "serverAllowedIps": "Allowed IPs must be a valid array of strings",
- "dns": "DNS must be a valid array of strings",
- "dnsMin": "DNS must have at least 1 item"
+ "serverAllowedIps": "Allowed IPs must be a valid array of strings"
},
"user": {
"username": "Username must be a valid string",
@@ -93,14 +80,31 @@
},
"userConfig": {
"host": "Host must be a valid string",
- "hostMin": "Host must contain at least 1 character",
- "port": "Port must be a valid number",
- "portMin": "Port must be at least 1",
- "portMax": "Port must be at most 65535"
+ "hostMin": "Host must contain at least 1 character"
},
"general": {
"sessionTimeout": "Session Timeout must be a valid number"
- }
+ },
+ "interface": {
+ "cidr": "CIDR must be a valid string",
+ "cidrMin": "CIDR must be at least 1 Character"
+ },
+ "hook": "Hook must be a valid string",
+ "mtu": "MTU must be a valid number",
+ "mtuMin": "MTU must be at least 1280",
+ "mtuMax": "MTU must be at most 9000",
+ "port": "Port must be a valid number",
+ "portMin": "Port must be at least 1",
+ "portMax": "Port must be at most 65535",
+ "persistentKeepalive": "Persistent Keepalive must be a valid number",
+ "persistentKeepaliveMin": "Persistent Keepalive must be at least 0",
+ "persistentKeepaliveMax": "Persistent Keepalive must be at most 65535",
+ "address": "IP Address must be a valid string",
+ "addressMin": "IP Address must be a be at least 1 Character",
+ "dns": "DNS must be a valid array of strings",
+ "dnsMin": "DNS must have at least 1 item",
+ "allowedIps": "Allowed IPs must be a valid array of strings",
+ "allowedIpsMin": "Allowed IPs must have at least 1 item"
},
"name": "Name",
"username": "Username",
diff --git a/src/server/api/admin/general.get.ts b/src/server/api/admin/general.get.ts
index 367c69c2..d27f39f9 100644
--- a/src/server/api/admin/general.get.ts
+++ b/src/server/api/admin/general.get.ts
@@ -1,4 +1,4 @@
-export default defineEventHandler(async () => {
+export default definePermissionEventHandler(actions.ADMIN, async () => {
const sessionConfig = await Database.general.getSessionConfig();
return {
sessionTimeout: sessionConfig.sessionTimeout,
diff --git a/src/server/api/admin/general.post.ts b/src/server/api/admin/general.post.ts
index 8919d30a..4a28cf29 100644
--- a/src/server/api/admin/general.post.ts
+++ b/src/server/api/admin/general.post.ts
@@ -1,10 +1,13 @@
import { GeneralUpdateSchema } from '#db/repositories/general/types';
-export default defineEventHandler(async (event) => {
- const data = await readValidatedBody(
- event,
- validateZod(GeneralUpdateSchema, event)
- );
- await Database.general.update(data);
- return { success: true };
-});
+export default definePermissionEventHandler(
+ actions.ADMIN,
+ async ({ event }) => {
+ const data = await readValidatedBody(
+ event,
+ validateZod(GeneralUpdateSchema, event)
+ );
+ await Database.general.update(data);
+ return { success: true };
+ }
+);
diff --git a/src/server/api/admin/hooks.get.ts b/src/server/api/admin/hooks.get.ts
index f90913f3..470ba3c2 100644
--- a/src/server/api/admin/hooks.get.ts
+++ b/src/server/api/admin/hooks.get.ts
@@ -1,4 +1,4 @@
-export default defineEventHandler(async () => {
+export default definePermissionEventHandler(actions.ADMIN, async () => {
const hooks = await Database.hooks.get('wg0');
if (!hooks) {
throw new Error('Hooks not found');
diff --git a/src/server/api/admin/hooks.post.ts b/src/server/api/admin/hooks.post.ts
index 820c60f4..1a291438 100644
--- a/src/server/api/admin/hooks.post.ts
+++ b/src/server/api/admin/hooks.post.ts
@@ -1,9 +1,14 @@
-export default defineEventHandler(async (event) => {
- const data = await readValidatedBody(
- event,
- validateZod(hooksUpdateType, event)
- );
- await Database.hooks.update(data);
- await WireGuard.saveConfig();
- return { success: true };
-});
+import { HooksUpdateSchema } from '#db/repositories/hooks/types';
+
+export default definePermissionEventHandler(
+ actions.ADMIN,
+ async ({ event }) => {
+ const data = await readValidatedBody(
+ event,
+ validateZod(HooksUpdateSchema, event)
+ );
+ await Database.hooks.update('wg0', data);
+ await WireGuard.saveConfig();
+ return { success: true };
+ }
+);
diff --git a/src/server/api/admin/interface.get.ts b/src/server/api/admin/interface.get.ts
deleted file mode 100644
index 9ae225a3..00000000
--- a/src/server/api/admin/interface.get.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export default defineEventHandler(async () => {
- const wgInterface = await Database.interfaces.get('wg0');
- return {
- ...wgInterface,
- privateKey: undefined,
- };
-});
diff --git a/src/server/api/admin/interface.post.ts b/src/server/api/admin/interface.post.ts
deleted file mode 100644
index 92986561..00000000
--- a/src/server/api/admin/interface.post.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export default defineEventHandler(async (event) => {
- const data = await readValidatedBody(
- event,
- validateZod(interfaceUpdateType, event)
- );
- await Database.system.updateInterface(data);
- await WireGuard.saveConfig();
- return { success: true };
-});
diff --git a/src/server/api/admin/interface/cidr.post.ts b/src/server/api/admin/interface/cidr.post.ts
new file mode 100644
index 00000000..7df34297
--- /dev/null
+++ b/src/server/api/admin/interface/cidr.post.ts
@@ -0,0 +1,15 @@
+import { InterfaceCidrUpdateSchema } from '#db/repositories/interface/types';
+
+export default definePermissionEventHandler(
+ actions.ADMIN,
+ async ({ event }) => {
+ const data = await readValidatedBody(
+ event,
+ validateZod(InterfaceCidrUpdateSchema, event)
+ );
+
+ await Database.interfaces.updateCidr('wg0', data);
+ await WireGuard.saveConfig();
+ return { success: true };
+ }
+);
diff --git a/src/server/api/admin/interface/index.get.ts b/src/server/api/admin/interface/index.get.ts
new file mode 100644
index 00000000..16536d6e
--- /dev/null
+++ b/src/server/api/admin/interface/index.get.ts
@@ -0,0 +1,12 @@
+export default definePermissionEventHandler(actions.ADMIN, async () => {
+ const wgInterface = await Database.interfaces.get('wg0');
+
+ if (!wgInterface) {
+ throw new Error('Interface not found');
+ }
+
+ return {
+ ...wgInterface,
+ privateKey: undefined,
+ };
+});
diff --git a/src/server/api/admin/interface/index.post.ts b/src/server/api/admin/interface/index.post.ts
new file mode 100644
index 00000000..11538d2e
--- /dev/null
+++ b/src/server/api/admin/interface/index.post.ts
@@ -0,0 +1,14 @@
+import { InterfaceUpdateSchema } from '#db/repositories/interface/types';
+
+export default definePermissionEventHandler(
+ actions.ADMIN,
+ async ({ event }) => {
+ const data = await readValidatedBody(
+ event,
+ validateZod(InterfaceUpdateSchema, event)
+ );
+ await Database.interfaces.update('wg0', data);
+ await WireGuard.saveConfig();
+ return { success: true };
+ }
+);
diff --git a/src/server/api/admin/userconfig.get.ts b/src/server/api/admin/userconfig.get.ts
new file mode 100644
index 00000000..d6315be3
--- /dev/null
+++ b/src/server/api/admin/userconfig.get.ts
@@ -0,0 +1,7 @@
+export default definePermissionEventHandler(actions.ADMIN, async () => {
+ const userConfig = await Database.userConfigs.get('wg0');
+ if (!userConfig) {
+ throw new Error('User config not found');
+ }
+ return userConfig;
+});
diff --git a/src/server/api/admin/userconfig.post.ts b/src/server/api/admin/userconfig.post.ts
new file mode 100644
index 00000000..afbc7499
--- /dev/null
+++ b/src/server/api/admin/userconfig.post.ts
@@ -0,0 +1,14 @@
+import { UserConfigUpdateSchema } from '#db/repositories/userConfig/types';
+
+export default definePermissionEventHandler(
+ actions.ADMIN,
+ async ({ event }) => {
+ const data = await readValidatedBody(
+ event,
+ validateZod(UserConfigUpdateSchema, event)
+ );
+ await Database.userConfigs.update('wg0', data);
+ await WireGuard.saveConfig();
+ return { success: true };
+ }
+);
diff --git a/src/server/api/admin/userconfig/cidr.post.ts b/src/server/api/admin/userconfig/cidr.post.ts
deleted file mode 100644
index 5b845aa1..00000000
--- a/src/server/api/admin/userconfig/cidr.post.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export default defineEventHandler(async (event) => {
- const data = await readValidatedBody(
- event,
- validateZod(cidrUpdateType, event)
- );
-
- await WireGuard.updateAddressRange(data);
- return { success: true };
-});
diff --git a/src/server/api/admin/userconfig/index.get.ts b/src/server/api/admin/userconfig/index.get.ts
deleted file mode 100644
index b7683045..00000000
--- a/src/server/api/admin/userconfig/index.get.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export default defineEventHandler(async () => {
- const system = await Database.system.get();
- return system.userConfig;
-});
diff --git a/src/server/api/admin/userconfig/index.post.ts b/src/server/api/admin/userconfig/index.post.ts
deleted file mode 100644
index a6146db8..00000000
--- a/src/server/api/admin/userconfig/index.post.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export default defineEventHandler(async (event) => {
- const data = await readValidatedBody(
- event,
- validateZod(userConfigUpdateType, event)
- );
- await Database.system.updateUserConfig(data);
- await WireGuard.saveConfig();
- return { success: true };
-});
diff --git a/src/server/api/setup/4.post.ts b/src/server/api/setup/4.post.ts
index fd7f29c0..0dd73670 100644
--- a/src/server/api/setup/4.post.ts
+++ b/src/server/api/setup/4.post.ts
@@ -1,14 +1,6 @@
import { UserSetupType } from '#db/repositories/user/types';
-export default defineEventHandler(async (event) => {
- const { done } = await Database.general.getSetupStep();
- if (done) {
- throw createError({
- statusCode: 400,
- statusMessage: 'Invalid state',
- });
- }
-
+export default defineSetupEventHandler(async ({ event }) => {
const { username, password } = await readValidatedBody(
event,
validateZod(UserSetupType, event)
diff --git a/src/server/api/setup/5.post.ts b/src/server/api/setup/5.post.ts
index 514c4166..1e0b36d6 100644
--- a/src/server/api/setup/5.post.ts
+++ b/src/server/api/setup/5.post.ts
@@ -1,14 +1,6 @@
import { UserConfigSetupType } from '#db/repositories/userConfig/types';
-export default defineEventHandler(async (event) => {
- const { done } = await Database.general.getSetupStep();
- if (done) {
- throw createError({
- statusCode: 400,
- statusMessage: 'Invalid state',
- });
- }
-
+export default defineSetupEventHandler(async ({ event }) => {
const { host, port } = await readValidatedBody(
event,
validateZod(UserConfigSetupType, event)
diff --git a/src/server/api/setup/migrate.post.ts b/src/server/api/setup/migrate.post.ts
index b4933de7..d5256f9c 100644
--- a/src/server/api/setup/migrate.post.ts
+++ b/src/server/api/setup/migrate.post.ts
@@ -2,14 +2,7 @@
import { stringifyIp } from 'ip-bigint';
import { z } from 'zod';*/
-export default defineEventHandler(async (/*event*/) => {
- const { done } = await Database.general.getSetupStep();
- if (done) {
- throw createError({
- statusCode: 400,
- statusMessage: 'Invalid state',
- });
- }
+export default defineSetupEventHandler(async (/*{ event }*/) => {
// TODO: Implement
/*
diff --git a/src/server/database/repositories/client/service.ts b/src/server/database/repositories/client/service.ts
index 7c8971e1..942b220e 100644
--- a/src/server/database/repositories/client/service.ts
+++ b/src/server/database/repositories/client/service.ts
@@ -2,8 +2,8 @@ import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { client } from './schema';
import type { ClientCreateType, UpdateClientType } from './types';
-import type { ID } from '../../schema';
-import { wgInterface, userConfig } from '../../schema';
+import type { ID } from '#db/schema';
+import { wgInterface, userConfig } from '#db/schema';
import { parseCidr } from 'cidr-tools';
function createPreparedStatement(db: DBType) {
diff --git a/src/server/database/repositories/client/types.ts b/src/server/database/repositories/client/types.ts
index 5b730d9f..68663209 100644
--- a/src/server/database/repositories/client/types.ts
+++ b/src/server/database/repositories/client/types.ts
@@ -3,13 +3,6 @@ import z from 'zod';
import type { client } from './schema';
-const schemaForType =
-
() =>
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- >(arg: S) => {
- return arg;
- };
-
export type ID = string;
export type ClientType = InferSelectModel;
@@ -35,11 +28,6 @@ const expiresAt = z
.pipe(safeStringRefine)
.nullable();
-const address = z
- .string({ message: 'zod.client.address' })
- .min(1, { message: 'zod.client.addressMin' })
- .pipe(safeStringRefine);
-
const address4 = z
.string({ message: 'zod.client.address4' })
.min(1, { message: 'zod.client.address4Min' })
@@ -50,30 +38,10 @@ const address6 = z
.min(1, { message: 'zod.client.address6Min' })
.pipe(safeStringRefine);
-const allowedIps = z
- .array(address, { message: 'zod.client.allowedIps' })
- .min(1, { message: 'zod.client.allowedIpsMin' });
-
-const serverAllowedIps = z.array(address, {
+const serverAllowedIps = z.array(AddressSchema, {
message: 'zod.serverAllowedIps',
});
-const mtu = z
- .number({ message: 'zod.client.mtu' })
- .min(1280, { message: 'zod.client.mtuMin' })
- .max(9000, { message: 'zod.client.mtuMax' });
-
-const persistentKeepalive = z
- .number({ message: 'zod.client.persistentKeepalive' })
- .min(0, 'zod.client.persistentKeepaliveMin')
- .max(65535, 'zod.client.persistentKeepaliveMax');
-
-const enabled = z.boolean({ message: 'zod.enabled' });
-
-const dns = z
- .array(address, { message: 'zod.client.dns' })
- .min(1, 'zod.client.dnsMin');
-
export const ClientCreateSchema = z.object({
name: name,
expiresAt: expiresAt,
@@ -84,15 +52,15 @@ export type ClientCreateType = z.infer;
export const ClientUpdateSchema = schemaForType()(
z.object({
name: name,
- enabled: enabled,
+ enabled: EnabledSchema,
expiresAt: expiresAt,
ipv4Address: address4,
ipv6Address: address6,
- allowedIps: allowedIps,
+ allowedIps: AllowedIpsSchema,
serverAllowedIps: serverAllowedIps,
- mtu: mtu,
- persistentKeepalive: persistentKeepalive,
- dns: dns,
+ mtu: MtuSchema,
+ persistentKeepalive: PersistentKeepaliveSchema,
+ dns: DnsSchema,
})
);
diff --git a/src/server/database/repositories/general/types.ts b/src/server/database/repositories/general/types.ts
index 9e4bb4a3..05944528 100644
--- a/src/server/database/repositories/general/types.ts
+++ b/src/server/database/repositories/general/types.ts
@@ -11,3 +11,5 @@ export const GeneralUpdateSchema = z.object({
});
export type GeneralUpdateType = z.infer;
+
+export type SetupStepType = { step: number; done: boolean };
diff --git a/src/server/database/repositories/hooks/service.ts b/src/server/database/repositories/hooks/service.ts
index 1384070a..64d6c27c 100644
--- a/src/server/database/repositories/hooks/service.ts
+++ b/src/server/database/repositories/hooks/service.ts
@@ -1,6 +1,7 @@
import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { hooks } from './schema';
+import type { HooksUpdateType } from './types';
function createPreparedStatement(db: DBType) {
return {
@@ -11,13 +12,23 @@ function createPreparedStatement(db: DBType) {
}
export class HooksService {
+ #db: DBType;
#statements: ReturnType;
constructor(db: DBType) {
+ this.#db = db;
this.#statements = createPreparedStatement(db);
}
- get(wgInterface: string) {
- return this.#statements.get.execute({ interface: wgInterface });
+ get(infName: string) {
+ return this.#statements.get.execute({ interface: infName });
+ }
+
+ update(infName: string, data: HooksUpdateType) {
+ return this.#db
+ .update(hooks)
+ .set(data)
+ .where(eq(hooks.id, infName))
+ .execute();
}
}
diff --git a/src/server/database/repositories/hooks/types.ts b/src/server/database/repositories/hooks/types.ts
index 3372c504..3e76d045 100644
--- a/src/server/database/repositories/hooks/types.ts
+++ b/src/server/database/repositories/hooks/types.ts
@@ -1,4 +1,18 @@
import type { InferSelectModel } from 'drizzle-orm';
import type { hooks } from './schema';
+import z from 'zod';
export type HooksType = InferSelectModel;
+
+export type HooksUpdateType = Omit;
+
+const hook = z.string({ message: 'zod.hook' }).pipe(safeStringRefine);
+
+export const HooksUpdateSchema = schemaForType()(
+ z.object({
+ preUp: hook,
+ postUp: hook,
+ preDown: hook,
+ postDown: hook,
+ })
+);
diff --git a/src/server/database/repositories/interface/service.ts b/src/server/database/repositories/interface/service.ts
index 7fa3e313..7361f617 100644
--- a/src/server/database/repositories/interface/service.ts
+++ b/src/server/database/repositories/interface/service.ts
@@ -1,6 +1,10 @@
import type { DBType } from '#db/sqlite';
+import isCidr from 'is-cidr';
import { eq, sql } from 'drizzle-orm';
import { wgInterface } from './schema';
+import type { InterfaceCidrUpdateType, InterfaceUpdateType } from './types';
+import { client as clientSchema } from '#db/schema';
+import { parseCidr } from 'cidr-tools';
function createPreparedStatement(db: DBType) {
return {
@@ -20,9 +24,11 @@ function createPreparedStatement(db: DBType) {
}
export class InterfaceService {
+ #db: DBType;
#statements: ReturnType;
constructor(db: DBType) {
+ this.#db = db;
this.#statements = createPreparedStatement(db);
}
@@ -41,4 +47,44 @@ export class InterfaceService {
publicKey,
});
}
+
+ update(infName: string, data: InterfaceUpdateType) {
+ return this.#db
+ .update(wgInterface)
+ .set(data)
+ .where(eq(wgInterface.name, infName))
+ .execute();
+ }
+
+ updateCidr(infName: string, data: InterfaceCidrUpdateType) {
+ if (!isCidr(data.ipv4Cidr) || !isCidr(data.ipv6Cidr)) {
+ throw new Error('Invalid CIDR');
+ }
+ return this.#db.transaction(async (tx) => {
+ await tx
+ .update(wgInterface)
+ .set(data)
+ .where(eq(wgInterface.name, infName))
+ .execute();
+
+ const clients = await tx.query.client.findMany().execute();
+
+ for (const client of clients) {
+ // TODO: optimize
+ const clients = await tx.query.client.findMany().execute();
+
+ const nextIpv4 = nextIP(4, parseCidr(data.ipv4Cidr), clients);
+ const nextIpv6 = nextIP(6, parseCidr(data.ipv6Cidr), clients);
+
+ await tx
+ .update(clientSchema)
+ .set({
+ ipv4Address: nextIpv4,
+ ipv6Address: nextIpv6,
+ })
+ .where(eq(clientSchema.id, client.id))
+ .execute();
+ }
+ });
+ }
}
diff --git a/src/server/database/repositories/interface/types.ts b/src/server/database/repositories/interface/types.ts
index ef6720d6..a003cc62 100644
--- a/src/server/database/repositories/interface/types.ts
+++ b/src/server/database/repositories/interface/types.ts
@@ -1,4 +1,49 @@
import type { InferSelectModel } from 'drizzle-orm';
import type { wgInterface } from './schema';
+import z from 'zod';
export type InterfaceType = InferSelectModel;
+
+export type InterfaceCreateType = Omit<
+ InterfaceType,
+ 'createdAt' | 'updatedAt'
+>;
+
+export type InterfaceUpdateType = Omit<
+ InterfaceCreateType,
+ 'name' | 'createdAt' | 'updatedAt' | 'privateKey' | 'publicKey'
+>;
+
+const device = z
+ .string({ message: 'zod.device' })
+ .min(1, 'zod.deviceMin')
+ .pipe(safeStringRefine);
+
+const cidr = z
+ .string({ message: 'zod.interface.cidr' })
+ .min(1, { message: 'zod.interface.cidrMin' })
+ .pipe(safeStringRefine);
+
+export const InterfaceUpdateSchema = schemaForType()(
+ z.object({
+ ipv4Cidr: cidr,
+ ipv6Cidr: cidr,
+ mtu: MtuSchema,
+ port: PortSchema,
+ device: device,
+ enabled: EnabledSchema,
+ })
+);
+
+export type InterfaceCidrUpdateType = {
+ ipv4Cidr: string;
+ ipv6Cidr: string;
+};
+
+export const InterfaceCidrUpdateSchema =
+ schemaForType()(
+ z.object({
+ ipv4Cidr: cidr,
+ ipv6Cidr: cidr,
+ })
+ );
diff --git a/src/server/database/repositories/userConfig/service.ts b/src/server/database/repositories/userConfig/service.ts
index bbf9e5ec..42bdb99b 100644
--- a/src/server/database/repositories/userConfig/service.ts
+++ b/src/server/database/repositories/userConfig/service.ts
@@ -1,6 +1,7 @@
import type { DBType } from '#db/sqlite';
import { eq, sql } from 'drizzle-orm';
import { userConfig } from './schema';
+import type { UserConfigUpdateType } from './types';
function createPreparedStatement(db: DBType) {
return {
@@ -19,21 +20,31 @@ function createPreparedStatement(db: DBType) {
}
export class UserConfigService {
+ #db: DBType;
#statements: ReturnType;
constructor(db: DBType) {
+ this.#db = db;
this.#statements = createPreparedStatement(db);
}
- async get(wgInterface: string) {
- return await this.#statements.get.execute({ interface: wgInterface });
+ get(infName: string) {
+ return this.#statements.get.execute({ interface: infName });
}
- async updateHostPort(wgInterface: string, host: string, port: number) {
- return await this.#statements.updateHostPort.execute({
- interface: wgInterface,
+ updateHostPort(infName: string, host: string, port: number) {
+ return this.#statements.updateHostPort.execute({
+ interface: infName,
host,
port,
});
}
+
+ update(infName: string, data: UserConfigUpdateType) {
+ return this.#db
+ .update(userConfig)
+ .set(data)
+ .where(eq(userConfig.id, infName))
+ .execute();
+ }
}
diff --git a/src/server/database/repositories/userConfig/types.ts b/src/server/database/repositories/userConfig/types.ts
index 1358bf8c..165f3f27 100644
--- a/src/server/database/repositories/userConfig/types.ts
+++ b/src/server/database/repositories/userConfig/types.ts
@@ -9,12 +9,23 @@ const host = z
.min(1, 'zod.userConfig.hostMin')
.pipe(safeStringRefine);
-const port = z
- .number({ message: 'zod.userConfig.port' })
- .min(1, 'zod.userConfig.portMin')
- .max(65535, 'zod.userConfig.portMax');
-
export const UserConfigSetupType = z.object({
host: host,
- port: port,
+ port: PortSchema,
});
+
+export type UserConfigUpdateType = Omit<
+ UserConfigType,
+ 'id' | 'createdAt' | 'updatedAt'
+>;
+
+export const UserConfigUpdateSchema = schemaForType()(
+ z.object({
+ port: PortSchema,
+ defaultMtu: MtuSchema,
+ defaultPersistentKeepalive: PersistentKeepaliveSchema,
+ defaultDns: DnsSchema,
+ defaultAllowedIps: AllowedIpsSchema,
+ host: host,
+ })
+);
diff --git a/src/server/database/schema.ts b/src/server/database/schema.ts
index db3e45a7..73edce69 100644
--- a/src/server/database/schema.ts
+++ b/src/server/database/schema.ts
@@ -8,4 +8,5 @@ export * from './repositories/oneTimeLink/schema';
export * from './repositories/user/schema';
export * from './repositories/userConfig/schema';
+// TODO: move to types
export type ID = number;
diff --git a/src/server/utils/handler.ts b/src/server/utils/handler.ts
index 738acc4a..754bb8d3 100644
--- a/src/server/utils/handler.ts
+++ b/src/server/utils/handler.ts
@@ -1,11 +1,15 @@
import type { EventHandlerRequest, EventHandlerResponse, H3Event } from 'h3';
import type { UserType } from '#db/repositories/user/types';
+import type { SetupStepType } from '../database/repositories/general/types';
type PermissionHandler<
TReq extends EventHandlerRequest,
TRes extends EventHandlerResponse,
> = { (params: { event: H3Event; user: UserType }): TRes };
+/**
+ * check if the user has the permission to perform the action
+ */
export const definePermissionEventHandler = <
TReq extends EventHandlerRequest,
TRes extends EventHandlerResponse,
@@ -25,3 +29,31 @@ export const definePermissionEventHandler = <
return await handler({ event, user });
});
};
+
+type SetupHandler<
+ TReq extends EventHandlerRequest,
+ TRes extends EventHandlerResponse,
+> = { (params: { event: H3Event; setup: SetupStepType }): TRes };
+
+/**
+ * check if the setup is done, if not, run the handler
+ */
+export const defineSetupEventHandler = <
+ TReq extends EventHandlerRequest,
+ TRes extends EventHandlerResponse,
+>(
+ handler: SetupHandler
+) => {
+ return defineEventHandler(async (event) => {
+ const setup = await Database.general.getSetupStep();
+
+ if (setup.done) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'Invalid state',
+ });
+ }
+
+ return await handler({ event, setup });
+ });
+};
diff --git a/src/server/utils/types.ts b/src/server/utils/types.ts
index 1515cfe1..41607b52 100644
--- a/src/server/utils/types.ts
+++ b/src/server/utils/types.ts
@@ -13,6 +13,43 @@ export const safeStringRefine = z
// TODO: create custom getValidatedRouterParams and readValidatedBody wrapper
+export const EnabledSchema = z.boolean({ message: 'zod.enabled' });
+
+export const MtuSchema = z
+ .number({ message: 'zod.mtu' })
+ .min(1280, { message: 'zod.mtuMin' })
+ .max(9000, { message: 'zod.mtuMax' });
+
+export const PortSchema = z
+ .number({ message: 'zod.port' })
+ .min(1, { message: 'zod.portMin' })
+ .max(65535, { message: 'zod.portMax' });
+
+export const PersistentKeepaliveSchema = z
+ .number({ message: 'zod.persistentKeepalive' })
+ .min(0, 'zod.persistentKeepaliveMin')
+ .max(65535, 'zod.persistentKeepaliveMax');
+
+export const AddressSchema = z
+ .string({ message: 'zod.address' })
+ .min(1, { message: 'zod.addressMin' })
+ .pipe(safeStringRefine);
+
+export const DnsSchema = z
+ .array(AddressSchema, { message: 'zod.dns' })
+ .min(1, 'zod.dnsMin');
+
+export const AllowedIpsSchema = z
+ .array(AddressSchema, { message: 'zod.allowedIps' })
+ .min(1, { message: 'zod.allowedIpsMin' });
+
+export const schemaForType =
+ () =>
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ >(arg: S) => {
+ return arg;
+ };
+
export function validateZod(
schema: ZodSchema,
event?: H3Event
diff --git a/src/shared/utils/permissions.ts b/src/shared/utils/permissions.ts
index f83ed6f6..3b7c5762 100644
--- a/src/shared/utils/permissions.ts
+++ b/src/shared/utils/permissions.ts
@@ -1,4 +1,4 @@
-// TODO: will need to be updated when we have more roles and actions
+// TODO: implement ABAC
export const actions = {
ADMIN: 'ADMIN',