Browse Source

Merge 6134f549be into 68fde7d165

pull/1864/merge
Bernd Storath 1 month ago
committed by GitHub
parent
commit
0ca9800871
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      .vscode/settings.json
  2. 2
      src/app/components/Header/LangSelector.vue
  3. 3
      src/app/composables/useSubmit.ts
  4. 2
      src/app/pages/admin.vue
  5. 2
      src/app/pages/clients/[id].vue
  6. 4
      src/app/pages/index.vue
  7. 2
      src/app/pages/login.vue
  8. 8
      src/app/pages/me.vue
  9. 4
      src/app/pages/setup/migrate.vue
  10. 4
      src/app/stores/clients.ts
  11. 5
      src/app/utils/math.ts
  12. 2
      src/cli/index.ts
  13. 27
      src/eslint.config.js
  14. 11
      src/eslint.config.mjs
  15. 7
      src/nuxt.config.ts
  16. 8
      src/override.tsconfig.json
  17. 69
      src/server/api/me/totp.post.ts
  18. 5
      src/server/database/repositories/client/schema.ts
  19. 20
      src/server/database/repositories/client/types.ts
  20. 12
      src/server/database/repositories/general/types.ts
  21. 12
      src/server/database/repositories/interface/types.ts
  22. 4
      src/server/database/repositories/oneTimeLink/types.ts
  23. 26
      src/server/database/repositories/user/types.ts
  24. 4
      src/server/database/repositories/userConfig/types.ts
  25. 2
      src/server/routes/metrics/json.get.ts
  26. 8
      src/server/routes/metrics/prometheus.get.ts
  27. 2
      src/server/tsconfig.json
  28. 4
      src/server/utils/Database.ts
  29. 13
      src/server/utils/WireGuard.ts
  30. 9
      src/server/utils/cmd.ts
  31. 10
      src/server/utils/handler.ts
  32. 13
      src/server/utils/ip.ts
  33. 2
      src/server/utils/release.ts
  34. 9
      src/server/utils/template.ts
  35. 141
      src/server/utils/types.ts
  36. 2
      src/server/utils/wgHelper.ts
  37. 2
      src/shared/utils/permissions.ts
  38. 2
      src/tsconfig.json

6
.vscode/settings.json

@ -15,6 +15,9 @@
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
@ -29,5 +32,8 @@
"i18n-ally.sortKeys": false,
"i18n-ally.keepFulfilled": false,
"i18n-ally.keystyle": "nested",
"i18n-ally.regex.usageMatchAppend": [
"[^\\w\\d]\\$i18n\\(['\"`]({key})['\"`]"
],
"editor.gotoLocation.multipleDefinitions": "goto"
}

2
src/app/components/Header/LangSelector.vue

@ -37,7 +37,7 @@ const { locales, locale, setLocale } = useI18n();
const langProxy = ref(locale);
watchEffect(() => {
setLocale(langProxy.value);
void setLocale(langProxy.value);
});
const langs = locales.value.sort((a, b) => a.code.localeCompare(b.code));

3
src/app/composables/useSubmit.ts

