Browse Source

more refactoring

pull/1864/head
Bernd Storath 3 months ago
parent
commit
6134f549be
  1. 3
      .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. 8
      src/override.tsconfig.json
  14. 69
      src/server/api/me/totp.post.ts
  15. 20
      src/server/database/repositories/client/types.ts
  16. 12
      src/server/database/repositories/general/types.ts
  17. 12
      src/server/database/repositories/interface/types.ts
  18. 4
      src/server/database/repositories/oneTimeLink/types.ts
  19. 26
      src/server/database/repositories/user/types.ts
  20. 4
      src/server/database/repositories/userConfig/types.ts
  21. 2
      src/server/routes/metrics/json.get.ts
  22. 2
      src/server/tsconfig.json
  23. 2
      src/server/utils/ip.ts
  24. 8
      src/server/utils/template.ts
  25. 46
      src/server/utils/types.ts
  26. 2
      src/tsconfig.json

3
.vscode/settings.json

@ -32,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

@ -131,7 +131,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

@ -117,7 +117,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);
@ -148,6 +148,7 @@ const _updatePassword = useSubmit(
method: 'post',
},
{
// eslint-disable-next-line @typescript-eslint/require-await
revert: async () => {
currentPassword.value = '';
newPassword.value = '';
@ -172,6 +173,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', {
@ -202,9 +204,9 @@ const _enable2fa = useSubmit(
{
revert: async (success, data) => {
if (success && data?.type === 'created') {
authStore.update();
twofa.value = null;
code.value = '';
await authStore.update();
}
},
}
@ -227,8 +229,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

@ -63,8 +63,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);

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);
}
);

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)) {

2
src/server/tsconfig.json

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

2
src/server/utils/ip.ts

@ -104,7 +104,7 @@ function getPrivateInformation() {
if (internal) {
continue;
}
if (!(name in obj)) {
if (!obj[name]) {
obj[name] = {
ipv4: [],
ipv6: [],

8
src/server/utils/template.ts

@ -4,11 +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: string) => {
if (key in values) {
return templ.replace(/\{\{(\w+)\}\}/g, (match, ...keys: string[]) => {
const key = keys[0];
if (key && values[key] !== undefined) {
return values[key];
} else {
return match;
}
return match;
});
}

46
src/server/utils/types.ts

@ -5,55 +5,55 @@ 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 =

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