Browse Source

Refactor New UI (#1342)

* refactor code

* refactor code

* add some todos

* update pnpm, start migrating to database

* add missing i18n key

* add todo

* basic setup styling
pull/1648/head
Bernd Storath 7 months ago
committed by Bernd Storath
parent
commit
2e6190eb0d
  1. 2
      README.md
  2. 2
      package.json
  3. 1
      src/i18n.config.ts
  4. 5241
      src/package-lock.json
  5. 2
      src/package.json
  6. 30
      src/pages/setup.vue
  7. 3
      src/server/api/cnf/[clientOneTimeLink].ts
  8. 1
      src/server/api/remember-me.get.ts
  9. 2
      src/server/api/session.post.ts
  10. 6
      src/server/api/ui-chart-type.get.ts
  11. 12
      src/server/api/ui-sort-clients.get.ts
  12. 2
      src/server/api/wg-enable-expire-time.get.ts
  13. 2
      src/server/api/wg-enable-one-time-links.get.ts
  14. 1
      src/server/middleware/setup.ts
  15. 3
      src/server/utils/Database.ts
  16. 8
      src/server/utils/WireGuard.ts
  17. 2
      src/server/utils/config.ts
  18. 51
      src/services/database/inmemory.ts
  19. 61
      src/services/database/lowdb.ts
  20. 31
      src/services/database/repositories/database.ts
  21. 146
      src/services/database/repositories/system.ts
  22. 55
      src/services/database/repositories/system/index.ts
  23. 45
      src/services/database/repositories/system/model.ts
  24. 22
      src/services/database/repositories/system/repository.ts
  25. 62
      src/services/database/repositories/types.ts
  26. 55
      src/services/database/repositories/user.ts
  27. 29
      src/services/database/repositories/user/model.ts
  28. 39
      src/services/database/repositories/user/repository.ts.ts

2
README.md

@ -26,7 +26,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
- Multilanguage Support
- Traffic Stats (default off)
- One Time Links (default off)
- Client Expiry (default off)
- Client Expiration (default off)
- Prometheus metrics support
## Requirements

2
package.json

@ -7,5 +7,5 @@
"sudostart": "sudo docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy",
"start": "docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy"
},
"packageManager": "pnpm@9.7.1"
"packageManager": "pnpm@9.9.0"
}

1
src/i18n.config.ts

@ -4,6 +4,7 @@ export default defineI18nConfig(() => ({
messages: {
en: {
name: 'Name',
username: 'Username',
password: 'Password',
signIn: 'Sign In',
logout: 'Logout',

5241
src/package-lock.json

File diff suppressed because it is too large

2
src/package.json

@ -51,5 +51,5 @@
"typescript": "^5.5.4",
"vue-tsc": "^2.0.29"
},
"packageManager": "pnpm@9.7.1"
"packageManager": "pnpm@9.9.0"
}

30
src/pages/setup.vue

@ -1,7 +1,15 @@
<template>
<main>
<div>
<h1>Welcome to your first setup of wg-easy !</h1>
<h1
class="text-4xl font-medium my-16 text-gray-700 dark:text-neutral-200 text-center"
>
<img src="/logo.png" width="32" class="inline align-middle dark:bg" />
<span class="align-middle">WireGuard</span>
</h1>
<div
class="shadow rounded-md bg-white dark:bg-neutral-700 mx-auto w-96 p-5 overflow-hidden mt-10 text-gray-700 dark:text-neutral-200"
>
<h2>Welcome to your first setup of wg-easy !</h2>
<p>Please first enter an admin username and a strong password.</p>
<form @submit="newAccount">
<div>
@ -12,6 +20,7 @@
type="text"
name="username"
autocomplete="username"
class="px-3 py-2 text-sm dark:bg-neutral-700 text-gray-500 dark:text-gray-500 mb-5 border-2 border-gray-100 dark:border-neutral-800 rounded-lg w-full focus:border-red-800 dark:focus:border-red-800 dark:placeholder:text-neutral-400 outline-none"
/>
</div>
<div>
@ -22,13 +31,26 @@
type="password"
name="password"
autocomplete="new-password"
class="px-3 py-2 text-sm dark:bg-neutral-700 text-gray-500 dark:text-gray-500 mb-5 border-2 border-gray-100 dark:border-neutral-800 rounded-lg w-full focus:border-red-800 dark:focus:border-red-800 dark:placeholder:text-neutral-400 outline-none"
/>
</div>
<div>
<label for="accept">I accept the condition.</label>
<label for="accept">I accept the condition</label>
<input id="accept" type="checkbox" name="accept" />
</div>
<button type="submit">Save</button>
<input
type="submit"
value="Save"
:class="[
{
'bg-red-800 dark:bg-red-800 hover:bg-red-700 dark:hover:bg-red-700 transition cursor-pointer':
password && username,
'bg-gray-200 dark:bg-neutral-800 cursor-not-allowed':
!password && !username,
},
'w-full rounded shadow py-2 text-sm text-white dark:text-white',
]"
/>
</form>
</div>
</main>

3
src/server/api/cnf/[clientOneTimeLink].ts

@ -5,12 +5,13 @@ export default defineEventHandler(async (event) => {
statusCode: 500,
statusMessage: 'Invalid',
});
if (!system.wgEnableOneTimeLinks) {
if (!system.oneTimeLinks.enabled) {
throw createError({
status: 404,
message: 'Invalid state',
});
}
// TODO: validate with zod
const clientOneTimeLink = getRouterParam(event, 'clientOneTimeLink');
const clients = await WireGuard.getClients();
const client = clients.find(

1
src/server/api/remember-me.get.ts

@ -1,4 +1,5 @@
export default defineEventHandler(async (event) => {
setHeader(event, 'Content-Type', 'application/json');
// TODO: enable by default
return MAX_AGE > 0;
});

2
src/server/api/session.post.ts

@ -22,8 +22,6 @@ export default defineEventHandler(async (event) => {
});
}
// TODO: timing againts timing attack
const system = await Database.getSystem();
if (!system)
throw createError({

6
src/server/api/ui-chart-type.get.ts

@ -7,9 +7,5 @@ export default defineEventHandler(async (event) => {
statusMessage: 'Invalid',
});
const number = system.trafficStats.type;
if (Number.isNaN(number)) {
return 0;
}
return number;
return system.trafficStats.type;
});

12
src/server/api/ui-sort-clients.get.ts

@ -1,5 +1,11 @@
export default defineEventHandler((event) => {
export default defineEventHandler(async (event) => {
setHeader(event, 'Content-Type', 'application/json');
const sort = UI_ENABLE_SORT_CLIENTS;
return sort === 'true' ? true : false;
const system = await Database.getSystem();
if (!system)
throw createError({
statusCode: 500,
statusMessage: 'Invalid',
});
return system.sortClients.enabled;
});

2
src/server/api/wg-enable-expire-time.get.ts

@ -7,5 +7,5 @@ export default defineEventHandler(async (event) => {
statusMessage: 'Invalid',
});
return system.wgEnableExpiresTime;
return system.clientExpiration.enabled;
});

2
src/server/api/wg-enable-one-time-links.get.ts

@ -7,5 +7,5 @@ export default defineEventHandler(async (event) => {
statusMessage: 'Invalid',
});
return system.wgEnableOneTimeLinks;
return system.oneTimeLinks.enabled;
});

1
src/server/middleware/setup.ts

@ -10,6 +10,7 @@ export default defineEventHandler(async (event) => {
}
const users = await Database.getUsers();
// TODO: better error messages for api requests
if (users.length === 0) {
return sendRedirect(event, '/setup', 302);
}

3
src/server/utils/Database.ts

@ -1,7 +1,6 @@
/**
* Changing the Database Provider
* This design allows for easy swapping of different database implementations.
*
*/
// import InMemory from '~/services/database/inmemory';
@ -14,4 +13,6 @@ provider.connect().catch((err) => {
process.exit(1);
});
// TODO: check if old config exists and tell user about migration path
export default provider;

8
src/server/utils/WireGuard.ts

@ -464,9 +464,13 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
async cronJobEveryMinute() {
const config = await this.getConfig();
const system = await Database.getSystem();
if (!system) {
throw new Error('Invalid Database');
}
let needSaveConfig = false;
// Expires Feature
if (WG_ENABLE_EXPIRES_TIME === 'true') {
if (system.clientExpiration.enabled) {
for (const client of Object.values(config.clients)) {
if (client.enabled !== true) continue;
if (
@ -481,7 +485,7 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
}
}
// One Time Link Feature
if (WG_ENABLE_ONE_TIME_LINKS === 'true') {
if (system.oneTimeLinks.enabled) {
for (const client of Object.values(config.clients)) {
if (
client.oneTimeLink !== null &&

2
src/server/utils/config.ts

@ -2,8 +2,6 @@ import type { SessionConfig } from 'h3';
import debug from 'debug';
export const PORT = process.env.PORT || '51821';
export const WEBUI_HOST = process.env.WEBUI_HOST || '0.0.0.0';
export const MAX_AGE = process.env.MAX_AGE
? parseInt(process.env.MAX_AGE, 10) * 60
: 0;

51
src/services/database/inmemory.ts

@ -1,44 +1,47 @@
import crypto from 'node:crypto';
import debug from 'debug';
import DatabaseProvider, { DatabaseError } from './repositories/database';
import {
DatabaseProvider,
DatabaseError,
DEFAULT_DATABASE,
} from './repositories/database';
import { hashPassword, isPasswordStrong } from '~/server/utils/password';
import { Lang } from './repositories/types';
import SYSTEM from './repositories/system';
import { DEFAULT_SYSTEM } from './repositories/system';
import type { User } from './repositories/user/model';
import type { ID } from './repositories/types';
import type { User } from './repositories/user';
const DEBUG = debug('InMemoryDB');
// In-Memory Database Provider
export default class InMemory extends DatabaseProvider {
#data = DEFAULT_DATABASE;
async connect() {
this.data.system = SYSTEM;
DEBUG('Connection done');
this.#data.system = DEFAULT_SYSTEM;
DEBUG('Connected successfully');
}
async disconnect() {
this.data = { system: null, users: [] };
DEBUG('Diconnect done');
this.#data = { system: null, users: [] };
DEBUG('Disconnected successfully');
}
async getSystem() {
DEBUG('Get System');
return this.data.system;
return this.#data.system;
}
async getLang() {
return this.data.system?.lang || Lang.EN;
return this.#data.system?.lang || 'en';
}
async getUsers() {
return this.data.users;
return this.#data.users;
}
async getUser(id: ID) {
async getUser(id: string) {
DEBUG('Get User');
return this.data.users.find((user) => user.id === id);
return this.#data.users.find((user) => user.id === id);
}
async newUserWithPassword(username: string, password: string) {
@ -52,7 +55,7 @@ export default class InMemory extends DatabaseProvider {
throw new DatabaseError(DatabaseError.ERROR_PASSWORD_REQ);
}
const isUserExist = this.data.users.find(
const isUserExist = this.#data.users.find(
(user) => user.username === username
);
if (isUserExist) {
@ -60,7 +63,7 @@ export default class InMemory extends DatabaseProvider {
}
const now = new Date();
const isUserEmpty = this.data.users.length === 0;
const isUserEmpty = this.#data.users.length === 0;
const newUser: User = {
id: crypto.randomUUID(),
@ -72,22 +75,22 @@ export default class InMemory extends DatabaseProvider {
updatedAt: now,
};
this.data.users.push(newUser);
this.#data.users.push(newUser);
}
async updateUser(user: User) {
let _user = await this.getUser(user.id);
if (_user) {
let oldUser = await this.getUser(user.id);
if (oldUser) {
DEBUG('Update User');
_user = user;
oldUser = user;
}
}
async deleteUser(id: ID) {
async deleteUser(id: string) {
DEBUG('Delete User');
const idx = this.data.users.findIndex((user) => user.id === id);
const idx = this.#data.users.findIndex((user) => user.id === id);
if (idx !== -1) {
this.data.users.splice(idx, 1);
this.#data.users.splice(idx, 1);
}
}
}

61
src/services/database/lowdb.ts

@ -2,33 +2,36 @@ import crypto from 'node:crypto';
import debug from 'debug';
import { join } from 'path';
import DatabaseProvider, { DatabaseError } from './repositories/database';
import {
DatabaseProvider,
DatabaseError,
DEFAULT_DATABASE,
} from './repositories/database';
import { hashPassword, isPasswordStrong } from '~/server/utils/password';
import { JSONFilePreset } from 'lowdb/node';
import { Lang } from './repositories/types';
import SYSTEM from './repositories/system';
import { DEFAULT_SYSTEM } from './repositories/system';
import type { User } from './repositories/user/model';
import type { DBData } from './repositories/database';
import type { ID } from './repositories/types';
import type { Low } from 'lowdb';
import type { User } from './repositories/user';
import type { Database } from './repositories/database';
const DEBUG = debug('LowDB');
export default class LowDB extends DatabaseProvider {
private _db!: Low<DBData>;
#db!: Low<Database>;
// is this really needed?
private async __init() {
// TODO: assume path to db file
const dbFilePath = join(WG_PATH, 'db.json');
this._db = await JSONFilePreset(dbFilePath, this.data);
this.#db = await JSONFilePreset(dbFilePath, DEFAULT_DATABASE);
}
async connect() {
try {
// load file db
await this._db.read();
DEBUG('Connection done');
await this.#db.read();
DEBUG('Connected successfully');
return;
} catch (error) {
DEBUG('Database does not exist : ', error);
@ -41,36 +44,38 @@ export default class LowDB extends DatabaseProvider {
throw new DatabaseError(DatabaseError.ERROR_INIT);
}
this._db.update((data) => (data.system = SYSTEM));
// TODO: move to DEFAULT_DATABASE
this.#db.update((data) => (data.system = DEFAULT_SYSTEM));
DEBUG('Connection done');
DEBUG('Connected successfully');
}
async disconnect() {
DEBUG('Diconnect done');
DEBUG('Disconnected successfully');
}
async getSystem() {
DEBUG('Get System');
return this._db.data.system;
return this.#db.data.system;
}
async getLang() {
return this._db.data.system?.lang || Lang.EN;
return this.#db.data.system?.lang || 'en';
}
async getUsers() {
return this._db.data.users;
return this.#db.data.users;
}
async getUser(id: ID) {
async getUser(id: string) {
DEBUG('Get User');
return this._db.data.users.find((user) => user.id === id);
return this.#db.data.users.find((user) => user.id === id);
}
async newUserWithPassword(username: string, password: string) {
DEBUG('New User');
// TODO: should be handled by zod. completely remove database error
if (username.length < 8) {
throw new DatabaseError(DatabaseError.ERROR_USERNAME_REQ);
}
@ -79,7 +84,7 @@ export default class LowDB extends DatabaseProvider {
throw new DatabaseError(DatabaseError.ERROR_PASSWORD_REQ);
}
const isUserExist = this._db.data.users.find(
const isUserExist = this.#db.data.users.find(
(user) => user.username === username
);
if (isUserExist) {
@ -87,7 +92,7 @@ export default class LowDB extends DatabaseProvider {
}
const now = new Date();
const isUserEmpty = this._db.data.users.length === 0;
const isUserEmpty = this.#db.data.users.length === 0;
const newUser: User = {
id: crypto.randomUUID(),
@ -99,23 +104,23 @@ export default class LowDB extends DatabaseProvider {
updatedAt: now,
};
this._db.update((data) => data.users.push(newUser));
this.#db.update((data) => data.users.push(newUser));
}
async updateUser(user: User) {
let _user = await this.getUser(user.id);
if (_user) {
let oldUser = await this.getUser(user.id);
if (oldUser) {
DEBUG('Update User');
_user = user;
this._db.write();
oldUser = user;
this.#db.write();
}
}
async deleteUser(id: ID) {
async deleteUser(id: string) {
DEBUG('Delete User');
const idx = this._db.data.users.findIndex((user) => user.id === id);
const idx = this.#db.data.users.findIndex((user) => user.id === id);
if (idx !== -1) {
this._db.update((data) => data.users.splice(idx, 1));
this.#db.update((data) => data.users.splice(idx, 1));
}
}
}

31
src/services/database/repositories/database.ts

@ -1,17 +1,19 @@
import type SystemRepository from './system/repository';
import type UserRepository from './user/repository.ts';
import type { Lang, ID } from './types';
import type { User } from './user/model';
import type { System } from './system/model';
// TODO: re-export type from /user & /system
import type { System, SystemRepository } from './system';
import type { User, UserRepository } from './user';
import type { Lang } from './types';
// Represent data structure
export type DBData = {
export type Database = {
// TODO: always return correct value, greatly improves code
system: System | null;
users: User[];
};
export const DEFAULT_DATABASE: Database = {
system: null,
users: [],
};
/**
* Abstract class for database operations.
* Provides methods to connect, disconnect, and interact with system and user data.
@ -19,11 +21,9 @@ export type DBData = {
* **Note :** Always throw `DatabaseError` to ensure proper API error handling.
*
*/
export default abstract class DatabaseProvider
export abstract class DatabaseProvider
implements SystemRepository, UserRepository
{
protected data: DBData = { system: null, users: [] };
/**
* Connects to the database.
*/
@ -35,16 +35,17 @@ export default abstract class DatabaseProvider
abstract disconnect(): Promise<void>;
abstract getSystem(): Promise<System | null>;
// TODO: remove
abstract getLang(): Promise<Lang>;
abstract getUsers(): Promise<Array<User>>;
abstract getUser(id: ID): Promise<User | undefined>;
abstract getUsers(): Promise<User[]>;
abstract getUser(id: string): Promise<User | undefined>;
abstract newUserWithPassword(
username: string,
password: string
): Promise<void>;
abstract updateUser(_user: User): Promise<void>;
abstract deleteUser(id: ID): Promise<void>;
abstract updateUser(user: User): Promise<void>;
abstract deleteUser(id: string): Promise<void>;
}
/**

146
src/services/database/repositories/system.ts

@ -0,0 +1,146 @@
import packageJson from '@/package.json';
import type { SessionConfig } from 'h3';
import type { Lang } from './types';
export type IpTables = {
PreUp: string;
PostUp: string;
PreDown: string;
PostDown: string;
};
export type WGInterface = {
privateKey: string;
publicKey: string;
address: string;
};
export type WGConfig = {
mtu: number;
persistentKeepalive: number;
rangeAddress: string;
defaultDns: string[];
allowedIps: string[];
};
export enum ChartType {
None = 0,
Line = 1,
Area = 2,
Bar = 3,
}
export type TrafficStats = {
enabled: boolean;
type: ChartType;
};
export type Prometheus = {
enabled: boolean;
password: string | null;
};
export type Feature = {
enabled: boolean;
};
/**
* Representing the WireGuard network configuration data structure of a computer interface system.
*/
export type System = {
interface: WGInterface;
release: string;
// maxAge
sessionTimeout: number;
lang: Lang;
userConfig: WGConfig;
wgPath: string;
wgDevice: string;
wgHost: string;
wgPort: number;
wgConfigPort: number;
iptables: IpTables;
trafficStats: TrafficStats;
clientExpiration: Feature;
oneTimeLinks: Feature;
sortClients: Feature;
prometheus: Prometheus;
sessionConfig: SessionConfig;
};
/**
* Interface for system-related database operations.
* This interface provides methods for retrieving system configuration data
* and specific system properties, such as the language setting, from the database.
*/
export interface SystemRepository {
/**
* Retrieves the system configuration data from the database.
*/
getSystem(): Promise<System | null>;
/**
* Retrieves the system's language setting.
*/
getLang(): Promise<Lang>;
}
// TODO: move to migration
export const DEFAULT_SYSTEM: System = {
release: packageJson.release.version,
interface: {
privateKey: '',
publicKey: '',
address: '10.8.0.1',
},
sessionTimeout: 3600, // 1 hour
lang: 'en',
userConfig: {
mtu: 1420,
persistentKeepalive: 0,
// TODO: assume handle CIDR to compute next ip in WireGuard
rangeAddress: '10.8.0.0/24',
defaultDns: ['1.1.1.1'],
allowedIps: ['0.0.0.0/0', '::/0'],
},
wgPath: WG_PATH,
wgDevice: 'wg0',
wgHost: WG_HOST || '',
wgPort: 51820,
wgConfigPort: 51820,
iptables: {
PreUp: '',
PostUp: '',
PreDown: '',
PostDown: '',
},
trafficStats: {
enabled: false,
type: ChartType.None,
},
clientExpiration: {
enabled: false,
},
oneTimeLinks: {
enabled: false,
},
sortClients: {
enabled: false,
},
prometheus: {
enabled: false,
password: null,
},
sessionConfig: {
password: getRandomHex(256),
name: 'wg-easy',
cookie: undefined,
},
};

55
src/services/database/repositories/system/index.ts

@ -1,55 +0,0 @@
import packageJson from '@/package.json';
import { ChartType, Lang } from '../types';
import type { System } from './model';
const DEFAULT_SYSTEM_MODEL: System = {
release: packageJson.release.version,
interface: {
privateKey: '',
publicKey: '',
address: '10.8.0.1',
},
port: PORT ? Number(PORT) : 51821,
webuiHost: '0.0.0.0',
sessionTimeout: 3600, // 1 hour
lang: Lang.EN,
userConfig: {
mtu: 1420,
persistentKeepalive: 0,
// TODO: assume handle CIDR to compute next ip in WireGuard
rangeAddress: '10.8.0.0/24',
defaultDns: ['1.1.1.1'],
allowedIps: ['0.0.0.0/0', '::/0'],
},
wgPath: WG_PATH,
wgDevice: 'wg0',
wgHost: WG_HOST || '',
wgPort: 51820,
wgConfigPort: 51820,
iptables: {
wgPreUp: '',
wgPostUp: '',
wgPreDown: '',
wgPostDown: '',
},
trafficStats: {
enabled: false,
type: ChartType.None,
},
wgEnableExpiresTime: false,
wgEnableOneTimeLinks: false,
wgEnableSortClients: false,
prometheus: {
enabled: false,
password: null,
},
sessionConfig: {
password: getRandomHex(256),
name: 'wg-easy',
cookie: undefined,
},
};
export default DEFAULT_SYSTEM_MODEL;

45
src/services/database/repositories/system/model.ts

@ -1,45 +0,0 @@
import type { SessionConfig } from 'h3';
import type {
Url,
IpTables,
Lang,
Port,
Prometheus,
SessionTimeOut,
TrafficStats,
Version,
WGConfig,
WGInterface,
} from '../types';
/**
* Representing the WireGuard network configuration data structure of a computer interface system.
*/
export type System = {
interface: WGInterface;
release: Version;
port: number;
webuiHost: string;
// maxAge
sessionTimeout: SessionTimeOut;
lang: Lang;
userConfig: WGConfig;
wgPath: string;
wgDevice: string;
wgHost: Url;
wgPort: Port;
wgConfigPort: Port;
iptables: IpTables;
trafficStats: TrafficStats;
wgEnableExpiresTime: boolean;
wgEnableOneTimeLinks: boolean;
wgEnableSortClients: boolean;
prometheus: Prometheus;
sessionConfig: SessionConfig;
};

22
src/services/database/repositories/system/repository.ts

@ -1,22 +0,0 @@
import type { Lang } from '../types';
import type { System } from './model';
/**
* Interface for system-related database operations.
* This interface provides methods for retrieving system configuration data
* and specific system properties, such as the language setting, from the database.
*/
export default interface SystemRepository {
/**
* Retrieves the system configuration data from the database.
* @returns {Promise<System | null>} A promise that resolves to the system data
* if found, or `undefined` if the system data is not available.
*/
getSystem(): Promise<System | null>;
/**
* Retrieves the system's language setting.
* @returns {Promise<Lang>} The current language setting of the system.
*/
getLang(): Promise<Lang>;
}

62
src/services/database/repositories/types.ts

@ -1,61 +1 @@
import type * as crypto from 'node:crypto';
export enum Lang {
/* english */
EN = 'en',
/* french */
FR = 'fr',
}
export type Ipv4 = `${number}.${number}.${number}.${number}`;
export type Ipv4CIDR = `${number}.${number}.${number}.${number}/${number}`;
export type Ipv6 =
`${string}:${string}:${string}:${string}:${string}:${string}:${string}:${string}`;
export type Ipv6CIDR =
`${string}:${string}:${string}:${string}:${string}:${string}:${string}:${string}/${number}`;
export type Address = Ipv4 | Ipv4CIDR | Ipv6 | Ipv6CIDR | '::/0';
export type UrlHttp = `http://${string}`;
export type UrlHttps = `https://${string}`;
export type Url = string | UrlHttp | UrlHttps | Address;
export type ID = crypto.UUID;
export type Version = string;
export type SessionTimeOut = number;
export type Port = number;
export type HashPassword = string;
export type Command = string;
export type Key = string;
export type IpTables = {
wgPreUp: Command;
wgPostUp: Command;
wgPreDown: Command;
wgPostDown: Command;
};
export type WGInterface = {
privateKey: Key;
publicKey: Key;
address: Address;
};
export type WGConfig = {
mtu: number;
persistentKeepalive: number;
rangeAddress: Address;
defaultDns: Array<Address>;
allowedIps: Array<Address>;
};
export enum ChartType {
None = 0,
Line = 1,
Area = 2,
Bar = 3,
}
export type TrafficStats = {
enabled: boolean;
type: ChartType;
};
export type Prometheus = {
enabled: boolean;
password: HashPassword | null;
};
export type Lang = 'en' | 'fr';

55
src/services/database/repositories/user.ts

@ -0,0 +1,55 @@
/**
* Represents user roles within the application, each with specific permissions :
*
* - `ADMIN`: Full permissions to all resources, including the app, database, etc
* - `EDITOR`: Granted write and read permissions on their own resources as well as
* `CLIENT` resources, but without `ADMIN` privileges
* - `CLIENT`: Granted write and read permissions only on their own resources.
*/
export type ROLE = 'ADMIN' | 'EDITOR' | 'CLIENT';
/**
* Representing a user data structure.
*/
export type User = {
id: string;
role: ROLE;
username: string;
password: string;
name?: string;
address?: string;
privateKey?: string;
publicKey?: string;
preSharedKey?: string;
createdAt: Date;
updatedAt: Date;
enabled: boolean;
};
/**
* Interface for user-related database operations.
* This interface provides methods for managing user data.
*/
export interface UserRepository {
/**
* Retrieves all users from the database.
*/
getUsers(): Promise<User[]>;
/**
* Retrieves a user by their ID or User object from the database.
*/
getUser(id: string): Promise<User | undefined>;
newUserWithPassword(username: string, password: string): Promise<void>;
/**
* Updates a user in the database.
*/
updateUser(user: User): Promise<void>;
/**
* Deletes a user from the database.
*/
deleteUser(id: string): Promise<void>;
}

29
src/services/database/repositories/user/model.ts

@ -1,29 +0,0 @@
import type { Address, ID, Key, HashPassword } from '../types';
/**
* Represents user roles within the application, each with specific permissions :
*
* - `ADMIN`: Full permissions to all resources, including the app, database, etc
* - `EDITOR`: Granted write and read permissions on their own resources as well as
* `CLIENT` resources, but without `ADMIN` privileges
* - `CLIENT`: Granted write and read permissions only on their own resources.
*/
export type ROLE = 'ADMIN' | 'EDITOR' | 'CLIENT';
/**
* Representing a user data structure.
*/
export type User = {
id: ID;
role: ROLE;
username: string;
password: HashPassword;
name?: string;
address?: Address;
privateKey?: Key;
publicKey?: Key;
preSharedKey?: string;
createdAt: Date;
updatedAt: Date;
enabled: boolean;
};

39
src/services/database/repositories/user/repository.ts.ts

@ -1,39 +0,0 @@
import type { ID } from '../types';
import type { User } from './model';
/**
* Interface for user-related database operations.
* This interface provides methods for managing user data.
*/
export default interface UserRepository {
/**
* Retrieves all users from the database.
* @returns {Promise<Array<User>>} A array of users data.
*/
getUsers(): Promise<Array<User>>;
/**
* Retrieves a user by their ID or User object from the database.
* @param {ID} id - The ID of the user or a User object.
* @returns {Promise<User | undefined>} A promise that resolves to the user data
* if found, or `undefined` if the user is not available.
*/
getUser(id: ID): Promise<User | undefined>;
newUserWithPassword(username: string, password: string): Promise<void>;
/**
* Updates a user in the database.
* @param {User} user - The user to be saved.
*
* @returns {Promise<void>} A promise that resolves when the operation is complete.
*/
updateUser(user: User): Promise<void>;
/**
* Deletes a user from the database.
* @param {ID} id - The ID of the user or a User object to be deleted.
* @returns {Promise<void>} A promise that resolves when the user has been deleted.
*/
deleteUser(id: ID): Promise<void>;
}
Loading…
Cancel
Save