@ -52,12 +52,13 @@ export function useSubmit<
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
await opts.revert(true, res as any);
} catch (e) {
if (e instanceof FetchError) {
toast.showToast({
type: 'error',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
message: e.data.message,
});
} else if (e instanceof Error) {

2
src/app/pages/admin.vue

@ -38,7 +38,7 @@
<script setup lang="ts">
const authStore = useAuthStore();
authStore.update();
void authStore.update();
const { t } = useI18n();

2
src/app/pages/clients/[id].vue

@ -134,7 +134,7 @@
<script lang="ts" setup>
const authStore = useAuthStore();
authStore.update();
void authStore.update();
const route = useRoute();
const id = route.params.id as string;

4
src/app/pages/index.vue

@ -29,7 +29,7 @@
<script setup lang="ts">
const authStore = useAuthStore();
authStore.update();
void authStore.update();
const globalStore = useGlobalStore();
const clientsStore = useClientsStore();
@ -39,7 +39,7 @@ const clientsStore = useClientsStore();
const intervalId = ref<NodeJS.Timeout | null>(null);
clientsStore.refresh();
void clientsStore.refresh();
onMounted(() => {
// TODO?: replace with websocket or similar

2
src/app/pages/login.vue

@ -68,7 +68,7 @@
<script setup lang="ts">
const authStore = useAuthStore();
authStore.update();
void authStore.update();
const toast = useToast();
const { t } = useI18n();

8
src/app/pages/me.vue

@ -120,7 +120,7 @@
import { encodeQR } from 'qr';
const authStore = useAuthStore();
authStore.update();
void authStore.update();
const name = ref(authStore.userData?.name);
const email = ref(authStore.userData?.email);
@ -151,6 +151,7 @@ const _updatePassword = useSubmit(
method: 'post',
},
{
// eslint-disable-next-line @typescript-eslint/require-await
revert: async () => {
currentPassword.value = '';
newPassword.value = '';
@ -175,6 +176,7 @@ const _setup2fa = useSubmit(
method: 'post',
},
{
// eslint-disable-next-line @typescript-eslint/require-await
revert: async (success, data) => {
if (success && data?.type === 'setup') {
const qrcode = encodeQR(data.uri, 'svg', {
@ -205,9 +207,9 @@ const _enable2fa = useSubmit(
{
revert: async (success, data) => {
if (success && data?.type === 'created') {
authStore.update();
twofa.value = null;
code.value = '';
await authStore.update();
}
},
}
@ -230,8 +232,8 @@ const _disable2fa = useSubmit(
{
revert: async (success, data) => {
if (success && data?.type === 'deleted') {
authStore.update();
disable2faPassword.value = '';
await authStore.update();
}
},
}

4
src/app/pages/setup/migrate.vue

@ -65,8 +65,8 @@ async function readFileContent(file: File): Promise<string> {
reader.onload = (event) => {
resolve(event.target?.result as string);
};
reader.onerror = (error) => {
reject(error);
reader.onerror = () => {
reject(reader.error ?? new Error('Failed to read file'));
};
reader.readAsText(file);
});

4
src/app/stores/clients.ts

@ -46,9 +46,9 @@ export const useClientsStore = defineStore('Clients', () => {
if (!clientsPersist.value[client.id]) {
clientsPersist.value[client.id] = {
transferRxHistory: Array(50).fill(0),
transferRxHistory: (Array(50) as number[]).fill(0),
transferRxPrevious: client.transferRx ?? 0,
transferTxHistory: Array(50).fill(0),
transferTxHistory: (Array(50) as number[]).fill(0),
transferTxPrevious: client.transferTx ?? 0,
transferRxCurrent: 0,
transferTxCurrent: 0,

5
src/app/utils/math.ts

@ -7,8 +7,7 @@ export function bytes(
if (bytes === 0) return '0 B';
if (Number.isNaN(bytes) && !Number.isFinite(bytes)) return 'NaN';
const k = kib ? 1024 : 1000;
const dm =
decimals != null && !Number.isNaN(decimals) && decimals >= 0 ? decimals : 2;
const dm = !Number.isNaN(decimals) && decimals >= 0 ? decimals : 2;
const sizes = kib
? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', 'BiB']
: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
@ -17,7 +16,7 @@ export function bytes(
const index = sizes.indexOf(maxunit);
if (index !== -1) i = index;
}
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i] ?? 'NaN'}`;
}
/**

2
src/cli/index.ts

@ -89,4 +89,4 @@ const main = defineCommand({
},
});
runMain(main);
void runMain(main);

27
src/eslint.config.js

@ -0,0 +1,27 @@
// @ts-check
import eslintConfigPrettier from 'eslint-config-prettier';
import withNuxt from './.nuxt/eslint.config.mjs';
export default withNuxt([
{
rules: {
'import/order': 'warn',
},
},
eslintConfigPrettier,
]).override('nuxt/typescript/rules', {
rules: {
'@typescript-eslint/restrict-template-expressions': [
'error',
{
allowAny: false,
allowBoolean: true,
allowNever: false,
allowNullish: false,
allowNumber: true,
allowRegExp: false,
},
],
},
});

11
src/eslint.config.mjs

@ -1,11 +0,0 @@
import eslintConfigPrettier from 'eslint-config-prettier';
import withNuxt from './.nuxt/eslint.config.mjs';
export default withNuxt([
{
rules: {
'import/order': 'warn',
},
},
eslintConfigPrettier,
]);

7
src/nuxt.config.ts

@ -88,4 +88,11 @@ export default defineNuxtConfig({
// for typecheck reasons (https://github.com/nuxt/cli/issues/323)
'#db': fileURLToPath(new URL('./server/database/', import.meta.url)),
},
eslint: {
config: {
typescript: {
tsconfigPath: './tsconfig.json',
},
},
},
});

8
src/override.tsconfig.json

@ -0,0 +1,8 @@
{
"compilerOptions": {
"noUncheckedIndexedAccess": true,
"useUnknownInCatchVariables": true,
"strictNullChecks": true,
"noFallthroughCasesInSwitch": true
}
}

69
src/server/api/me/totp.post.ts

@ -20,46 +20,49 @@ export default definePermissionEventHandler(
validateZod(UserUpdateTotpSchema, event)
);
const type = body.type;
checkPermissions(user);
if (body.type === 'setup') {
const key = new Secret({ size: 20 });
switch (type) {
case 'setup': {
const key = new Secret({ size: 20 });
const totp = new TOTP({
issuer: 'wg-easy',
label: user.username,
algorithm: 'SHA1',
digits: 6,
period: 30,
secret: key,
});
const totp = new TOTP({
issuer: 'wg-easy',
label: user.username,
algorithm: 'SHA1',
digits: 6,
period: 30,
secret: key,
});
await Database.users.updateTotpKey(user.id, key.base32);
await Database.users.updateTotpKey(user.id, key.base32);
return {
success: true,
type: 'setup',
key: key.base32,
uri: totp.toString(),
} as Response;
} else if (body.type === 'create') {
await Database.users.verifyTotp(user.id, body.code);
return {
success: true,
type: 'setup',
key: key.base32,
uri: totp.toString(),
} as Response;
}
case 'create': {
await Database.users.verifyTotp(user.id, body.code);
return {
success: true,
type: 'created',
} as Response;
} else if (body.type === 'delete') {
await Database.users.deleteTotpKey(user.id, body.currentPassword);
return {
success: true,
type: 'created',
} as Response;
}
case 'delete': {
await Database.users.deleteTotpKey(user.id, body.currentPassword);
return {
success: true,
type: 'deleted',
} as Response;
return {
success: true,
type: 'deleted',
} as Response;
}
}
throw createError({
statusCode: 400,
statusMessage: 'Invalid request',
});
assertUnreachable(type);
}
);

5
src/server/database/repositories/client/schema.ts

@ -49,10 +49,7 @@ export const client = sqliteTable('clients_table', {
});
export const clientsRelations = relations(client, ({ one }) => ({
oneTimeLink: one(oneTimeLink, {
fields: [client.id],
references: [oneTimeLink.id],
}),
oneTimeLink: one(oneTimeLink),
user: one(user, {
fields: [client.userId],
references: [user.id],

20
src/server/database/repositories/client/types.ts

@ -18,29 +18,29 @@ export type UpdateClientType = Omit<
>;
const name = z
.string({ message: t('zod.client.name') })
.min(1, t('zod.client.name'))
.string({ message: $i18n('zod.client.name') })
.min(1, $i18n('zod.client.name'))
.pipe(safeStringRefine);
// TODO?: validate iso string
const expiresAt = z
.string({ message: t('zod.client.expiresAt') })
.min(1, t('zod.client.expiresAt'))
.string({ message: $i18n('zod.client.expiresAt') })
.min(1, $i18n('zod.client.expiresAt'))
.pipe(safeStringRefine)
.nullable();
const address4 = z
.string({ message: t('zod.client.address4') })
.min(1, { message: t('zod.client.address4') })
.string({ message: $i18n('zod.client.address4') })
.min(1, { message: $i18n('zod.client.address4') })
.pipe(safeStringRefine);
const address6 = z
.string({ message: t('zod.client.address6') })
.min(1, { message: t('zod.client.address6') })
.string({ message: $i18n('zod.client.address6') })
.min(1, { message: $i18n('zod.client.address6') })
.pipe(safeStringRefine);
const serverAllowedIps = z.array(AddressSchema, {
message: t('zod.client.serverAllowedIps'),
message: $i18n('zod.client.serverAllowedIps'),
});
export const ClientCreateSchema = z.object({
@ -71,7 +71,7 @@ export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
);
// TODO: investigate if coerce is bad
const clientId = z.number({ message: t('zod.client.id'), coerce: true });
const clientId = z.number({ message: $i18n('zod.client.id'), coerce: true });
export const ClientGetSchema = z.object({
clientId: clientId,

12
src/server/database/repositories/general/types.ts

@ -4,13 +4,17 @@ import type { general } from './schema';
export type GeneralType = InferSelectModel<typeof general>;
const sessionTimeout = z.number({ message: t('zod.general.sessionTimeout') });
const sessionTimeout = z.number({
message: $i18n('zod.general.sessionTimeout'),
});
const metricsEnabled = z.boolean({ message: t('zod.general.metricsEnabled') });
const metricsEnabled = z.boolean({
message: $i18n('zod.general.metricsEnabled'),
});
const metricsPassword = z
.string({ message: t('zod.general.metricsPassword') })
.min(1, { message: t('zod.general.metricsPassword') })
.string({ message: $i18n('zod.general.metricsPassword') })
.min(1, { message: $i18n('zod.general.metricsPassword') })
.nullable();
export const GeneralUpdateSchema = z.object({

12
src/server/database/repositories/interface/types.ts

@ -16,14 +16,16 @@ export type InterfaceUpdateType = Omit<
>;
const device = z
.string({ message: t('zod.interface.device') })
.min(1, t('zod.interface.device'))
.string({ message: $i18n('zod.interface.device') })
.min(1, $i18n('zod.interface.device'))
.pipe(safeStringRefine);
const cidr = z
.string({ message: t('zod.interface.cidr') })
.min(1, { message: t('zod.interface.cidr') })
.refine((value) => isCidr(value), { message: t('zod.interface.cidrValid') })
.string({ message: $i18n('zod.interface.cidr') })
.min(1, { message: $i18n('zod.interface.cidr') })
.refine((value) => isCidr(value), {
message: $i18n('zod.interface.cidrValid'),
})
.pipe(safeStringRefine);
export const InterfaceUpdateSchema = schemaForType<InterfaceUpdateType>()(

4
src/server/database/repositories/oneTimeLink/types.ts

@ -5,8 +5,8 @@ import type { oneTimeLink } from './schema';
export type OneTimeLinkType = InferSelectModel<typeof oneTimeLink>;
const oneTimeLinkType = z
.string({ message: t('zod.otl') })
.min(1, t('zod.otl'))
.string({ message: $i18n('zod.otl') })
.min(1, $i18n('zod.otl'))
.pipe(safeStringRefine);
export const OneTimeLinkGetSchema = z.object({

26
src/server/database/repositories/user/types.ts

@ -5,20 +5,20 @@ import type { user } from './schema';
export type UserType = InferSelectModel<typeof user>;
const username = z
.string({ message: t('zod.user.username') })
.min(2, t('zod.user.username'))
.string({ message: $i18n('zod.user.username') })
.min(2, $i18n('zod.user.username'))
.pipe(safeStringRefine);
const password = z
.string({ message: t('zod.user.password') })
.min(12, t('zod.user.password'))
.string({ message: $i18n('zod.user.password') })
.min(12, $i18n('zod.user.password'))
.pipe(safeStringRefine);
const remember = z.boolean({ message: t('zod.user.remember') });
const remember = z.boolean({ message: $i18n('zod.user.remember') });
const totpCode = z
.string({ message: t('zod.user.totpCode') })
.min(6, t('zod.user.totpCode'))
.string({ message: $i18n('zod.user.totpCode') })
.min(6, $i18n('zod.user.totpCode'))
.pipe(safeStringRefine);
export const UserLoginSchema = z.object({
@ -35,18 +35,18 @@ export const UserSetupSchema = z
confirmPassword: password,
})
.refine((val) => val.password === val.confirmPassword, {
message: t('zod.user.passwordMatch'),
message: $i18n('zod.user.passwordMatch'),
});
const name = z
.string({ message: t('zod.user.name') })
.string({ message: $i18n('zod.user.name') })
.min(1, 'zod.user.name')
.pipe(safeStringRefine);
const email = z
.string({ message: t('zod.user.email') })
.min(5, t('zod.user.email'))
.email({ message: t('zod.user.emailInvalid') })
.string({ message: $i18n('zod.user.email') })
.min(5, $i18n('zod.user.email'))
.email({ message: $i18n('zod.user.emailInvalid') })
.pipe(safeStringRefine)
.nullable();
@ -62,7 +62,7 @@ export const UserUpdatePasswordSchema = z
confirmPassword: password,
})
.refine((val) => val.newPassword === val.confirmPassword, {
message: t('zod.user.passwordMatch'),
message: $i18n('zod.user.passwordMatch'),
});
export const UserUpdateTotpSchema = z.union([

4
src/server/database/repositories/userConfig/types.ts

@ -5,8 +5,8 @@ import type { userConfig } from './schema';
export type UserConfigType = InferSelectModel<typeof userConfig>;
const host = z
.string({ message: t('zod.userConfig.host') })
.min(1, t('zod.userConfig.host'))
.string({ message: $i18n('zod.userConfig.host') })
.min(1, $i18n('zod.userConfig.host'))
.pipe(safeStringRefine);
export const UserConfigSetupSchema = z.object({

2
src/server/routes/metrics/json.get.ts

@ -9,7 +9,7 @@ async function getMetricsJSON() {
let wireguardConnectedPeersCount = 0;
for (const client of clients) {
wireguardPeerCount++;
if (client.enabled === true) {
if (client.enabled) {
wireguardEnabledPeersCount++;
}
if (isPeerConnected(client)) {

8
src/server/routes/metrics/prometheus.get.ts

@ -14,7 +14,7 @@ async function getPrometheusResponse() {
const wireguardLatestHandshakeSeconds = [];
for (const client of clients) {
wireguardPeerCount++;
if (client.enabled === true) {
if (client.enabled) {
wireguardEnabledPeersCount++;
}
@ -55,15 +55,15 @@ async function getPrometheusResponse() {
'',
'# HELP wireguard_sent_bytes Bytes sent to the peer',
'# TYPE wireguard_sent_bytes counter',
`${wireguardSentBytes.join('\n')}`,
wireguardSentBytes.join('\n'),
'',
'# HELP wireguard_received_bytes Bytes received from the peer',
'# TYPE wireguard_received_bytes counter',
`${wireguardReceivedBytes.join('\n')}`,
wireguardReceivedBytes.join('\n'),
'',
'# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake',
'# TYPE wireguard_latest_handshake_seconds gauge',
`${wireguardLatestHandshakeSeconds.join('\n')}`,
wireguardLatestHandshakeSeconds.join('\n'),
];
return returnText.join('\n');

2
src/server/tsconfig.json

@ -1,3 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
"extends": ["../.nuxt/tsconfig.server.json", "../override.tsconfig.json"]
}

4
src/server/utils/Database.ts

@ -16,9 +16,9 @@ const nullObject = new Proxy(
// eslint-disable-next-line import/no-mutable-exports
let provider = nullObject as never as DBServiceType;
connect().then((db) => {
void connect().then((db) => {
provider = db;
WireGuard.Startup();
void WireGuard.Startup();
});
export default provider;

13
src/server/utils/WireGuard.ts

@ -158,10 +158,12 @@ class WireGuard {
WG_DEBUG(`Starting Wireguard Interface ${wgInterface.name}...`);
await this.#saveWireguardConfig(wgInterface);
await wg.down(wgInterface.name).catch(() => {});
await wg.up(wgInterface.name).catch((err) => {
await wg.up(wgInterface.name).catch((err: unknown) => {
if (
err &&
err.message &&
typeof err === 'object' &&
'message' in err &&
typeof err.message === 'string' &&
err.message.includes(`Cannot find device "${wgInterface.name}"`)
) {
throw new Error(
@ -181,13 +183,15 @@ class WireGuard {
}
// TODO: handle as worker_thread
// eslint-disable-next-line @typescript-eslint/require-await
async startCronJob() {
setIntervalImmediately(() => {
this.cronJob().catch((err) => {
this.cronJob().catch((err: unknown) => {
WG_DEBUG('Running Cron Job failed.');
console.error(err);
});
}, 60 * 1000);
return;
}
// Shutdown wireguard
@ -206,7 +210,7 @@ class WireGuard {
let needsSave = false;
// Expires Feature
for (const client of clients) {
if (client.enabled !== true) continue;
if (!client.enabled) continue;
if (
client.expiresAt !== null &&
new Date() > new Date(client.expiresAt)
@ -234,6 +238,7 @@ class WireGuard {
}
}
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (OLD_ENV.PASSWORD || OLD_ENV.PASSWORD_HASH) {
throw new Error(
`

9
src/server/utils/cmd.ts

@ -9,7 +9,7 @@ export function exec(
) {
if (typeof log === 'string') {
CMD_DEBUG(`$ ${log}`);
} else if (log === true) {
} else if (log) {
CMD_DEBUG(`$ ${cmd}`);
}
@ -24,8 +24,11 @@ export function exec(
shell: 'bash',
},
(err, stdout) => {
if (err) return reject(err);
return resolve(String(stdout).trim());
if (err) {
reject(err);
return;
}
resolve(String(stdout).trim());
}
);
});

10
src/server/utils/handler.ts

@ -99,16 +99,16 @@ export const defineSetupEventHandler = <
});
}
const validSetupSteps =
ValidSetupSteps[setup.step as keyof typeof ValidSetupSteps];
if (!validSetupSteps) {
if (!(setup.step in ValidSetupSteps)) {
throw createError({
statusCode: 500,
statusMessage: 'Invalid setup step',
});
}
const validSetupSteps =
ValidSetupSteps[setup.step as keyof typeof ValidSetupSteps];
if (!validSetupSteps.includes(step as never)) {
throw createError({
statusCode: 400,
@ -169,7 +169,7 @@ export const defineMetricsHandler = <
}
}
if (metricsConfig[type] !== true) {
if (!metricsConfig[type]) {
throw createError({
statusCode: 400,
statusMessage: 'Metrics not enabled',

13
src/server/utils/ip.ts

@ -110,11 +110,16 @@ function getPrivateInformation() {
ipv6: [],
};
}
if (family === 'IPv4') {
obj[name].ipv4.push(address);
} else if (family === 'IPv6') {
obj[name].ipv6.push(address);
switch (family) {
case 'IPv4':
obj[name].ipv4.push(address);
continue;
case 'IPv6':
obj[name].ipv6.push(address);
continue;
}
assertUnreachable(family);
}
}

2
src/server/utils/release.ts

@ -5,7 +5,7 @@ type GithubRelease = {
async function fetchLatestRelease() {
try {
const response = await $fetch<GithubRelease>(
const response = await $fetch<GithubRelease | ''>(
'https://api.github.com/repos/wg-easy/wg-easy/releases/latest',
{ method: 'get', timeout: 5000 }
);

9
src/server/utils/template.ts

@ -4,8 +4,13 @@ import type { InterfaceType } from '#db/repositories/interface/types';
* Replace all {{key}} in the template with the values[key]
*/
export function template(templ: string, values: Record<string, string>) {
return templ.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return values[key] !== undefined ? values[key] : match;
return templ.replace(/\{\{(\w+)\}\}/g, (match, ...keys: string[]) => {
const key = keys[0];
if (key && values[key] !== undefined) {
return values[key];
} else {
return match;
}
});
}

141
src/server/utils/types.ts

@ -1,59 +1,59 @@
import type { ZodSchema } from 'zod';
import z from 'zod';
import type { H3Event, EventHandlerRequest } from 'h3';
import type { H3Event } from 'h3';
export type ID = number;
/**
* return the string as is
*
* used for i18n ally
* does not translate
*/
export const t = (v: string) => v;
export function $i18n(text: string) {
return text;
}
export const safeStringRefine = z
.string()
.refine(
(v) => v !== '__proto__' && v !== 'constructor' && v !== 'prototype',
{ message: t('zod.stringMalformed') }
{ message: $i18n('zod.stringMalformed') }
);
export const EnabledSchema = z.boolean({ message: t('zod.enabled') });
export const EnabledSchema = z.boolean({ message: $i18n('zod.enabled') });
export const MtuSchema = z
.number({ message: t('zod.mtu') })
.min(1280, { message: t('zod.mtu') })
.max(9000, { message: t('zod.mtu') });
.number({ message: $i18n('zod.mtu') })
.min(1280, { message: $i18n('zod.mtu') })
.max(9000, { message: $i18n('zod.mtu') });
export const PortSchema = z
.number({ message: t('zod.port') })
.min(1, { message: t('zod.port') })
.max(65535, { message: t('zod.port') });
.number({ message: $i18n('zod.port') })
.min(1, { message: $i18n('zod.port') })
.max(65535, { message: $i18n('zod.port') });
export const PersistentKeepaliveSchema = z
.number({ message: t('zod.persistentKeepalive') })
.min(0, t('zod.persistentKeepalive'))
.max(65535, t('zod.persistentKeepalive'));
.number({ message: $i18n('zod.persistentKeepalive') })
.min(0, $i18n('zod.persistentKeepalive'))
.max(65535, $i18n('zod.persistentKeepalive'));
export const AddressSchema = z
.string({ message: t('zod.address') })
.min(1, { message: t('zod.address') })
.string({ message: $i18n('zod.address') })
.min(1, { message: $i18n('zod.address') })
.pipe(safeStringRefine);
export const DnsSchema = z
.array(AddressSchema, { message: t('zod.dns') })
.min(1, t('zod.dns'));
.array(AddressSchema, { message: $i18n('zod.dns') })
.min(1, $i18n('zod.dns'));
export const AllowedIpsSchema = z
.array(AddressSchema, { message: t('zod.allowedIps') })
.min(1, { message: t('zod.allowedIps') });
.array(AddressSchema, { message: $i18n('zod.allowedIps') })
.min(1, { message: $i18n('zod.allowedIps') });
export const FileSchema = z.object({
file: z.string({ message: t('zod.file') }),
file: z.string({ message: $i18n('zod.file') }),
});
export const HookSchema = z
.string({ message: t('zod.hook') })
.string({ message: $i18n('zod.hook') })
.pipe(safeStringRefine);
export const schemaForType =
@ -63,10 +63,7 @@ export const schemaForType =
return arg;
};
export function validateZod<T>(
schema: ZodSchema<T>,
event: H3Event<EventHandlerRequest>
) {
export function validateZod<T>(schema: ZodSchema<T>, event: H3Event) {
return async (data: unknown) => {
try {
return await schema.parseAsync(data);
@ -79,64 +76,60 @@ export function validateZod<T>(
.map((v) => {
let m = v.message;
if (t) {
let newMessage = null;
if (v.message.startsWith('zod.')) {
switch (v.code) {
case 'too_small':
switch (v.type) {
let newMessage = null;
if (v.message.startsWith('zod.')) {
switch (v.code) {
case 'too_small':
switch (v.type) {
case 'string':
newMessage = t('zod.generic.stringMin', [
t(v.message),
v.minimum,
]);
break;
case 'number':
newMessage = t('zod.generic.numberMin', [
t(v.message),
v.minimum,
]);
break;
}
break;
case 'invalid_type': {
if (v.received === 'null' || v.received === 'undefined') {
newMessage = t('zod.generic.required', [v.path.join('.')]);
} else {
switch (v.expected) {
case 'string':
newMessage = t('zod.generic.stringMin', [
newMessage = t('zod.generic.validString', [
t(v.message),
]);
break;
case 'boolean':
newMessage = t('zod.generic.validBoolean', [
t(v.message),
v.minimum,
]);
break;
case 'number':
newMessage = t('zod.generic.numberMin', [
newMessage = t('zod.generic.validNumber', [
t(v.message),
]);
break;
case 'array':
newMessage = t('zod.generic.validArray', [
t(v.message),
v.minimum,
]);
break;
}
break;
case 'invalid_type': {
if (v.received === 'null' || v.received === 'undefined') {
newMessage = t('zod.generic.required', [
v.path.join('.'),
]);
} else {
switch (v.expected) {
case 'string':
newMessage = t('zod.generic.validString', [
t(v.message),
]);
break;
case 'boolean':
newMessage = t('zod.generic.validBoolean', [
t(v.message),
]);
break;
case 'number':
newMessage = t('zod.generic.validNumber', [
t(v.message),
]);
break;
case 'array':
newMessage = t('zod.generic.validArray', [
t(v.message),
]);
break;
}
}
break;
}
break;
}
}
if (newMessage) {
m = newMessage;
} else {
m = t(v.message);
}
}
if (newMessage) {
m = newMessage;
} else {
m = t(v.message);
}
return m;

2
src/server/utils/wgHelper.ts

@ -10,7 +10,7 @@ export const wg = {
const allowedIps = [
`${client.ipv4Address}/32`,
`${client.ipv6Address}/128`,
...(client.serverAllowedIps ?? []),
...client.serverAllowedIps,
];
const extraLines = [];

2
src/shared/utils/permissions.ts

@ -134,7 +134,7 @@ export function hasPermissionsWithData<Resource extends keyof Permissions>(
) {
let checked = false;
return {
check(data?: Permissions[Resource]['dataType']) {
check(this: void, data?: Permissions[Resource]['dataType']) {
checked = true;
const isAllowed = hasPermissions(user, resource, action, data);

2
src/tsconfig.json

@ -1,4 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
"extends": ["./.nuxt/tsconfig.json", "./override.tsconfig.json"]
}

Loading…
Cancel
Save