From 4bbcc1c10a73c09a66547d5b426d8ffb979e9414 Mon Sep 17 00:00:00 2001 From: Bernd Storath <999999bst@gmail.com> Date: Tue, 11 Feb 2025 14:28:52 +0100 Subject: [PATCH] start improving zod translations --- src/i18n/locales/en.json | 26 +++++---- .../client/[clientId]/configuration.get.ts | 2 +- .../api/client/[clientId]/disable.post.ts | 2 +- .../api/client/[clientId]/enable.post.ts | 2 +- .../[clientId]/generateOneTimeLink.post.ts | 2 +- .../api/client/[clientId]/index.delete.ts | 2 +- .../api/client/[clientId]/index.post.ts | 2 +- .../api/client/[clientId]/qrcode.svg.get.ts | 2 +- src/server/api/client/index.post.ts | 2 +- src/server/api/me/index.post.ts | 7 ++- src/server/api/me/password.post.ts | 7 ++- src/server/api/setup/4.post.ts | 2 + src/server/api/setup/5.post.ts | 1 + .../database/repositories/client/types.ts | 8 +-- .../database/repositories/user/types.ts | 54 +++++++++-------- src/server/routes/cnf/[oneTimeLink].ts | 2 +- src/server/utils/types.ts | 58 ++++++++++++++++--- 17 files changed, 121 insertions(+), 60 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 29cfc7fa..301a808e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -39,6 +39,15 @@ "migration": "Restore the backup" }, "zod": { + "generic": { + "required": "{0} is required", + "validNumber": "{0} must be a valid number", + "validString": "{0} must be a valid string", + "validBoolean": "{0} must be a valid boolean", + "validStringArray": "{0} must be a valid array of strings", + "stringMin": "{0} must be at least {1} Character", + "numberMin": "{0} must be at least {1}" + }, "client": { "id": "Client ID must be a valid number", "name": "Name must be a valid string", @@ -52,20 +61,17 @@ "serverAllowedIps": "Allowed IPs must be a valid array of strings" }, "user": { - "username": "Username must be a valid string", - "usernameMin": "Username must be at least 8 Characters", - "password": "Password must be a valid string", - "passwordMin": "Password must be at least 12 Characters", + "username": "Username", + "password": "Password", "passwordUppercase": "Password must have at least 1 uppercase letter", "passwordLowercase": "Password must have at least 1 lowercase letter", "passwordNumber": "Password must have at least 1 number", "passwordSpecial": "Password must have at least 1 special character", - "remember": "Remember must be a valid boolean", - "accept": "Please accept the condition", - "name": "Name must be a valid string", - "nameMin": "Name must be at least 1 Character", - "email": "Email must be a valid string", - "emailMin": "Email must be at least 1 Character", + "remember": "Remember", + "accept": "Accept", + "acceptTrue": "Accept Conditions to continue", + "name": "Name", + "email": "Email", "emailInvalid": "Email must be a valid email", "passwordMatch": "Passwords must match" }, diff --git a/src/server/api/client/[clientId]/configuration.get.ts b/src/server/api/client/[clientId]/configuration.get.ts index 7faa531a..2fe407d5 100644 --- a/src/server/api/client/[clientId]/configuration.get.ts +++ b/src/server/api/client/[clientId]/configuration.get.ts @@ -6,7 +6,7 @@ export default definePermissionEventHandler( async ({ event, checkPermissions }) => { const { clientId } = await getValidatedRouterParams( event, - validateZod(ClientGetSchema) + validateZod(ClientGetSchema, event) ); const client = await Database.clients.get(clientId); checkPermissions(client); diff --git a/src/server/api/client/[clientId]/disable.post.ts b/src/server/api/client/[clientId]/disable.post.ts index 2e1a58c0..0338045f 100644 --- a/src/server/api/client/[clientId]/disable.post.ts +++ b/src/server/api/client/[clientId]/disable.post.ts @@ -6,7 +6,7 @@ export default definePermissionEventHandler( async ({ event, checkPermissions }) => { const { clientId } = await getValidatedRouterParams( event, - validateZod(ClientGetSchema) + validateZod(ClientGetSchema, event) ); const client = await Database.clients.get(clientId); diff --git a/src/server/api/client/[clientId]/enable.post.ts b/src/server/api/client/[clientId]/enable.post.ts index 2e1a58c0..0338045f 100644 --- a/src/server/api/client/[clientId]/enable.post.ts +++ b/src/server/api/client/[clientId]/enable.post.ts @@ -6,7 +6,7 @@ export default definePermissionEventHandler( async ({ event, checkPermissions }) => { const { clientId } = await getValidatedRouterParams( event, - validateZod(ClientGetSchema) + validateZod(ClientGetSchema, event) ); const client = await Database.clients.get(clientId); diff --git a/src/server/api/client/[clientId]/generateOneTimeLink.post.ts b/src/server/api/client/[clientId]/generateOneTimeLink.post.ts index 618addf5..9363ed93 100644 --- a/src/server/api/client/[clientId]/generateOneTimeLink.post.ts +++ b/src/server/api/client/[clientId]/generateOneTimeLink.post.ts @@ -6,7 +6,7 @@ export default definePermissionEventHandler( async ({ event, checkPermissions }) => { const { clientId } = await getValidatedRouterParams( event, - validateZod(ClientGetSchema) + validateZod(ClientGetSchema, event) ); const client = await Database.clients.get(clientId); diff --git a/src/server/api/client/[clientId]/index.delete.ts b/src/server/api/client/[clientId]/index.delete.ts index 171ad9e2..2267e89d 100644 --- a/src/server/api/client/[clientId]/index.delete.ts +++ b/src/server/api/client/[clientId]/index.delete.ts @@ -6,7 +6,7 @@ export default definePermissionEventHandler( async ({ event, checkPermissions }) => { const { clientId } = await getValidatedRouterParams( event, - validateZod(ClientGetSchema) + validateZod(ClientGetSchema, event) ); const client = await Database.clients.get(clientId); diff --git a/src/server/api/client/[clientId]/index.post.ts b/src/server/api/client/[clientId]/index.post.ts index 21bdd4e2..82f49b52 100644 --- a/src/server/api/client/[clientId]/index.post.ts +++ b/src/server/api/client/[clientId]/index.post.ts @@ -9,7 +9,7 @@ export default definePermissionEventHandler( async ({ event, checkPermissions }) => { const { clientId } = await getValidatedRouterParams( event, - validateZod(ClientGetSchema) + validateZod(ClientGetSchema, event) ); const data = await readValidatedBody( diff --git a/src/server/api/client/[clientId]/qrcode.svg.get.ts b/src/server/api/client/[clientId]/qrcode.svg.get.ts index 19ad85e6..6d25d577 100644 --- a/src/server/api/client/[clientId]/qrcode.svg.get.ts +++ b/src/server/api/client/[clientId]/qrcode.svg.get.ts @@ -6,7 +6,7 @@ export default definePermissionEventHandler( async ({ event, checkPermissions }) => { const { clientId } = await getValidatedRouterParams( event, - validateZod(ClientGetSchema) + validateZod(ClientGetSchema, event) ); const client = await Database.clients.get(clientId); diff --git a/src/server/api/client/index.post.ts b/src/server/api/client/index.post.ts index ea4940fe..930c625e 100644 --- a/src/server/api/client/index.post.ts +++ b/src/server/api/client/index.post.ts @@ -6,7 +6,7 @@ export default definePermissionEventHandler( async ({ event }) => { const { name, expiresAt } = await readValidatedBody( event, - validateZod(ClientCreateSchema) + validateZod(ClientCreateSchema, event) ); await Database.clients.create({ name, expiresAt }); diff --git a/src/server/api/me/index.post.ts b/src/server/api/me/index.post.ts index 10b338ef..daf19c55 100644 --- a/src/server/api/me/index.post.ts +++ b/src/server/api/me/index.post.ts @@ -3,11 +3,14 @@ import { UserUpdateSchema } from '#db/repositories/user/types'; export default definePermissionEventHandler( 'me', 'update', - async ({ event, user }) => { + async ({ event, user, checkPermissions }) => { const { name, email } = await readValidatedBody( event, - validateZod(UserUpdateSchema) + validateZod(UserUpdateSchema, event) ); + + checkPermissions(user); + await Database.users.update(user.id, name, email); return { success: true }; } diff --git a/src/server/api/me/password.post.ts b/src/server/api/me/password.post.ts index b7fad3d3..87ace186 100644 --- a/src/server/api/me/password.post.ts +++ b/src/server/api/me/password.post.ts @@ -3,11 +3,14 @@ import { UserUpdatePasswordSchema } from '#db/repositories/user/types'; export default definePermissionEventHandler( 'me', 'update', - async ({ event, user }) => { + async ({ event, user, checkPermissions }) => { const { newPassword, currentPassword } = await readValidatedBody( event, - validateZod(UserUpdatePasswordSchema) + validateZod(UserUpdatePasswordSchema, event) ); + + checkPermissions(user); + await Database.users.updatePassword(user.id, currentPassword, newPassword); return { success: true }; } diff --git a/src/server/api/setup/4.post.ts b/src/server/api/setup/4.post.ts index 4c8eb82a..0869b07f 100644 --- a/src/server/api/setup/4.post.ts +++ b/src/server/api/setup/4.post.ts @@ -6,6 +6,8 @@ export default defineSetupEventHandler(async ({ event }) => { validateZod(UserSetupSchema, event) ); + // TODO: validate setup step + await Database.users.create(username, password); await Database.general.setSetupStep(5); return { success: true }; diff --git a/src/server/api/setup/5.post.ts b/src/server/api/setup/5.post.ts index 4a2de39e..593fc1b0 100644 --- a/src/server/api/setup/5.post.ts +++ b/src/server/api/setup/5.post.ts @@ -5,6 +5,7 @@ export default defineSetupEventHandler(async ({ event }) => { event, validateZod(UserConfigSetupSchema, event) ); + // TODO: validate setup step await Database.userConfigs.updateHostPort(host, port); await Database.general.setSetupStep(0); return { success: true }; diff --git a/src/server/database/repositories/client/types.ts b/src/server/database/repositories/client/types.ts index 51c759ec..cdf1a0d0 100644 --- a/src/server/database/repositories/client/types.ts +++ b/src/server/database/repositories/client/types.ts @@ -18,13 +18,13 @@ export type UpdateClientType = Omit< >; const name = z - .string({ message: 'zod.client.name' }) - .min(1, 'zod.client.nameMin') + .string({ message: '!zod.generic.validString:zod.client.name' }) + .min(1, '!zod.generic.stringMinOne:zod.client.nameMin') .pipe(safeStringRefine); const expiresAt = z - .string({ message: 'zod.client.expireDate' }) - .min(1, 'zod.client.expireDateMin') + .string({ message: '!zod.generic.validString:zod.client.expireDate' }) + .min(1, '!zod.generic.stringMinOne:zod.client.expireDateMin') .pipe(safeStringRefine) .nullable(); diff --git a/src/server/database/repositories/user/types.ts b/src/server/database/repositories/user/types.ts index 5837b19e..7396d82b 100644 --- a/src/server/database/repositories/user/types.ts +++ b/src/server/database/repositories/user/types.ts @@ -4,33 +4,23 @@ import z from 'zod'; export type UserType = InferSelectModel; +const t = (v: string) => v; + const username = z - .string({ message: 'zod.user.username' }) - .min(8, 'zod.user.usernameMin') + .string({ message: t('zod.user.username') }) + .min(8, t('zod.user.username')) .pipe(safeStringRefine); const password = z - .string({ message: 'zod.user.password' }) - .min(12, 'zod.user.passwordMin') - .regex(/[A-Z]/, 'zod.user.passwordUppercase') - .regex(/[a-z]/, 'zod.user.passwordLowercase') - .regex(/\d/, 'zod.user.passwordNumber') - .regex(/[!@#$%^&*(),.?":{}|<>]/, 'zod.user.passwordSpecial') - .pipe(safeStringRefine); - -const remember = z.boolean({ message: 'zod.user.remember' }); - -const name = z - .string({ message: 'zod.user.name' }) - .min(1, 'zod.user.nameMin') + .string({ message: t('zod.user.password') }) + .min(12, t('zod.user.password')) + .regex(/[A-Z]/, t('zod.user.passwordUppercase')) + .regex(/[a-z]/, t('zod.user.passwordLowercase')) + .regex(/\d/, t('zod.user.passwordNumber')) + .regex(/[!@#$%^&*(),.?":{}|<>]/, t('zod.user.passwordSpecial')) .pipe(safeStringRefine); -const email = z - .string({ message: 'zod.user.email' }) - .min(5, 'zod.user.emailMin') - .email({ message: 'zod.user.emailInvalid' }) - .pipe(safeStringRefine) - .nullable(); +const remember = z.boolean({ message: t('zod.user.remember') }); export const UserLoginSchema = z.object( { @@ -41,9 +31,11 @@ export const UserLoginSchema = z.object( { message: objectMessage } ); -const accept = z.boolean().refine((val) => val === true, { - message: 'zod.user.accept', -}); +const accept = z + .boolean({ message: t('zod.user.accept') }) + .refine((val) => val === true, { + message: t('zod.user.acceptTrue'), + }); export const UserSetupSchema = z.object( { @@ -54,6 +46,18 @@ export const UserSetupSchema = z.object( { message: objectMessage } ); +const name = z + .string({ message: t('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') }) + .pipe(safeStringRefine) + .nullable(); + export const UserUpdateSchema = z.object( { name: name, @@ -72,5 +76,5 @@ export const UserUpdatePasswordSchema = z { message: objectMessage } ) .refine((val) => val.newPassword === val.confirmPassword, { - message: 'zod.user.passwordMatch', + message: t('zod.user.passwordMatch'), }); diff --git a/src/server/routes/cnf/[oneTimeLink].ts b/src/server/routes/cnf/[oneTimeLink].ts index 79592533..a7e6497b 100644 --- a/src/server/routes/cnf/[oneTimeLink].ts +++ b/src/server/routes/cnf/[oneTimeLink].ts @@ -3,7 +3,7 @@ import { OneTimeLinkGetSchema } from '#db/repositories/oneTimeLink/types'; export default defineEventHandler(async (event) => { const { oneTimeLink } = await getValidatedRouterParams( event, - validateZod(OneTimeLinkGetSchema) + validateZod(OneTimeLinkGetSchema, event) ); const clients = await WireGuard.getAllClients(); const client = clients.find( diff --git a/src/server/utils/types.ts b/src/server/utils/types.ts index 41607b52..87609c4d 100644 --- a/src/server/utils/types.ts +++ b/src/server/utils/types.ts @@ -52,26 +52,68 @@ export const schemaForType = export function validateZod( schema: ZodSchema, - event?: H3Event + event: H3Event ) { return async (data: unknown) => { - let t: null | ((key: string) => string) = null; - - if (event) { - t = await useTranslation(event); - } - try { return await schema.parseAsync(data); } catch (error) { let message = 'Unexpected Error'; if (error instanceof z.ZodError) { + const t = await useTranslation(event); + message = error.issues .map((v) => { let m = v.message; if (t) { - m = t(m); + 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.validString', [ + t(v.message), + ]); + break; + case 'boolean': + newMessage = t('zod.generic.validBoolean', [ + t(v.message), + ]); + break; + } + } + break; + } + } + } + if (newMessage) { + m = newMessage; + } else { + m = t(v.message); + } } return m;