mirror of https://github.com/wg-easy/wg-easy
Browse Source
* start drizzle migration * split schema * improve schema * improve schema, cascade, unique * improve structure, start migration * migrate to sqlite * work in prod docker * start adding a better permission handler * permission matrix, permission handler * update packages * move session timeout to session config, use new permission handler * improve docker dev only install dependencies if changed * implement setup * migrate to sqlite * improve debug, fix custom migration * migrate to sqlite * regenerate migrations * ignore autogenerated migrations from prettier * migrate to sqlite * migrate to sqlite * Migrate to sqlite * fix prod error * move nuxt middleware from server to nuxt * update corepack in prod dockerfile * use correct branch for workflow * make docker file build on armv6/v7 * fix client update * update zod locales * cancel pr workflow if new commit * test concurrencypull/1648/head
committed by
Bernd Storath
117 changed files with 5437 additions and 3096 deletions
@ -0,0 +1 @@ |
|||
public-hoist-pattern[]=@libsql/linux* |
@ -1 +1,2 @@ |
|||
pnpm-lock.yaml |
|||
server/database/migrations/meta |
|||
|
@ -0,0 +1,28 @@ |
|||
export default defineNuxtRouteMiddleware(async (to) => { |
|||
// api & setup handled server side
|
|||
if (to.path.startsWith('/api/') || to.path.startsWith('/setup')) { |
|||
return; |
|||
} |
|||
|
|||
const authStore = useAuthStore(); |
|||
const userData = await authStore.getSession(); |
|||
|
|||
// skip login if already logged in
|
|||
if (to.path === '/login') { |
|||
if (userData?.username) { |
|||
return navigateTo('/', { redirectCode: 302 }); |
|||
} |
|||
return; |
|||
} |
|||
// Require auth for every page other than Login
|
|||
if (!userData?.username) { |
|||
return navigateTo('/login', { redirectCode: 302 }); |
|||
} |
|||
|
|||
// Check for admin access
|
|||
if (to.path.startsWith('/admin')) { |
|||
if (userData.role !== roles.ADMIN) { |
|||
return abortNavigation('Not allowed to access Admin Panel'); |
|||
} |
|||
} |
|||
}); |
@ -0,0 +1,10 @@ |
|||
import { defineConfig } from 'drizzle-kit'; |
|||
|
|||
export default defineConfig({ |
|||
out: './server/database/migrations', |
|||
schema: './server/database/schema.ts', |
|||
dialect: 'sqlite', |
|||
dbCredentials: { |
|||
url: 'file:./wg0.db', |
|||
}, |
|||
}); |
File diff suppressed because it is too large
@ -1,4 +1,6 @@ |
|||
export default defineEventHandler(async () => { |
|||
const system = await Database.system.get(); |
|||
return system.general; |
|||
export default definePermissionEventHandler(actions.ADMIN, async () => { |
|||
const sessionConfig = await Database.general.getSessionConfig(); |
|||
return { |
|||
sessionTimeout: sessionConfig.sessionTimeout, |
|||
}; |
|||
}); |
|||
|
@ -1,8 +1,13 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { GeneralUpdateSchema } from '#db/repositories/general/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.ADMIN, |
|||
async ({ event }) => { |
|||
const data = await readValidatedBody( |
|||
event, |
|||
validateZod(generalUpdateType, event) |
|||
validateZod(GeneralUpdateSchema, event) |
|||
); |
|||
await Database.system.updateGeneral(data); |
|||
await Database.general.update(data); |
|||
return { success: true }; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,4 +1,7 @@ |
|||
export default defineEventHandler(async () => { |
|||
const system = await Database.system.get(); |
|||
return system.hooks; |
|||
export default definePermissionEventHandler(actions.ADMIN, async () => { |
|||
const hooks = await Database.hooks.get('wg0'); |
|||
if (!hooks) { |
|||
throw new Error('Hooks not found'); |
|||
} |
|||
return hooks; |
|||
}); |
|||
|
@ -1,9 +1,14 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { HooksUpdateSchema } from '#db/repositories/hooks/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.ADMIN, |
|||
async ({ event }) => { |
|||
const data = await readValidatedBody( |
|||
event, |
|||
validateZod(hooksUpdateType, event) |
|||
validateZod(HooksUpdateSchema, event) |
|||
); |
|||
await Database.system.updateHooks(data); |
|||
await Database.hooks.update('wg0', data); |
|||
await WireGuard.saveConfig(); |
|||
return { success: true }; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,8 +0,0 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const { host, port } = await readValidatedBody( |
|||
event, |
|||
validateZod(hostPortType, event) |
|||
); |
|||
await Database.system.updateClientsHostPort(host, port); |
|||
return { success: true }; |
|||
}); |
@ -1,4 +0,0 @@ |
|||
export default defineEventHandler(async () => { |
|||
const system = await Database.system.get(); |
|||
return system.interface; |
|||
}); |
@ -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 }; |
|||
}); |
@ -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 }; |
|||
} |
|||
); |
@ -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, |
|||
}; |
|||
}); |
@ -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 }; |
|||
} |
|||
); |
@ -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; |
|||
}); |
@ -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 }; |
|||
} |
|||
); |
@ -1,9 +0,0 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const data = await readValidatedBody( |
|||
event, |
|||
validateZod(cidrUpdateType, event) |
|||
); |
|||
|
|||
await WireGuard.updateAddressRange(data); |
|||
return { success: true }; |
|||
}); |
@ -1,4 +0,0 @@ |
|||
export default defineEventHandler(async () => { |
|||
const system = await Database.system.get(); |
|||
return system.userConfig; |
|||
}); |
@ -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 }; |
|||
}); |
@ -1,8 +1,14 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { ClientGetSchema } from '#db/repositories/client/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.CLIENT, |
|||
async ({ event }) => { |
|||
const { clientId } = await getValidatedRouterParams( |
|||
event, |
|||
validateZod(clientIdType) |
|||
validateZod(ClientGetSchema) |
|||
); |
|||
await WireGuard.disableClient({ clientId }); |
|||
await Database.clients.toggle(clientId, false); |
|||
await WireGuard.saveConfig(); |
|||
return { success: true }; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,8 +1,14 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { ClientGetSchema } from '#db/repositories/client/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.CLIENT, |
|||
async ({ event }) => { |
|||
const { clientId } = await getValidatedRouterParams( |
|||
event, |
|||
validateZod(clientIdType) |
|||
validateZod(ClientGetSchema) |
|||
); |
|||
await WireGuard.enableClient({ clientId }); |
|||
await Database.clients.toggle(clientId, false); |
|||
await WireGuard.saveConfig(); |
|||
return { success: true }; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,8 +1,13 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { ClientGetSchema } from '#db/repositories/client/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.CLIENT, |
|||
async ({ event }) => { |
|||
const { clientId } = await getValidatedRouterParams( |
|||
event, |
|||
validateZod(clientIdType) |
|||
validateZod(ClientGetSchema) |
|||
); |
|||
await WireGuard.generateOneTimeLink({ clientId }); |
|||
await Database.oneTimeLinks.generate(clientId); |
|||
return { success: true }; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,8 +1,14 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { ClientGetSchema } from '#db/repositories/client/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.CLIENT, |
|||
async ({ event }) => { |
|||
const { clientId } = await getValidatedRouterParams( |
|||
event, |
|||
validateZod(clientIdType) |
|||
validateZod(ClientGetSchema) |
|||
); |
|||
await WireGuard.deleteClient({ clientId }); |
|||
await Database.clients.delete(clientId); |
|||
await WireGuard.saveConfig(); |
|||
return { success: true }; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,7 +1,19 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { ClientGetSchema } from '~~/server/database/repositories/client/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.CLIENT, |
|||
async ({ event }) => { |
|||
const { clientId } = await getValidatedRouterParams( |
|||
event, |
|||
validateZod(clientIdType) |
|||
validateZod(ClientGetSchema, event) |
|||
); |
|||
return WireGuard.getClient({ clientId }); |
|||
}); |
|||
const result = await Database.clients.get(clientId); |
|||
if (!result) { |
|||
throw createError({ |
|||
statusCode: 404, |
|||
statusMessage: 'Client not found', |
|||
}); |
|||
} |
|||
return result; |
|||
} |
|||
); |
|||
|
@ -1,18 +1,24 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { |
|||
ClientGetSchema, |
|||
ClientUpdateSchema, |
|||
} from '#db/repositories/client/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.CLIENT, |
|||
async ({ event }) => { |
|||
const { clientId } = await getValidatedRouterParams( |
|||
event, |
|||
validateZod(clientIdType) |
|||
validateZod(ClientGetSchema) |
|||
); |
|||
|
|||
const data = await readValidatedBody( |
|||
event, |
|||
validateZod(clientUpdateType, event) |
|||
validateZod(ClientUpdateSchema, event) |
|||
); |
|||
|
|||
await WireGuard.updateClient({ |
|||
clientId, |
|||
client: data, |
|||
}); |
|||
await Database.clients.update(clientId, data); |
|||
await WireGuard.saveConfig(); |
|||
|
|||
return { success: true }; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,9 +1,14 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
import { ClientGetSchema } from '#db/repositories/client/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.CLIENT, |
|||
async ({ event }) => { |
|||
const { clientId } = await getValidatedRouterParams( |
|||
event, |
|||
validateZod(clientIdType) |
|||
validateZod(ClientGetSchema) |
|||
); |
|||
const svg = await WireGuard.getClientQRCodeSVG({ clientId }); |
|||
setHeader(event, 'Content-Type', 'image/svg+xml'); |
|||
return svg; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,3 +1,3 @@ |
|||
export default defineEventHandler(() => { |
|||
export default definePermissionEventHandler(actions.CLIENT, () => { |
|||
return WireGuard.getClients(); |
|||
}); |
|||
|
@ -1,8 +1,14 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const { name, expireDate } = await readValidatedBody( |
|||
import { ClientCreateSchema } from '#db/repositories/client/types'; |
|||
|
|||
export default definePermissionEventHandler( |
|||
actions.CLIENT, |
|||
async ({ event }) => { |
|||
const { name, expiresAt } = await readValidatedBody( |
|||
event, |
|||
validateZod(createType) |
|||
validateZod(ClientCreateSchema) |
|||
); |
|||
await WireGuard.createClient({ name, expireDate }); |
|||
await Database.clients.create({ name, expiresAt }); |
|||
await WireGuard.saveConfig(); |
|||
return { success: true }; |
|||
}); |
|||
} |
|||
); |
|||
|
@ -1,17 +1,12 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const setupDone = await Database.setup.done(); |
|||
if (setupDone) { |
|||
throw createError({ |
|||
statusCode: 400, |
|||
statusMessage: 'Invalid state', |
|||
}); |
|||
} |
|||
import { UserSetupType } from '#db/repositories/user/types'; |
|||
|
|||
export default defineSetupEventHandler(async ({ event }) => { |
|||
const { username, password } = await readValidatedBody( |
|||
event, |
|||
validateZod(passwordSetupType, event) |
|||
validateZod(UserSetupType, event) |
|||
); |
|||
await Database.user.create(username, password); |
|||
await Database.setup.set(5); |
|||
|
|||
await Database.users.create(username, password); |
|||
await Database.general.setSetupStep(5); |
|||
return { success: true }; |
|||
}); |
|||
|
@ -1,17 +1,11 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const setupDone = await Database.setup.done(); |
|||
if (setupDone) { |
|||
throw createError({ |
|||
statusCode: 400, |
|||
statusMessage: 'Invalid state', |
|||
}); |
|||
} |
|||
import { UserConfigSetupType } from '#db/repositories/userConfig/types'; |
|||
|
|||
export default defineSetupEventHandler(async ({ event }) => { |
|||
const { host, port } = await readValidatedBody( |
|||
event, |
|||
validateZod(hostPortType, event) |
|||
validateZod(UserConfigSetupType, event) |
|||
); |
|||
await Database.system.updateClientsHostPort(host, port); |
|||
await Database.setup.set('success'); |
|||
await Database.userConfigs.updateHostPort('wg0', host, port); |
|||
await Database.general.setSetupStep(0); |
|||
return { success: true }; |
|||
}); |
|||
|
@ -1,6 +1,9 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const config = await WireGuard.backupConfiguration(); |
|||
export default definePermissionEventHandler( |
|||
actions.ADMIN, |
|||
async (/*{ event }*/) => { |
|||
/*const config = await WireGuard.backupConfiguration(); |
|||
setHeader(event, 'Content-Disposition', 'attachment; filename="wg0.json"'); |
|||
setHeader(event, 'Content-Type', 'text/json'); |
|||
return config; |
|||
}); |
|||
return config;*/ |
|||
} |
|||
); |
|||
|
@ -1,5 +1,8 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const { file } = await readValidatedBody(event, validateZod(fileType)); |
|||
export default definePermissionEventHandler( |
|||
actions.ADMIN, |
|||
async (/*{ event }*/) => { |
|||
/*const { file } = await readValidatedBody(event, validateZod(fileType)); |
|||
await WireGuard.restoreConfiguration(file); |
|||
return { success: true }; |
|||
}); |
|||
return { success: true };*/ |
|||
} |
|||
); |
|||
|
@ -0,0 +1,100 @@ |
|||
CREATE TABLE `clients_table` ( |
|||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, |
|||
`name` text NOT NULL, |
|||
`ipv4_address` text NOT NULL, |
|||
`ipv6_address` text NOT NULL, |
|||
`private_key` text NOT NULL, |
|||
`public_key` text NOT NULL, |
|||
`pre_shared_key` text NOT NULL, |
|||
`expires_at` text, |
|||
`allowed_ips` text NOT NULL, |
|||
`server_allowed_ips` text NOT NULL, |
|||
`persistent_keepalive` integer NOT NULL, |
|||
`mtu` integer NOT NULL, |
|||
`dns` text NOT NULL, |
|||
`enabled` integer NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE UNIQUE INDEX `clients_table_ipv4_address_unique` ON `clients_table` (`ipv4_address`);--> statement-breakpoint |
|||
CREATE UNIQUE INDEX `clients_table_ipv6_address_unique` ON `clients_table` (`ipv6_address`);--> statement-breakpoint |
|||
CREATE TABLE `general_table` ( |
|||
`id` integer PRIMARY KEY DEFAULT 1 NOT NULL, |
|||
`setupStep` integer NOT NULL, |
|||
`session_password` text NOT NULL, |
|||
`session_timeout` integer NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE TABLE `hooks_table` ( |
|||
`id` text PRIMARY KEY NOT NULL, |
|||
`pre_up` text NOT NULL, |
|||
`post_up` text NOT NULL, |
|||
`pre_down` text NOT NULL, |
|||
`post_down` text NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
FOREIGN KEY (`id`) REFERENCES `interfaces_table`(`name`) ON UPDATE cascade ON DELETE cascade |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE TABLE `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, |
|||
`enabled` integer NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE UNIQUE INDEX `interfaces_table_port_unique` ON `interfaces_table` (`port`);--> statement-breakpoint |
|||
CREATE TABLE `prometheus_table` ( |
|||
`id` text PRIMARY KEY NOT NULL, |
|||
`password` text NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
FOREIGN KEY (`id`) REFERENCES `interfaces_table`(`name`) ON UPDATE cascade ON DELETE cascade |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE TABLE `one_time_links_table` ( |
|||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, |
|||
`one_time_link` text NOT NULL, |
|||
`expires_at` text NOT NULL, |
|||
`clientId` integer NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
FOREIGN KEY (`clientId`) REFERENCES `clients_table`(`id`) ON UPDATE cascade ON DELETE cascade |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE UNIQUE INDEX `one_time_links_table_one_time_link_unique` ON `one_time_links_table` (`one_time_link`);--> statement-breakpoint |
|||
CREATE TABLE `users_table` ( |
|||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, |
|||
`username` text NOT NULL, |
|||
`password` text NOT NULL, |
|||
`email` text, |
|||
`name` text NOT NULL, |
|||
`role` integer NOT NULL, |
|||
`enabled` integer NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL |
|||
); |
|||
--> statement-breakpoint |
|||
CREATE UNIQUE INDEX `users_table_username_unique` ON `users_table` (`username`);--> statement-breakpoint |
|||
CREATE TABLE `user_configs_table` ( |
|||
`id` text PRIMARY KEY NOT NULL, |
|||
`default_mtu` integer NOT NULL, |
|||
`default_persistent_keepalive` integer NOT NULL, |
|||
`default_dns` text NOT NULL, |
|||
`default_allowed_ips` text NOT NULL, |
|||
`host` text NOT NULL, |
|||
`port` integer NOT NULL, |
|||
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
|||
FOREIGN KEY (`id`) REFERENCES `interfaces_table`(`name`) ON UPDATE cascade ON DELETE cascade |
|||
); |
@ -0,0 +1,18 @@ |
|||
PRAGMA journal_mode=WAL;--> statement-breakpoint |
|||
INSERT INTO `general_table` (`setupStep`, `session_password`, `session_timeout`) |
|||
VALUES (1, hex(randomblob(256)), 3600); |
|||
--> statement-breakpoint |
|||
INSERT INTO `interfaces_table` (`name`, `device`, `port`, `private_key`, `public_key`, `ipv4_cidr`, `ipv6_cidr`, `mtu`, `enabled`) |
|||
VALUES ('wg0', 'eth0', 51820, '---default---', '---default---', '10.8.0.0/24', 'fdcc:ad94:bacf:61a4::cafe:0/112', 1420, 1); |
|||
--> statement-breakpoint |
|||
INSERT INTO `hooks_table` (`id`, `pre_up`, `post_up`, `pre_down`, `post_down`) |
|||
VALUES ( |
|||
'wg0', |
|||
'', |
|||
'iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE; ip6tables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -j ACCEPT;', |
|||
'', |
|||
'iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -s {{ipv6Cidr}} -o {{device}} -j MASQUERADE; ip6tables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -j ACCEPT;' |
|||
); |
|||
--> statement-breakpoint |
|||
INSERT INTO `user_configs_table` (`id`, `default_mtu`, `default_persistent_keepalive`, `default_dns`, `default_allowed_ips`, `host`, `port`) |
|||
VALUES ('wg0', 1420, 0, '["1.1.1.1","2606:4700:4700::1111"]', '["0.0.0.0/0","::/0"]', '', 51820) |
@ -0,0 +1,686 @@ |
|||
{ |
|||
"version": "6", |
|||
"dialect": "sqlite", |
|||
"id": "25907c5f-be21-4ae6-88c4-1a72b2f335e7", |
|||
"prevId": "00000000-0000-0000-0000-000000000000", |
|||
"tables": { |
|||
"clients_table": { |
|||
"name": "clients_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": true |
|||
}, |
|||
"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 |
|||
}, |
|||
"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": true, |
|||
"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 |
|||
}, |
|||
"dns": { |
|||
"name": "dns", |
|||
"type": "text", |
|||
"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": { |
|||
"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": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"general_table": { |
|||
"name": "general_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": 1 |
|||
}, |
|||
"setupStep": { |
|||
"name": "setupStep", |
|||
"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 |
|||
}, |
|||
"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 |
|||
}, |
|||
"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": {} |
|||
}, |
|||
"prometheus_table": { |
|||
"name": "prometheus_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"password": { |
|||
"name": "password", |
|||
"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": { |
|||
"prometheus_table_id_interfaces_table_name_fk": { |
|||
"name": "prometheus_table_id_interfaces_table_name_fk", |
|||
"tableFrom": "prometheus_table", |
|||
"tableTo": "interfaces_table", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onDelete": "cascade", |
|||
"onUpdate": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"one_time_links_table": { |
|||
"name": "one_time_links_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": true |
|||
}, |
|||
"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 |
|||
}, |
|||
"clientId": { |
|||
"name": "clientId", |
|||
"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": { |
|||
"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_clientId_clients_table_id_fk": { |
|||
"name": "one_time_links_table_clientId_clients_table_id_fk", |
|||
"tableFrom": "one_time_links_table", |
|||
"tableTo": "clients_table", |
|||
"columnsFrom": [ |
|||
"clientId" |
|||
], |
|||
"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 |
|||
}, |
|||
"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 |
|||
}, |
|||
"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,686 @@ |
|||
{ |
|||
"id": "60af732f-adc0-405d-96cc-2f818585f593", |
|||
"prevId": "25907c5f-be21-4ae6-88c4-1a72b2f335e7", |
|||
"version": "6", |
|||
"dialect": "sqlite", |
|||
"tables": { |
|||
"clients_table": { |
|||
"name": "clients_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": true |
|||
}, |
|||
"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 |
|||
}, |
|||
"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": true, |
|||
"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 |
|||
}, |
|||
"dns": { |
|||
"name": "dns", |
|||
"type": "text", |
|||
"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": { |
|||
"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": {}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"general_table": { |
|||
"name": "general_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false, |
|||
"default": 1 |
|||
}, |
|||
"setupStep": { |
|||
"name": "setupStep", |
|||
"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 |
|||
}, |
|||
"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", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"tableTo": "interfaces_table", |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onUpdate": "cascade", |
|||
"onDelete": "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 |
|||
}, |
|||
"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": {} |
|||
}, |
|||
"prometheus_table": { |
|||
"name": "prometheus_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "text", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": false |
|||
}, |
|||
"password": { |
|||
"name": "password", |
|||
"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": { |
|||
"prometheus_table_id_interfaces_table_name_fk": { |
|||
"name": "prometheus_table_id_interfaces_table_name_fk", |
|||
"tableFrom": "prometheus_table", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"tableTo": "interfaces_table", |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onUpdate": "cascade", |
|||
"onDelete": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
}, |
|||
"one_time_links_table": { |
|||
"name": "one_time_links_table", |
|||
"columns": { |
|||
"id": { |
|||
"name": "id", |
|||
"type": "integer", |
|||
"primaryKey": true, |
|||
"notNull": true, |
|||
"autoincrement": true |
|||
}, |
|||
"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 |
|||
}, |
|||
"clientId": { |
|||
"name": "clientId", |
|||
"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": { |
|||
"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_clientId_clients_table_id_fk": { |
|||
"name": "one_time_links_table_clientId_clients_table_id_fk", |
|||
"tableFrom": "one_time_links_table", |
|||
"columnsFrom": [ |
|||
"clientId" |
|||
], |
|||
"tableTo": "clients_table", |
|||
"columnsTo": [ |
|||
"id" |
|||
], |
|||
"onUpdate": "cascade", |
|||
"onDelete": "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 |
|||
}, |
|||
"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 |
|||
}, |
|||
"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", |
|||
"columnsFrom": [ |
|||
"id" |
|||
], |
|||
"tableTo": "interfaces_table", |
|||
"columnsTo": [ |
|||
"name" |
|||
], |
|||
"onUpdate": "cascade", |
|||
"onDelete": "cascade" |
|||
} |
|||
}, |
|||
"compositePrimaryKeys": {}, |
|||
"uniqueConstraints": {}, |
|||
"checkConstraints": {} |
|||
} |
|||
}, |
|||
"views": {}, |
|||
"enums": {}, |
|||
"_meta": { |
|||
"columns": {}, |
|||
"schemas": {}, |
|||
"tables": {} |
|||
}, |
|||
"internal": { |
|||
"indexes": {} |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
{ |
|||
"version": "7", |
|||
"dialect": "sqlite", |
|||
"entries": [ |
|||
{ |
|||
"idx": 0, |
|||
"version": "6", |
|||
"when": 1737122352401, |
|||
"tag": "0000_short_skin", |
|||
"breakpoints": true |
|||
}, |
|||
{ |
|||
"idx": 1, |
|||
"version": "6", |
|||
"when": 1737122356601, |
|||
"tag": "0001_classy_the_stranger", |
|||
"breakpoints": true |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,37 @@ |
|||
import { sql, relations } from 'drizzle-orm'; |
|||
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
import { oneTimeLink } from '../../schema'; |
|||
|
|||
export const client = sqliteTable('clients_table', { |
|||
id: int().primaryKey({ autoIncrement: true }), |
|||
name: text().notNull(), |
|||
ipv4Address: text('ipv4_address').notNull().unique(), |
|||
ipv6Address: text('ipv6_address').notNull().unique(), |
|||
privateKey: text('private_key').notNull(), |
|||
publicKey: text('public_key').notNull(), |
|||
preSharedKey: text('pre_shared_key').notNull(), |
|||
expiresAt: text('expires_at'), |
|||
allowedIps: text('allowed_ips', { mode: 'json' }).$type<string[]>().notNull(), |
|||
serverAllowedIps: text('server_allowed_ips', { mode: 'json' }) |
|||
.$type<string[]>() |
|||
.notNull(), |
|||
persistentKeepalive: int('persistent_keepalive').notNull(), |
|||
mtu: int().notNull(), |
|||
dns: text({ mode: 'json' }).$type<string[]>().notNull(), |
|||
enabled: int({ mode: 'boolean' }).notNull(), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
|||
|
|||
export const clientsRelations = relations(client, ({ one }) => ({ |
|||
oneTimeLink: one(oneTimeLink, { |
|||
fields: [client.id], |
|||
references: [oneTimeLink.clientId], |
|||
}), |
|||
})); |
@ -0,0 +1,128 @@ |
|||
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 '#db/schema'; |
|||
import { wgInterface, userConfig } from '#db/schema'; |
|||
import { parseCidr } from 'cidr-tools'; |
|||
|
|||
function createPreparedStatement(db: DBType) { |
|||
return { |
|||
findAll: db.query.client |
|||
.findMany({ |
|||
with: { |
|||
oneTimeLink: true, |
|||
}, |
|||
}) |
|||
.prepare(), |
|||
findById: db.query.client |
|||
.findFirst({ where: eq(client.id, sql.placeholder('id')) }) |
|||
.prepare(), |
|||
toggle: db |
|||
.update(client) |
|||
.set({ enabled: sql.placeholder('enabled') as never as boolean }) |
|||
.where(eq(client.id, sql.placeholder('id'))) |
|||
.prepare(), |
|||
delete: db |
|||
.delete(client) |
|||
.where(eq(client.id, sql.placeholder('id'))) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class ClientService { |
|||
#db: DBType; |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#db = db; |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
async getAll() { |
|||
const result = await this.#statements.findAll.execute(); |
|||
return result.map((row) => ({ |
|||
...row, |
|||
createdAt: new Date(row.createdAt), |
|||
updatedAt: new Date(row.updatedAt), |
|||
})); |
|||
} |
|||
|
|||
get(id: ID) { |
|||
return this.#statements.findById.execute({ id }); |
|||
} |
|||
|
|||
async create({ name, expiresAt }: ClientCreateType) { |
|||
const privateKey = await wg.generatePrivateKey(); |
|||
const publicKey = await wg.getPublicKey(privateKey); |
|||
const preSharedKey = await wg.generatePreSharedKey(); |
|||
|
|||
let parsedExpiresAt = expiresAt; |
|||
if (parsedExpiresAt) { |
|||
const expiresAtDate = new Date(parsedExpiresAt); |
|||
expiresAtDate.setHours(23); |
|||
expiresAtDate.setMinutes(59); |
|||
expiresAtDate.setSeconds(59); |
|||
parsedExpiresAt = expiresAtDate.toISOString(); |
|||
} |
|||
|
|||
return this.#db.transaction(async (tx) => { |
|||
const clients = await tx.query.client.findMany().execute(); |
|||
const clientInterface = await tx.query.wgInterface |
|||
.findFirst({ |
|||
where: eq(wgInterface.name, 'wg0'), |
|||
}) |
|||
.execute(); |
|||
|
|||
if (!clientInterface) { |
|||
throw new Error('WireGuard interface not found'); |
|||
} |
|||
|
|||
const clientConfig = await tx.query.userConfig |
|||
.findFirst({ |
|||
where: eq(userConfig.id, clientInterface.name), |
|||
}) |
|||
.execute(); |
|||
|
|||
if (!clientConfig) { |
|||
throw new Error('WireGuard interface configuration not found'); |
|||
} |
|||
|
|||
const ipv4Cidr = parseCidr(clientInterface.ipv4Cidr); |
|||
const ipv4Address = nextIP(4, ipv4Cidr, clients); |
|||
const ipv6Cidr = parseCidr(clientInterface.ipv6Cidr); |
|||
const ipv6Address = nextIP(6, ipv6Cidr, clients); |
|||
|
|||
await tx |
|||
.insert(client) |
|||
.values({ |
|||
name, |
|||
expiresAt: parsedExpiresAt, |
|||
privateKey, |
|||
publicKey, |
|||
preSharedKey, |
|||
ipv4Address, |
|||
ipv6Address, |
|||
mtu: clientConfig.defaultMtu, |
|||
allowedIps: clientConfig.defaultAllowedIps, |
|||
dns: clientConfig.defaultDns, |
|||
persistentKeepalive: clientConfig.defaultPersistentKeepalive, |
|||
serverAllowedIps: [], |
|||
enabled: true, |
|||
}) |
|||
.execute(); |
|||
}); |
|||
} |
|||
|
|||
toggle(id: ID, enabled: boolean) { |
|||
return this.#statements.toggle.execute({ id, enabled }); |
|||
} |
|||
|
|||
delete(id: ID) { |
|||
return this.#statements.delete.execute({ id }); |
|||
} |
|||
|
|||
update(id: ID, data: UpdateClientType) { |
|||
return this.#db.update(client).set(data).where(eq(client.id, id)).execute(); |
|||
} |
|||
} |
@ -0,0 +1,72 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import z from 'zod'; |
|||
|
|||
import type { client } from './schema'; |
|||
|
|||
export type ID = string; |
|||
|
|||
export type ClientType = InferSelectModel<typeof client>; |
|||
|
|||
export type CreateClientType = Omit< |
|||
ClientType, |
|||
'createdAt' | 'updatedAt' | 'id' |
|||
>; |
|||
|
|||
export type UpdateClientType = Omit< |
|||
CreateClientType, |
|||
'privateKey' | 'publicKey' | 'preSharedKey' |
|||
>; |
|||
|
|||
const name = z |
|||
.string({ message: 'zod.client.name' }) |
|||
.min(1, 'zod.client.nameMin') |
|||
.pipe(safeStringRefine); |
|||
|
|||
const expiresAt = z |
|||
.string({ message: 'zod.client.expireDate' }) |
|||
.min(1, 'zod.client.expireDateMin') |
|||
.pipe(safeStringRefine) |
|||
.nullable(); |
|||
|
|||
const address4 = z |
|||
.string({ message: 'zod.client.address4' }) |
|||
.min(1, { message: 'zod.client.address4Min' }) |
|||
.pipe(safeStringRefine); |
|||
|
|||
const address6 = z |
|||
.string({ message: 'zod.client.address6' }) |
|||
.min(1, { message: 'zod.client.address6Min' }) |
|||
.pipe(safeStringRefine); |
|||
|
|||
const serverAllowedIps = z.array(AddressSchema, { |
|||
message: 'zod.serverAllowedIps', |
|||
}); |
|||
|
|||
export const ClientCreateSchema = z.object({ |
|||
name: name, |
|||
expiresAt: expiresAt, |
|||
}); |
|||
|
|||
export type ClientCreateType = z.infer<typeof ClientCreateSchema>; |
|||
|
|||
export const ClientUpdateSchema = schemaForType<UpdateClientType>()( |
|||
z.object({ |
|||
name: name, |
|||
enabled: EnabledSchema, |
|||
expiresAt: expiresAt, |
|||
ipv4Address: address4, |
|||
ipv6Address: address6, |
|||
allowedIps: AllowedIpsSchema, |
|||
serverAllowedIps: serverAllowedIps, |
|||
mtu: MtuSchema, |
|||
persistentKeepalive: PersistentKeepaliveSchema, |
|||
dns: DnsSchema, |
|||
}) |
|||
); |
|||
|
|||
// TODO: investigate if coerce is bad
|
|||
const clientId = z.number({ message: 'zod.client.id', coerce: true }); |
|||
|
|||
export const ClientGetSchema = z.object({ |
|||
clientId: clientId, |
|||
}); |
@ -0,0 +1,16 @@ |
|||
import { sql } from 'drizzle-orm'; |
|||
import { sqliteTable, text, int } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
export const general = sqliteTable('general_table', { |
|||
id: int().primaryKey({ autoIncrement: false }).default(1), |
|||
setupStep: int().notNull(), |
|||
sessionPassword: text('session_password').notNull(), |
|||
sessionTimeout: int('session_timeout').notNull(), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
@ -0,0 +1,68 @@ |
|||
import type { DBType } from '#db/sqlite'; |
|||
import { sql } from 'drizzle-orm'; |
|||
import { general } from './schema'; |
|||
import type { GeneralUpdateType } from './types'; |
|||
|
|||
function createPreparedStatement(db: DBType) { |
|||
return { |
|||
find: db.query.general.findFirst().prepare(), |
|||
updateSetupStep: db |
|||
.update(general) |
|||
.set({ |
|||
setupStep: sql.placeholder('setupStep') as never as number, |
|||
}) |
|||
.prepare(), |
|||
update: db |
|||
.update(general) |
|||
.set({ |
|||
sessionTimeout: sql.placeholder('sessionTimeout') as never as number, |
|||
}) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class GeneralService { |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
/** |
|||
* @throws |
|||
*/ |
|||
private async get() { |
|||
const result = await this.#statements.find.execute(); |
|||
if (!result) { |
|||
throw new Error('General Config not found'); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* @throws |
|||
*/ |
|||
async getSetupStep() { |
|||
const result = await this.get(); |
|||
return { step: result.setupStep, done: result.setupStep === 0 }; |
|||
} |
|||
|
|||
setSetupStep(step: number) { |
|||
return this.#statements.updateSetupStep.execute({ setupStep: step }); |
|||
} |
|||
|
|||
/** |
|||
* @throws |
|||
*/ |
|||
async getSessionConfig() { |
|||
const result = await this.get(); |
|||
return { |
|||
sessionPassword: result.sessionPassword, |
|||
sessionTimeout: result.sessionTimeout, |
|||
}; |
|||
} |
|||
|
|||
update(data: GeneralUpdateType) { |
|||
return this.#statements.update.execute(data); |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { general } from './schema'; |
|||
import z from 'zod'; |
|||
|
|||
export type GeneralType = InferSelectModel<typeof general>; |
|||
|
|||
const sessionTimeout = z.number({ message: 'zod.general.sessionTimeout' }); |
|||
|
|||
export const GeneralUpdateSchema = z.object({ |
|||
sessionTimeout: sessionTimeout, |
|||
}); |
|||
|
|||
export type GeneralUpdateType = z.infer<typeof GeneralUpdateSchema>; |
|||
|
|||
export type SetupStepType = { step: number; done: boolean }; |
@ -0,0 +1,24 @@ |
|||
import { sql } from 'drizzle-orm'; |
|||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
import { wgInterface } from '../../schema'; |
|||
|
|||
export const hooks = sqliteTable('hooks_table', { |
|||
id: text() |
|||
.primaryKey() |
|||
.references(() => wgInterface.name, { |
|||
onDelete: 'cascade', |
|||
onUpdate: 'cascade', |
|||
}), |
|||
preUp: text('pre_up').notNull(), |
|||
postUp: text('post_up').notNull(), |
|||
preDown: text('pre_down').notNull(), |
|||
postDown: text('post_down').notNull(), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
@ -0,0 +1,34 @@ |
|||
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 { |
|||
get: db.query.hooks |
|||
.findFirst({ where: eq(hooks.id, sql.placeholder('interface')) }) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class HooksService { |
|||
#db: DBType; |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#db = db; |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
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(); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { hooks } from './schema'; |
|||
import z from 'zod'; |
|||
|
|||
export type HooksType = InferSelectModel<typeof hooks>; |
|||
|
|||
export type HooksUpdateType = Omit<HooksType, 'id' | 'createdAt' | 'updatedAt'>; |
|||
|
|||
const hook = z.string({ message: 'zod.hook' }).pipe(safeStringRefine); |
|||
|
|||
export const HooksUpdateSchema = schemaForType<HooksUpdateType>()( |
|||
z.object({ |
|||
preUp: hook, |
|||
postUp: hook, |
|||
preDown: hook, |
|||
postDown: hook, |
|||
}) |
|||
); |
@ -0,0 +1,39 @@ |
|||
import { sql, relations } from 'drizzle-orm'; |
|||
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
import { userConfig, hooks, prometheus } from '../../schema'; |
|||
|
|||
// maybe support multiple interfaces in the future
|
|||
export const wgInterface = sqliteTable('interfaces_table', { |
|||
name: text().primaryKey(), |
|||
device: text().notNull(), |
|||
port: int().notNull().unique(), |
|||
privateKey: text('private_key').notNull(), |
|||
publicKey: text('public_key').notNull(), |
|||
ipv4Cidr: text('ipv4_cidr').notNull(), |
|||
ipv6Cidr: text('ipv6_cidr').notNull(), |
|||
mtu: int().notNull(), |
|||
enabled: int({ mode: 'boolean' }).notNull(), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
|||
|
|||
export const wgInterfaceRelations = relations(wgInterface, ({ one }) => ({ |
|||
hooks: one(hooks, { |
|||
fields: [wgInterface.name], |
|||
references: [hooks.id], |
|||
}), |
|||
prometheus: one(prometheus, { |
|||
fields: [wgInterface.name], |
|||
references: [prometheus.id], |
|||
}), |
|||
userConfig: one(userConfig, { |
|||
fields: [wgInterface.name], |
|||
references: [userConfig.id], |
|||
}), |
|||
})); |
@ -0,0 +1,90 @@ |
|||
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 { |
|||
get: db.query.wgInterface |
|||
.findFirst({ where: eq(wgInterface.name, sql.placeholder('interface')) }) |
|||
.prepare(), |
|||
getAll: db.query.wgInterface.findMany().prepare(), |
|||
updateKeyPair: db |
|||
.update(wgInterface) |
|||
.set({ |
|||
privateKey: sql.placeholder('privateKey') as never as string, |
|||
publicKey: sql.placeholder('publicKey') as never as string, |
|||
}) |
|||
.where(eq(wgInterface.name, sql.placeholder('interface'))) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class InterfaceService { |
|||
#db: DBType; |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#db = db; |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
get(infName: string) { |
|||
return this.#statements.get.execute({ interface: infName }); |
|||
} |
|||
|
|||
getAll() { |
|||
return this.#statements.getAll.execute(); |
|||
} |
|||
|
|||
updateKeyPair(infName: string, privateKey: string, publicKey: string) { |
|||
return this.#statements.updateKeyPair.execute({ |
|||
interface: infName, |
|||
privateKey, |
|||
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(); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { wgInterface } from './schema'; |
|||
import z from 'zod'; |
|||
|
|||
export type InterfaceType = InferSelectModel<typeof wgInterface>; |
|||
|
|||
export type InterfaceCreateType = Omit< |
|||
InterfaceType, |
|||
'createdAt' | 'updatedAt' |
|||
>; |
|||
|
|||
export type InterfaceUpdateType = Omit< |
|||
InterfaceCreateType, |
|||
'name' | 'createdAt' | 'updatedAt' | 'privateKey' | 'publicKey' |
|||
>; |
|||
|
|||
const device = z |
|||
.string({ message: 'zod.interface.device' }) |
|||
.min(1, 'zod.interface.deviceMin') |
|||
.pipe(safeStringRefine); |
|||
|
|||
const cidr = z |
|||
.string({ message: 'zod.interface.cidr' }) |
|||
.min(1, { message: 'zod.interface.cidrMin' }) |
|||
.pipe(safeStringRefine); |
|||
|
|||
export const InterfaceUpdateSchema = schemaForType<InterfaceUpdateType>()( |
|||
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<InterfaceCidrUpdateType>()( |
|||
z.object({ |
|||
ipv4Cidr: cidr, |
|||
ipv6Cidr: cidr, |
|||
}) |
|||
); |
@ -0,0 +1,21 @@ |
|||
import { sql } from 'drizzle-orm'; |
|||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
import { wgInterface } from '../../schema'; |
|||
|
|||
export const prometheus = sqliteTable('prometheus_table', { |
|||
id: text() |
|||
.primaryKey() |
|||
.references(() => wgInterface.name, { |
|||
onDelete: 'cascade', |
|||
onUpdate: 'cascade', |
|||
}), |
|||
password: text().notNull(), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
@ -0,0 +1,31 @@ |
|||
import type { DBType } from '#db/sqlite'; |
|||
import { eq, sql } from 'drizzle-orm'; |
|||
import { prometheus } from './schema'; |
|||
|
|||
function createPreparedStatement(db: DBType) { |
|||
return { |
|||
get: db.query.prometheus |
|||
.findFirst({ where: eq(prometheus.id, sql.placeholder('interface')) }) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class PrometheusService { |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
get(infName: string) { |
|||
return this.#statements.get.execute({ interface: infName }); |
|||
} |
|||
} |
|||
|
|||
export class MetricsService { |
|||
prometheus: PrometheusService; |
|||
|
|||
constructor(db: DBType) { |
|||
this.prometheus = new PrometheusService(db); |
|||
} |
|||
} |
@ -0,0 +1,4 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { prometheus } from './schema'; |
|||
|
|||
export type PrometheusType = InferSelectModel<typeof prometheus>; |
@ -0,0 +1,27 @@ |
|||
import { sql, relations } from 'drizzle-orm'; |
|||
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
import { client } from '../../schema'; |
|||
|
|||
export const oneTimeLink = sqliteTable('one_time_links_table', { |
|||
id: int().primaryKey({ autoIncrement: true }), |
|||
oneTimeLink: text('one_time_link').notNull().unique(), |
|||
expiresAt: text('expires_at').notNull(), |
|||
clientId: int() |
|||
.notNull() |
|||
.references(() => client.id, { onDelete: 'cascade', onUpdate: 'cascade' }), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
|||
|
|||
export const oneTimeLinksRelations = relations(oneTimeLink, ({ one }) => ({ |
|||
client: one(client, { |
|||
fields: [oneTimeLink.clientId], |
|||
references: [client.id], |
|||
}), |
|||
})); |
@ -0,0 +1,52 @@ |
|||
import type { DBType } from '#db/sqlite'; |
|||
import { eq, sql } from 'drizzle-orm'; |
|||
import { oneTimeLink } from './schema'; |
|||
import type { ID } from '../../schema'; |
|||
import CRC32 from 'crc-32'; |
|||
|
|||
function createPreparedStatement(db: DBType) { |
|||
return { |
|||
delete: db |
|||
.delete(oneTimeLink) |
|||
.where(eq(oneTimeLink.id, sql.placeholder('id'))) |
|||
.prepare(), |
|||
create: db |
|||
.insert(oneTimeLink) |
|||
.values({ |
|||
clientId: sql.placeholder('id'), |
|||
oneTimeLink: sql.placeholder('oneTimeLink'), |
|||
expiresAt: sql.placeholder('expiresAt'), |
|||
}) |
|||
.prepare(), |
|||
erase: db |
|||
.update(oneTimeLink) |
|||
.set({ expiresAt: sql.placeholder('expiresAt') as never as string }) |
|||
.where(eq(oneTimeLink.clientId, sql.placeholder('id'))) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class OneTimeLinkService { |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
delete(id: ID) { |
|||
return this.#statements.delete.execute({ id }); |
|||
} |
|||
|
|||
generate(id: ID) { |
|||
const key = `${id}-${Math.floor(Math.random() * 1000)}`; |
|||
const oneTimeLink = Math.abs(CRC32.str(key)).toString(16); |
|||
const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString(); |
|||
|
|||
return this.#statements.create.execute({ id, oneTimeLink, expiresAt }); |
|||
} |
|||
|
|||
erase(id: ID) { |
|||
const expiresAt = Date.now() + 10 * 1000; |
|||
return this.#statements.erase.execute({ id, expiresAt }); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { oneTimeLink } from './schema'; |
|||
import { z } from 'zod'; |
|||
|
|||
export type OneTimeLinkType = InferSelectModel<typeof oneTimeLink>; |
|||
|
|||
const oneTimeLinkType = z |
|||
.string({ message: 'zod.otl.otl' }) |
|||
.min(1, 'zod.otl.otlMin') |
|||
.pipe(safeStringRefine); |
|||
|
|||
export const OneTimeLinkGetSchema = z.object( |
|||
{ |
|||
oneTimeLink: oneTimeLinkType, |
|||
}, |
|||
{ message: objectMessage } |
|||
); |
@ -0,0 +1,19 @@ |
|||
import { sql } from 'drizzle-orm'; |
|||
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
export const user = sqliteTable('users_table', { |
|||
id: int().primaryKey({ autoIncrement: true }), |
|||
username: text().notNull().unique(), |
|||
password: text().notNull(), |
|||
email: text(), |
|||
name: text().notNull(), |
|||
role: int().$type<Role>().notNull(), |
|||
enabled: int({ mode: 'boolean' }).notNull(), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
@ -0,0 +1,63 @@ |
|||
import type { DBType } from '#db/sqlite'; |
|||
import { eq, sql } from 'drizzle-orm'; |
|||
import { user } from './schema'; |
|||
import type { ID } from '../../schema'; |
|||
|
|||
function createPreparedStatement(db: DBType) { |
|||
return { |
|||
findAll: db.query.user.findMany().prepare(), |
|||
findById: db.query.user |
|||
.findFirst({ where: eq(user.id, sql.placeholder('id')) }) |
|||
.prepare(), |
|||
findByUsername: db.query.user |
|||
.findFirst({ |
|||
where: eq(user.username, sql.placeholder('username')), |
|||
}) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class UserService { |
|||
#db: DBType; |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#db = db; |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
async getAll() { |
|||
return this.#statements.findAll.execute(); |
|||
} |
|||
|
|||
async get(id: ID) { |
|||
return this.#statements.findById.execute({ id }); |
|||
} |
|||
|
|||
async getByUsername(username: string) { |
|||
return this.#statements.findByUsername.execute({ username }); |
|||
} |
|||
|
|||
async create(username: string, password: string) { |
|||
const hash = await hashPassword(password); |
|||
|
|||
return this.#db.transaction(async (tx) => { |
|||
const oldUser = await this.getByUsername(username); |
|||
|
|||
if (oldUser) { |
|||
throw new Error('User already exists'); |
|||
} |
|||
|
|||
const userCount = await tx.$count(user); |
|||
|
|||
await tx.insert(user).values({ |
|||
password: hash, |
|||
username, |
|||
email: null, |
|||
name: 'Administrator', |
|||
role: userCount === 0 ? roles.ADMIN : roles.CLIENT, |
|||
enabled: true, |
|||
}); |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { user } from './schema'; |
|||
import z from 'zod'; |
|||
|
|||
export type UserType = InferSelectModel<typeof user>; |
|||
|
|||
const username = z |
|||
.string({ message: 'zod.user.username' }) |
|||
.min(8, 'zod.user.usernameMin') |
|||
.pipe(safeStringRefine); |
|||
|
|||
const password = z |
|||
.string({ message: 'zod.user.password' }) |
|||
.min(12, 'zod.user.passwordMin') |
|||
.regex(/[A-Z]/, 'zod.user.passwordUppercase') |
|||
.regex(/[a-z]/, 'zod.user.passwordLowercase') |
|||
.regex(/\d/, 'zod.user.passwordNumber') |
|||
.regex(/[!@#$%^&*(),.?":{}|<>]/, 'zod.user.passwordSpecial') |
|||
.pipe(safeStringRefine); |
|||
|
|||
const remember = z.boolean({ message: 'zod.user.remember' }); |
|||
|
|||
export const UserLoginSchema = z.object( |
|||
{ |
|||
username: username, |
|||
password: password, |
|||
remember: remember, |
|||
}, |
|||
{ message: objectMessage } |
|||
); |
|||
|
|||
const accept = z.boolean().refine((val) => val === true, { |
|||
message: 'zod.user.accept', |
|||
}); |
|||
|
|||
export const UserSetupType = z.object( |
|||
{ |
|||
username: username, |
|||
password: password, |
|||
accept: accept, |
|||
}, |
|||
{ message: objectMessage } |
|||
); |
@ -0,0 +1,29 @@ |
|||
import { sql } from 'drizzle-orm'; |
|||
import { int, sqliteTable, text } from 'drizzle-orm/sqlite-core'; |
|||
|
|||
import { wgInterface } from '../../schema'; |
|||
|
|||
// default* means clients store it themselves
|
|||
export const userConfig = sqliteTable('user_configs_table', { |
|||
id: text() |
|||
.primaryKey() |
|||
.references(() => wgInterface.name, { |
|||
onDelete: 'cascade', |
|||
onUpdate: 'cascade', |
|||
}), |
|||
defaultMtu: int('default_mtu').notNull(), |
|||
defaultPersistentKeepalive: int('default_persistent_keepalive').notNull(), |
|||
defaultDns: text('default_dns', { mode: 'json' }).$type<string[]>().notNull(), |
|||
defaultAllowedIps: text('default_allowed_ips', { mode: 'json' }) |
|||
.$type<string[]>() |
|||
.notNull(), |
|||
host: text().notNull(), |
|||
port: int().notNull(), |
|||
createdAt: text('created_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`), |
|||
updatedAt: text('updated_at') |
|||
.notNull() |
|||
.default(sql`(CURRENT_TIMESTAMP)`) |
|||
.$onUpdate(() => sql`(CURRENT_TIMESTAMP)`), |
|||
}); |
@ -0,0 +1,50 @@ |
|||
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 { |
|||
get: db.query.userConfig |
|||
.findFirst({ where: eq(userConfig.id, sql.placeholder('interface')) }) |
|||
.prepare(), |
|||
updateHostPort: db |
|||
.update(userConfig) |
|||
.set({ |
|||
host: sql.placeholder('host') as never as string, |
|||
port: sql.placeholder('port') as never as number, |
|||
}) |
|||
.where(eq(userConfig.id, sql.placeholder('interface'))) |
|||
.prepare(), |
|||
}; |
|||
} |
|||
|
|||
export class UserConfigService { |
|||
#db: DBType; |
|||
#statements: ReturnType<typeof createPreparedStatement>; |
|||
|
|||
constructor(db: DBType) { |
|||
this.#db = db; |
|||
this.#statements = createPreparedStatement(db); |
|||
} |
|||
|
|||
get(infName: string) { |
|||
return this.#statements.get.execute({ interface: infName }); |
|||
} |
|||
|
|||
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(); |
|||
} |
|||
} |
@ -0,0 +1,31 @@ |
|||
import type { InferSelectModel } from 'drizzle-orm'; |
|||
import type { userConfig } from './schema'; |
|||
import z from 'zod'; |
|||
|
|||
export type UserConfigType = InferSelectModel<typeof userConfig>; |
|||
|
|||
const host = z |
|||
.string({ message: 'zod.userConfig.host' }) |
|||
.min(1, 'zod.userConfig.hostMin') |
|||
.pipe(safeStringRefine); |
|||
|
|||
export const UserConfigSetupType = z.object({ |
|||
host: host, |
|||
port: PortSchema, |
|||
}); |
|||
|
|||
export type UserConfigUpdateType = Omit< |
|||
UserConfigType, |
|||
'id' | 'createdAt' | 'updatedAt' |
|||
>; |
|||
|
|||
export const UserConfigUpdateSchema = schemaForType<UserConfigUpdateType>()( |
|||
z.object({ |
|||
port: PortSchema, |
|||
defaultMtu: MtuSchema, |
|||
defaultPersistentKeepalive: PersistentKeepaliveSchema, |
|||
defaultDns: DnsSchema, |
|||
defaultAllowedIps: AllowedIpsSchema, |
|||
host: host, |
|||
}) |
|||
); |
@ -0,0 +1,12 @@ |
|||
// Make sure to not use any Path Aliases in these files
|
|||
export * from './repositories/client/schema'; |
|||
export * from './repositories/general/schema'; |
|||
export * from './repositories/hooks/schema'; |
|||
export * from './repositories/interface/schema'; |
|||
export * from './repositories/metrics/schema'; |
|||
export * from './repositories/oneTimeLink/schema'; |
|||
export * from './repositories/user/schema'; |
|||
export * from './repositories/userConfig/schema'; |
|||
|
|||
// TODO: move to types
|
|||
export type ID = number; |
@ -0,0 +1,63 @@ |
|||
import { drizzle } from 'drizzle-orm/libsql'; |
|||
import { migrate as drizzleMigrate } from 'drizzle-orm/libsql/migrator'; |
|||
import { createClient } from '@libsql/client'; |
|||
import debug from 'debug'; |
|||
|
|||
import * as schema from './schema'; |
|||
import { ClientService } from './repositories/client/service'; |
|||
import { GeneralService } from './repositories/general/service'; |
|||
import { UserService } from './repositories/user/service'; |
|||
import { UserConfigService } from './repositories/userConfig/service'; |
|||
import { InterfaceService } from './repositories/interface/service'; |
|||
import { HooksService } from './repositories/hooks/service'; |
|||
import { OneTimeLinkService } from './repositories/oneTimeLink/service'; |
|||
import { MetricsService } from './repositories/metrics/service'; |
|||
|
|||
const DB_DEBUG = debug('Database'); |
|||
|
|||
const client = createClient({ url: 'file:/etc/wireguard/wg0.db' }); |
|||
const db = drizzle({ client, schema }); |
|||
|
|||
export async function connect() { |
|||
await migrate(); |
|||
return new DBService(db); |
|||
} |
|||
|
|||
class DBService { |
|||
clients: ClientService; |
|||
general: GeneralService; |
|||
users: UserService; |
|||
userConfigs: UserConfigService; |
|||
interfaces: InterfaceService; |
|||
hooks: HooksService; |
|||
oneTimeLinks: OneTimeLinkService; |
|||
metrics: MetricsService; |
|||
|
|||
constructor(db: DBType) { |
|||
this.clients = new ClientService(db); |
|||
this.general = new GeneralService(db); |
|||
this.users = new UserService(db); |
|||
this.userConfigs = new UserConfigService(db); |
|||
this.interfaces = new InterfaceService(db); |
|||
this.hooks = new HooksService(db); |
|||
this.oneTimeLinks = new OneTimeLinkService(db); |
|||
this.metrics = new MetricsService(db); |
|||
} |
|||
} |
|||
|
|||
export type DBType = typeof db; |
|||
export type DBServiceType = DBService; |
|||
|
|||
async function migrate() { |
|||
try { |
|||
DB_DEBUG('Migrating database...'); |
|||
await drizzleMigrate(db, { |
|||
migrationsFolder: './server/database/migrations', |
|||
}); |
|||
DB_DEBUG('Migration complete'); |
|||
} catch (e) { |
|||
if (e instanceof Error) { |
|||
DB_DEBUG('Failed to migrate database:', e.message); |
|||
} |
|||
} |
|||
} |
@ -1,35 +0,0 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const url = getRequestURL(event); |
|||
const session = await useWGSession(event); |
|||
|
|||
// Api handled by session, Setup handled with setup middleware
|
|||
if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/setup')) { |
|||
return; |
|||
} |
|||
|
|||
if (url.pathname === '/login') { |
|||
if (session.data.userId) { |
|||
return sendRedirect(event, '/', 302); |
|||
} |
|||
return; |
|||
} |
|||
|
|||
// Require auth for every page other than Login
|
|||
// TODO: investigate /__nuxt_error (error page when unauthenticated)
|
|||
if (!session.data.userId) { |
|||
return sendRedirect(event, '/login', 302); |
|||
} |
|||
|
|||
if (url.pathname.startsWith('/admin')) { |
|||
const user = await Database.user.findById(session.data.userId); |
|||
if (!user) { |
|||
return sendRedirect(event, '/login', 302); |
|||
} |
|||
if (user.role !== 'ADMIN') { |
|||
throw createError({ |
|||
statusCode: 403, |
|||
statusMessage: 'Not allowed to access Admin Panel', |
|||
}); |
|||
} |
|||
} |
|||
}); |
@ -1,94 +0,0 @@ |
|||
import type { User } from '~~/services/database/repositories/user'; |
|||
|
|||
export default defineEventHandler(async (event) => { |
|||
const url = getRequestURL(event); |
|||
// If one method of a route is public, every method is public!
|
|||
// Handle api routes
|
|||
if ( |
|||
!url.pathname.startsWith('/api/') || |
|||
url.pathname.startsWith('/api/setup/') || |
|||
url.pathname === '/api/session' || |
|||
url.pathname === '/api/release' |
|||
) { |
|||
return; |
|||
} |
|||
const system = await Database.system.get(); |
|||
|
|||
const session = await getSession<WGSession>(event, system.sessionConfig); |
|||
const authorization = getHeader(event, 'Authorization'); |
|||
|
|||
let user: User | undefined = undefined; |
|||
if (session.data.userId) { |
|||
// Handle if authenticating using Session
|
|||
user = await Database.user.findById(session.data.userId); |
|||
} else if (authorization) { |
|||
// Handle if authenticating using Header
|
|||
const [method, value] = authorization.split(' '); |
|||
// Support Basic Authentication
|
|||
// TODO: support personal access token or similar
|
|||
if (method !== 'Basic' || !value) { |
|||
throw createError({ |
|||
statusCode: 401, |
|||
statusMessage: 'Session failed', |
|||
}); |
|||
} |
|||
|
|||
const basicValue = Buffer.from(value, 'base64').toString('utf-8'); |
|||
|
|||
// Split by first ":"
|
|||
const index = basicValue.indexOf(':'); |
|||
const username = basicValue.substring(0, index); |
|||
const password = basicValue.substring(index + 1); |
|||
|
|||
if (!username || !password) { |
|||
throw createError({ |
|||
statusCode: 401, |
|||
statusMessage: 'Session failed', |
|||
}); |
|||
} |
|||
|
|||
const users = await Database.user.findAll(); |
|||
const foundUser = users.find((v) => v.username === username); |
|||
|
|||
if (!foundUser) { |
|||
throw createError({ |
|||
statusCode: 401, |
|||
statusMessage: 'Session failed', |
|||
}); |
|||
} |
|||
|
|||
const userHashPassword = foundUser.password; |
|||
const passwordValid = await isPasswordValid(password, userHashPassword); |
|||
|
|||
if (!passwordValid) { |
|||
throw createError({ |
|||
statusCode: 401, |
|||
statusMessage: 'Incorrect Password', |
|||
}); |
|||
} |
|||
user = foundUser; |
|||
} |
|||
|
|||
if (!user) { |
|||
throw createError({ |
|||
statusCode: 401, |
|||
statusMessage: 'Not logged in', |
|||
}); |
|||
} |
|||
|
|||
if (!user.enabled) { |
|||
throw createError({ |
|||
statusCode: 403, |
|||
statusMessage: 'Account is disabled', |
|||
}); |
|||
} |
|||
|
|||
if (url.pathname.startsWith('/api/admin')) { |
|||
if (user.role !== 'ADMIN') { |
|||
throw createError({ |
|||
statusCode: 403, |
|||
statusMessage: 'Missing Permissions', |
|||
}); |
|||
} |
|||
} |
|||
}); |
@ -1,13 +1,13 @@ |
|||
export default defineEventHandler(async () => { |
|||
// TODO: check password
|
|||
|
|||
const system = await Database.system.get(); |
|||
if (!system.metrics.prometheus.enabled) { |
|||
const prometheus = await Database.metrics.prometheus.get('wg0'); |
|||
if (!prometheus) { |
|||
throw createError({ |
|||
statusCode: 400, |
|||
message: 'Prometheus metrics are not enabled', |
|||
}); |
|||
} |
|||
|
|||
return WireGuard.getMetricsJSON(); |
|||
return getMetricsJSON(); |
|||
}); |
|||
|
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue