diff --git a/src/server/api/account/create.post.ts b/src/server/api/account/create.post.ts index 4d58e470..b5194459 100644 --- a/src/server/api/account/create.post.ts +++ b/src/server/api/account/create.post.ts @@ -3,6 +3,6 @@ export default defineEventHandler(async (event) => { event, validateZod(passwordType) ); - await Database.createUser(username, password); + await Database.user.create(username, password); return { success: true }; }); diff --git a/src/server/api/account/setup.post.ts b/src/server/api/account/setup.post.ts index ca89a753..123308af 100644 --- a/src/server/api/account/setup.post.ts +++ b/src/server/api/account/setup.post.ts @@ -3,13 +3,13 @@ export default defineEventHandler(async (event) => { event, validateZod(passwordType) ); - const users = await Database.getUsers(); + const users = await Database.user.findAll(); if (users.length !== 0) { throw createError({ statusCode: 400, statusMessage: 'Invalid state', }); } - await Database.createUser(username, password); + await Database.user.create(username, password); return { success: true }; }); diff --git a/src/server/api/cnf/[oneTimeLink].ts b/src/server/api/cnf/[oneTimeLink].ts index ffb0eb8b..64d39c32 100644 --- a/src/server/api/cnf/[oneTimeLink].ts +++ b/src/server/api/cnf/[oneTimeLink].ts @@ -1,5 +1,5 @@ export default defineEventHandler(async (event) => { - const system = await Database.getSystem(); + const system = await Database.system.get(); if (!system.oneTimeLinks.enabled) { throw createError({ statusCode: 404, diff --git a/src/server/api/features.get.ts b/src/server/api/features.get.ts index c2868ede..0016e2bf 100644 --- a/src/server/api/features.get.ts +++ b/src/server/api/features.get.ts @@ -1,5 +1,5 @@ export default defineEventHandler(async () => { - const system = await Database.getSystem(); + const system = await Database.system.get(); return { trafficStats: system.trafficStats, sortClients: system.sortClients, diff --git a/src/server/api/lang.get.ts b/src/server/api/lang.get.ts index 2c31214b..d16ac483 100644 --- a/src/server/api/lang.get.ts +++ b/src/server/api/lang.get.ts @@ -1,5 +1,5 @@ export default defineEventHandler(async (event) => { setHeader(event, 'Content-Type', 'application/json'); - const system = await Database.getSystem(); + const system = await Database.system.get(); return system.lang; }); diff --git a/src/server/api/session.post.ts b/src/server/api/session.post.ts index 71e47b7a..ea9e1a36 100644 --- a/src/server/api/session.post.ts +++ b/src/server/api/session.post.ts @@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => { validateZod(credentialsType) ); - const users = await Database.getUsers(); + const users = await Database.user.findAll(); const user = users.find((user) => user.username == username); if (!user) throw createError({ @@ -23,7 +23,7 @@ export default defineEventHandler(async (event) => { }); } - const system = await Database.getSystem(); + const system = await Database.system.get(); const conf: SessionConfig = system.sessionConfig; diff --git a/src/server/api/wireguard/client/[clientId]/generateOneTimeLink.post.ts b/src/server/api/wireguard/client/[clientId]/generateOneTimeLink.post.ts index b55f8be1..221874e4 100644 --- a/src/server/api/wireguard/client/[clientId]/generateOneTimeLink.post.ts +++ b/src/server/api/wireguard/client/[clientId]/generateOneTimeLink.post.ts @@ -1,5 +1,5 @@ export default defineEventHandler(async (event) => { - const system = await Database.getSystem(); + const system = await Database.system.get(); if (!system.oneTimeLinks.enabled) { throw createError({ status: 404, diff --git a/src/server/middleware/session.ts b/src/server/middleware/session.ts index e60b1187..1df2a796 100644 --- a/src/server/middleware/session.ts +++ b/src/server/middleware/session.ts @@ -10,7 +10,7 @@ export default defineEventHandler(async (event) => { ) { return; } - const system = await Database.getSystem(); + const system = await Database.system.get(); if (!system) throw createError({ statusCode: 500, @@ -24,7 +24,7 @@ export default defineEventHandler(async (event) => { const authorization = getHeader(event, 'Authorization'); if (url.pathname.startsWith('/api/') && authorization) { - const users = await Database.getUsers(); + const users = await Database.user.findAll(); const user = users.find((user) => user.id == session.data.userId); if (!user) throw createError({ diff --git a/src/server/middleware/setup.ts b/src/server/middleware/setup.ts index a4fce170..6c28988d 100644 --- a/src/server/middleware/setup.ts +++ b/src/server/middleware/setup.ts @@ -10,7 +10,7 @@ export default defineEventHandler(async (event) => { return; } - const users = await Database.getUsers(); + const users = await Database.user.findAll(); if (users.length === 0) { if (url.pathname.startsWith('/api/')) { throw createError({ diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index ce339fa9..b10cccd5 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -16,8 +16,8 @@ class WireGuard { } async #saveWireguardConfig() { - const system = await Database.getSystem(); - const clients = await Database.getClients(); + const system = await Database.system.get(); + const clients = await Database.client.findAll(); const result = []; result.push(wg.generateServerInterface(system)); @@ -42,7 +42,7 @@ class WireGuard { } async getClients() { - const dbClients = await Database.getClients(); + const dbClients = await Database.client.findAll(); const clients = Object.entries(dbClients).map(([clientId, client]) => ({ id: clientId, name: client.name, @@ -91,7 +91,7 @@ class WireGuard { } async getClient({ clientId }: { clientId: string }) { - const client = await Database.getClient(clientId); + const client = await Database.client.findById(clientId); if (!client) { throw createError({ statusCode: 404, @@ -103,7 +103,7 @@ class WireGuard { } async getClientConfiguration({ clientId }: { clientId: string }) { - const system = await Database.getSystem(); + const system = await Database.system.get(); const client = await this.getClient({ clientId }); return wg.generateClientConfig(system, client); @@ -124,8 +124,8 @@ class WireGuard { name: string; expireDate: string | null; }) { - const system = await Database.getSystem(); - const clients = await Database.getClients(); + const system = await Database.system.get(); + const clients = await Database.client.findAll(); const privateKey = await wg.generatePrivateKey(); const publicKey = await wg.getPublicKey(privateKey); @@ -162,7 +162,7 @@ class WireGuard { client.expiresAt = date.toISOString(); } - await Database.createClient(client); + await Database.client.create(client); await this.saveConfig(); @@ -170,12 +170,12 @@ class WireGuard { } async deleteClient({ clientId }: { clientId: string }) { - await Database.deleteClient(clientId); + await Database.client.delete(clientId); await this.saveConfig(); } async enableClient({ clientId }: { clientId: string }) { - await Database.toggleClient(clientId, true); + await Database.client.toggle(clientId, true); await this.saveConfig(); } @@ -184,7 +184,7 @@ class WireGuard { const key = `${clientId}-${Math.floor(Math.random() * 1000)}`; const oneTimeLink = Math.abs(CRC32.str(key)).toString(16); const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString(); - await Database.createOneTimeLink(clientId, { + await Database.client.createOneTimeLink(clientId, { oneTimeLink, expiresAt, }); @@ -192,12 +192,12 @@ class WireGuard { } async eraseOneTimeLink({ clientId }: { clientId: string }) { - await Database.deleteOneTimeLink(clientId); + await Database.client.deleteOneTimeLink(clientId); await this.saveConfig(); } async disableClient({ clientId }: { clientId: string }) { - await Database.toggleClient(clientId, false); + await Database.client.toggle(clientId, false); await this.saveConfig(); } @@ -209,7 +209,7 @@ class WireGuard { clientId: string; name: string; }) { - await Database.updateClientName(clientId, name); + await Database.client.updateName(clientId, name); await this.saveConfig(); } @@ -228,7 +228,7 @@ class WireGuard { }); } - await Database.updateClientAddress4(clientId, address4); + await Database.client.updateAddress4(clientId, address4); await this.saveConfig(); } @@ -250,7 +250,7 @@ class WireGuard { updatedDate = date.toISOString(); } - await Database.updateClientExpirationDate(clientId, updatedDate); + await Database.client.updateExpirationDate(clientId, updatedDate); await this.saveConfig(); } @@ -323,8 +323,8 @@ class WireGuard { } async cronJob() { - const clients = await Database.getClients(); - const system = await Database.getSystem(); + const clients = await Database.client.findAll(); + const system = await Database.system.get(); // Expires Feature if (system.clientExpiration.enabled) { for (const client of Object.values(clients)) { @@ -334,7 +334,7 @@ class WireGuard { new Date() > new Date(client.expiresAt) ) { DEBUG(`Client ${client.id} expired.`); - await Database.toggleClient(client.id, false); + await Database.client.toggle(client.id, false); } } } @@ -346,7 +346,7 @@ class WireGuard { new Date() > new Date(client.oneTimeLink.expiresAt) ) { DEBUG(`Client ${client.id} One Time Link expired.`); - await Database.deleteOneTimeLink(client.id); + await Database.client.deleteOneTimeLink(client.id); } } } diff --git a/src/server/utils/config.ts b/src/server/utils/config.ts index f927590a..2b904bbb 100644 --- a/src/server/utils/config.ts +++ b/src/server/utils/config.ts @@ -34,7 +34,7 @@ export async function migrateConfig(input: unknown) { if (!res.success) { throw new Error('Invalid Config'); } - const system = await Database.getSystem(); + const system = await Database.system.get(); const oldConfig = res.data; const oldCidr = parseCidr(oldConfig.server.address + '/24'); const db = { diff --git a/src/server/utils/session.ts b/src/server/utils/session.ts index 0761cfed..e6f86bcc 100644 --- a/src/server/utils/session.ts +++ b/src/server/utils/session.ts @@ -5,7 +5,7 @@ export type WGSession = { }; export async function useWGSession(event: H3Event) { - const system = await Database.getSystem(); + const system = await Database.system.get(); if (!system) throw new Error('Invalid'); return useSession>(event, system.sessionConfig); } diff --git a/src/services/database/lowdb.ts b/src/services/database/lowdb.ts index 4ad3e736..992c533b 100644 --- a/src/services/database/lowdb.ts +++ b/src/services/database/lowdb.ts @@ -9,52 +9,26 @@ import { import { JSONFilePreset } from 'lowdb/node'; import type { Low } from 'lowdb'; -import type { User } from './repositories/user'; +import { UserRepository, type User } from './repositories/user'; import type { Database } from './repositories/database'; import { migrationRunner } from './migrations'; -import type { Client, NewClient, OneTimeLink } from './repositories/client'; +import { + ClientRepository, + type Client, + type NewClient, + type OneTimeLink, +} from './repositories/client'; +import { SystemRepository } from './repositories/system'; const DEBUG = debug('LowDB'); -export default class LowDB extends DatabaseProvider { - #db!: Low; - #connected = false; - - private async __init() { - const dbFilePath = '/etc/wireguard/db.json'; - this.#db = await JSONFilePreset(dbFilePath, DEFAULT_DATABASE); +export class LowDBSystem extends SystemRepository { + #db: Low; + constructor(db: Low) { + super(); + this.#db = db; } - - /** - * @throws - */ - async connect() { - if (this.#connected) { - return; - } - try { - await this.__init(); - DEBUG('Running Migrations'); - await migrationRunner(this.#db); - DEBUG('Migrations ran successfully'); - } catch (e) { - DEBUG(e); - throw new Error('Failed to initialize Database'); - } - this.#connected = true; - DEBUG('Connected successfully'); - } - - get connected() { - return this.#connected; - } - - async disconnect() { - this.#connected = false; - DEBUG('Disconnected successfully'); - } - - async getSystem() { + async get() { DEBUG('Get System'); const system = this.#db.data.system; // system is only null if migration failed @@ -63,18 +37,25 @@ export default class LowDB extends DatabaseProvider { } return system; } +} +export class LowDBUser extends UserRepository { + #db: Low; + constructor(db: Low) { + super(); + this.#db = db; + } // TODO: return copy to avoid mutation (everywhere) - async getUsers() { + async findAll() { return this.#db.data.users; } - async getUser(id: string) { + async findById(id: string) { DEBUG('Get User'); return this.#db.data.users.find((user) => user.id === id); } - async createUser(username: string, password: string) { + async create(username: string, password: string) { DEBUG('Create User'); const isUserExist = this.#db.data.users.find( @@ -106,9 +87,9 @@ export default class LowDB extends DatabaseProvider { await this.#db.update((data) => data.users.push(newUser)); } - async updateUser(user: User) { + async update(user: User) { // TODO: avoid mutation, prefer .update, updatedAt - let oldUser = await this.getUser(user.id); + let oldUser = await this.findById(user.id); if (oldUser) { DEBUG('Update User'); oldUser = user; @@ -116,25 +97,32 @@ export default class LowDB extends DatabaseProvider { } } - async deleteUser(id: string) { + async delete(id: string) { DEBUG('Delete User'); const idx = this.#db.data.users.findIndex((user) => user.id === id); if (idx !== -1) { await this.#db.update((data) => data.users.splice(idx, 1)); } } +} - async getClients() { +export class LowDBClient extends ClientRepository { + #db: Low; + constructor(db: Low) { + super(); + this.#db = db; + } + async findAll() { DEBUG('GET Clients'); return this.#db.data.clients; } - async getClient(id: string) { + async findById(id: string) { DEBUG('Get Client'); return this.#db.data.clients[id]; } - async createClient(client: NewClient) { + async create(client: NewClient) { DEBUG('Create Client'); const now = new Date().toISOString(); const newClient: Client = { ...client, createdAt: now, updatedAt: now }; @@ -143,7 +131,7 @@ export default class LowDB extends DatabaseProvider { }); } - async deleteClient(id: string) { + async delete(id: string) { DEBUG('Delete Client'); await this.#db.update((data) => { // TODO: find something better than delete @@ -152,7 +140,7 @@ export default class LowDB extends DatabaseProvider { }); } - async toggleClient(id: string, enable: boolean) { + async toggle(id: string, enable: boolean) { DEBUG('Toggle Client'); await this.#db.update((data) => { if (data.clients[id]) { @@ -161,7 +149,7 @@ export default class LowDB extends DatabaseProvider { }); } - async updateClientName(id: string, name: string) { + async updateName(id: string, name: string) { DEBUG('Update Client Name'); await this.#db.update((data) => { if (data.clients[id]) { @@ -170,7 +158,7 @@ export default class LowDB extends DatabaseProvider { }); } - async updateClientAddress4(id: string, address4: string) { + async updateAddress4(id: string, address4: string) { DEBUG('Update Client Address4'); await this.#db.update((data) => { if (data.clients[id]) { @@ -179,7 +167,7 @@ export default class LowDB extends DatabaseProvider { }); } - async updateClientExpirationDate(id: string, expirationDate: string | null) { + async updateExpirationDate(id: string, expirationDate: string | null) { DEBUG('Update Client Expiration Date'); await this.#db.update((data) => { if (data.clients[id]) { @@ -211,3 +199,49 @@ export default class LowDB extends DatabaseProvider { }); } } + +export default class LowDB extends DatabaseProvider { + #db!: Low; + #connected = false; + + system!: LowDBSystem; + user!: LowDBUser; + client!: LowDBClient; + + /** + * @throws + */ + async connect() { + if (this.#connected) { + return; + } + try { + DEBUG('Connecting'); + this.#db = await JSONFilePreset( + '/etc/wireguard/db.json', + DEFAULT_DATABASE + ); + + DEBUG('Running Migrations'); + await migrationRunner(this.#db); + DEBUG('Migrations ran successfully'); + } catch (e) { + DEBUG(e); + throw new Error('Failed to initialize Database'); + } + this.system = new LowDBSystem(this.#db); + this.user = new LowDBUser(this.#db); + this.client = new LowDBClient(this.#db); + this.#connected = true; + DEBUG('Connected successfully'); + } + + get connected() { + return this.#connected; + } + + async disconnect() { + this.#connected = false; + DEBUG('Disconnected successfully'); + } +} diff --git a/src/services/database/migrations/1.ts b/src/services/database/migrations/1.ts index cf930703..39a9c6c2 100644 --- a/src/services/database/migrations/1.ts +++ b/src/services/database/migrations/1.ts @@ -72,31 +72,29 @@ export async function run1(db: Low) { }; // TODO: properly check if ipv6 support - database.system.iptables.PostUp = ` -iptables -t nat -A POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE; + database.system.iptables.PostUp = + `iptables -t nat -A POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -s ${database.system.userConfig.address6Range} -o ${database.system.wgDevice} -j MASQUERADE; ip6tables -A INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; ip6tables -A FORWARD -i wg0 -j ACCEPT; -ip6tables -A FORWARD -o wg0 -j ACCEPT; -` - .split('\n') - .join(' '); +ip6tables -A FORWARD -o wg0 -j ACCEPT;` + .split('\n') + .join(' '); - database.system.iptables.PostDown = ` -iptables -t nat -D POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE; + database.system.iptables.PostDown = + `iptables -t nat -D POSTROUTING -s ${database.system.userConfig.address4Range} -o ${database.system.wgDevice} -j MASQUERADE; iptables -D INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -s ${database.system.userConfig.address6Range} -o ${database.system.wgDevice} -j MASQUERADE; ip6tables -D INPUT -p udp -m udp --dport ${database.system.wgPort} -j ACCEPT; ip6tables -D FORWARD -i wg0 -j ACCEPT; -ip6tables -D FORWARD -o wg0 -j ACCEPT; -` - .split('\n') - .join(' '); +ip6tables -D FORWARD -o wg0 -j ACCEPT;` + .split('\n') + .join(' '); db.data = database; db.write(); diff --git a/src/services/database/repositories/client.ts b/src/services/database/repositories/client.ts index 9b8573cf..a5d2cc85 100644 --- a/src/services/database/repositories/client.ts +++ b/src/services/database/repositories/client.ts @@ -31,18 +31,21 @@ export type NewClient = Omit; * Interface for client-related database operations. * This interface provides methods for managing client data. */ -export interface ClientRepository { - getClients(): Promise>; - getClient(id: string): Promise; - createClient(client: NewClient): Promise; - deleteClient(id: string): Promise; - toggleClient(id: string, enable: boolean): Promise; - updateClientName(id: string, name: string): Promise; - updateClientAddress4(id: string, address4: string): Promise; - updateClientExpirationDate( +export abstract class ClientRepository { + abstract findAll(): Promise>; + abstract findById(id: string): Promise; + abstract create(client: NewClient): Promise; + abstract delete(id: string): Promise; + abstract toggle(id: string, enable: boolean): Promise; + abstract updateName(id: string, name: string): Promise; + abstract updateAddress4(id: string, address4: string): Promise; + abstract updateExpirationDate( id: string, expirationDate: string | null ): Promise; - deleteOneTimeLink(id: string): Promise; - createOneTimeLink(id: string, oneTimeLink: OneTimeLink): Promise; + abstract deleteOneTimeLink(id: string): Promise; + abstract createOneTimeLink( + id: string, + oneTimeLink: OneTimeLink + ): Promise; } diff --git a/src/services/database/repositories/database.ts b/src/services/database/repositories/database.ts index a834ba1e..4beaab90 100644 --- a/src/services/database/repositories/database.ts +++ b/src/services/database/repositories/database.ts @@ -1,9 +1,4 @@ -import type { - ClientRepository, - Client, - NewClient, - OneTimeLink, -} from './client'; +import type { ClientRepository, Client } from './client'; import type { System, SystemRepository } from './system'; import type { User, UserRepository } from './user'; @@ -29,9 +24,7 @@ export const DEFAULT_DATABASE: Database = { * **Note :** Always throw `DatabaseError` to ensure proper API error handling. * */ -export abstract class DatabaseProvider - implements SystemRepository, UserRepository, ClientRepository -{ +export abstract class DatabaseProvider { /** * Connects to the database. */ @@ -42,30 +35,9 @@ export abstract class DatabaseProvider */ abstract disconnect(): Promise; - abstract getSystem(): Promise; - - abstract getUsers(): Promise; - abstract getUser(id: string): Promise; - abstract createUser(username: string, password: string): Promise; - abstract updateUser(user: User): Promise; - abstract deleteUser(id: string): Promise; - - abstract getClients(): Promise>; - abstract getClient(id: string): Promise; - abstract createClient(client: NewClient): Promise; - abstract deleteClient(id: string): Promise; - abstract toggleClient(id: string, enable: boolean): Promise; - abstract updateClientName(id: string, name: string): Promise; - abstract updateClientAddress4(id: string, address4: string): Promise; - abstract updateClientExpirationDate( - id: string, - expirationDate: string | null - ): Promise; - abstract deleteOneTimeLink(id: string): Promise; - abstract createOneTimeLink( - id: string, - oneTimeLink: OneTimeLink - ): Promise; + abstract system: SystemRepository; + abstract user: UserRepository; + abstract client: ClientRepository; } /** diff --git a/src/services/database/repositories/system.ts b/src/services/database/repositories/system.ts index 6fae9c8f..cf9c8aab 100644 --- a/src/services/database/repositories/system.ts +++ b/src/services/database/repositories/system.ts @@ -80,9 +80,9 @@ export type System = { * This interface provides methods for retrieving system configuration data * and specific system properties, such as the language setting, from the database. */ -export interface SystemRepository { +export abstract class SystemRepository { /** * Retrieves the system configuration data from the database. */ - getSystem(): Promise; + abstract get(): Promise; } diff --git a/src/services/database/repositories/user.ts b/src/services/database/repositories/user.ts index f7a04b79..c09285e0 100644 --- a/src/services/database/repositories/user.ts +++ b/src/services/database/repositories/user.ts @@ -28,26 +28,26 @@ export type User = { * Interface for user-related database operations. * This interface provides methods for managing user data. */ -export interface UserRepository { +export abstract class UserRepository { /** * Retrieves all users from the database. */ - getUsers(): Promise; + abstract findAll(): Promise; /** * Retrieves a user by their ID or User object from the database. */ - getUser(id: string): Promise; + abstract findById(id: string): Promise; - createUser(username: string, password: string): Promise; + abstract create(username: string, password: string): Promise; /** * Updates a user in the database. */ - updateUser(user: User): Promise; + abstract update(user: User): Promise; /** * Deletes a user from the database. */ - deleteUser(id: string): Promise; + abstract delete(id: string): Promise; }