Browse Source

Feat: Zod Generic String (#1661)

* start improving zod translations

* update zod translations
pull/1663/head
Bernd Storath 6 months ago
committed by GitHub
parent
commit
41fcd8fcda
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 132
      src/i18n/locales/en.json
  2. 2
      src/server/api/client/[clientId]/configuration.get.ts
  3. 2
      src/server/api/client/[clientId]/disable.post.ts
  4. 2
      src/server/api/client/[clientId]/enable.post.ts
  5. 2
      src/server/api/client/[clientId]/generateOneTimeLink.post.ts
  6. 2
      src/server/api/client/[clientId]/index.delete.ts
  7. 2
      src/server/api/client/[clientId]/index.post.ts
  8. 2
      src/server/api/client/[clientId]/qrcode.svg.get.ts
  9. 2
      src/server/api/client/index.post.ts
  10. 7
      src/server/api/me/index.post.ts
  11. 7
      src/server/api/me/password.post.ts
  12. 2
      src/server/api/setup/4.post.ts
  13. 1
      src/server/api/setup/5.post.ts
  14. 22
      src/server/database/repositories/client/types.ts
  15. 10
      src/server/database/repositories/general/types.ts
  16. 2
      src/server/database/repositories/hooks/types.ts
  17. 8
      src/server/database/repositories/interface/types.ts
  18. 4
      src/server/database/repositories/oneTimeLink/types.ts
  19. 52
      src/server/database/repositories/user/types.ts
  20. 4
      src/server/database/repositories/userConfig/types.ts
  21. 2
      src/server/routes/cnf/[oneTimeLink].ts
  22. 111
      src/server/utils/types.ts

132
src/i18n/locales/en.json

@ -38,73 +38,6 @@
"portPlaceholder": "443", "portPlaceholder": "443",
"migration": "Restore the backup" "migration": "Restore the backup"
}, },
"zod": {
"client": {
"id": "Client ID must be a valid number",
"name": "Name must be a valid string",
"nameMin": "Name must be at least 1 Character",
"expireDate": "expiredDate must be a valid string",
"expireDateMin": "expiredDate must be at least 1 Character",
"address4": "IPv4 Address must be a valid string",
"address4Min": "IPv4 Address must be a be at least 1 Character",
"address6": "IPv6 Address must be a valid string",
"address6Min": "IPv6 Address must be a be at least 1 Character",
"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",
"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",
"emailInvalid": "Email must be a valid email",
"passwordMatch": "Passwords must match"
},
"userConfig": {
"host": "Host must be a valid string",
"hostMin": "Host must contain at least 1 character"
},
"general": {
"sessionTimeout": "Session Timeout must be a valid number"
},
"interface": {
"cidr": "CIDR must be a valid string",
"cidrMin": "CIDR must be at least 1 Character",
"device": "Device must be a valid string",
"deviceMin": "Device must be at least 1 Character"
},
"otl": {
"otl": "oneTimeLink must be a valid string",
"otlMin": "oneTimeLink must be at least 1 Character"
},
"stringMalformed": "String is malformed",
"body": "Body must be a valid object",
"hook": "Hook must be a valid string",
"mtu": "MTU must be a valid number",
"mtuMin": "MTU must be at least 1280",
"mtuMax": "MTU must be at most 9000",
"port": "Port must be a valid number",
"portMin": "Port must be at least 1",
"portMax": "Port must be at most 65535",
"persistentKeepalive": "Persistent Keepalive must be a valid number",
"persistentKeepaliveMin": "Persistent Keepalive must be at least 0",
"persistentKeepaliveMax": "Persistent Keepalive must be at most 65535",
"address": "IP Address must be a valid string",
"addressMin": "IP Address must be a be at least 1 Character",
"dns": "DNS must be a valid array of strings",
"dnsMin": "DNS must have at least 1 item",
"allowedIps": "Allowed IPs must be a valid array of strings",
"allowedIpsMin": "Allowed IPs must have at least 1 item"
},
"name": "Name", "name": "Name",
"username": "Username", "username": "Username",
"signIn": "Sign In", "signIn": "Sign In",
@ -151,16 +84,67 @@
"clear": "Clear", "clear": "Clear",
"login": "Log in error" "login": "Log in error"
}, },
"general": {
"sessionTimeout": "Session Timeout",
"metrics": "Metrics",
"prometheus": "Prometheus",
"json": "JSON"
},
"form": { "form": {
"actions": "Actions", "actions": "Actions",
"save": "Save", "save": "Save",
"revert": "Revert" "revert": "Revert"
}, },
"password": "Password" "password": "Password",
"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",
"validArray": "{0} must be a valid array",
"stringMin": "{0} must be at least {1} Character",
"numberMin": "{0} must be at least {1}"
},
"client": {
"id": "Client ID",
"name": "Name",
"expiresAt": "Expires At",
"address4": "IPv4 Address",
"address6": "IPv6 Address",
"serverAllowedIps": "Server Allowed IPs"
},
"user": {
"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",
"accept": "Accept",
"acceptTrue": "Accept Conditions to continue",
"name": "Name",
"email": "Email",
"emailInvalid": "Email must be a valid email",
"passwordMatch": "Passwords must match"
},
"userConfig": {
"host": "Host"
},
"general": {
"sessionTimeout": "Session Timeout",
"metricsEnabled": "Metrics",
"metricsPassword": "Metrics Password"
},
"interface": {
"cidr": "CIDR",
"device": "Device"
},
"otl": "One Time link",
"stringMalformed": "String is malformed",
"body": "Body must be a valid object",
"hook": "Hook",
"enabled": "Enabled",
"mtu": "MTU",
"port": "Port",
"persistentKeepalive": "Persistent Keepalive",
"address": "IP Address",
"dns": "DNS",
"allowedIps": "Allowed IPs"
}
} }

2
src/server/api/client/[clientId]/configuration.get.ts

@ -6,7 +6,7 @@ export default definePermissionEventHandler(
async ({ event, checkPermissions }) => { async ({ event, checkPermissions }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(ClientGetSchema) validateZod(ClientGetSchema, event)
); );
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);
checkPermissions(client); checkPermissions(client);

2
src/server/api/client/[clientId]/disable.post.ts

@ -6,7 +6,7 @@ export default definePermissionEventHandler(
async ({ event, checkPermissions }) => { async ({ event, checkPermissions }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(ClientGetSchema) validateZod(ClientGetSchema, event)
); );
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);

2
src/server/api/client/[clientId]/enable.post.ts

@ -6,7 +6,7 @@ export default definePermissionEventHandler(
async ({ event, checkPermissions }) => { async ({ event, checkPermissions }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(ClientGetSchema) validateZod(ClientGetSchema, event)
); );
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);

2
src/server/api/client/[clientId]/generateOneTimeLink.post.ts

@ -6,7 +6,7 @@ export default definePermissionEventHandler(
async ({ event, checkPermissions }) => { async ({ event, checkPermissions }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(ClientGetSchema) validateZod(ClientGetSchema, event)
); );
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);

2
src/server/api/client/[clientId]/index.delete.ts

@ -6,7 +6,7 @@ export default definePermissionEventHandler(
async ({ event, checkPermissions }) => { async ({ event, checkPermissions }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(ClientGetSchema) validateZod(ClientGetSchema, event)
); );
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);

2
src/server/api/client/[clientId]/index.post.ts

@ -9,7 +9,7 @@ export default definePermissionEventHandler(
async ({ event, checkPermissions }) => { async ({ event, checkPermissions }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(ClientGetSchema) validateZod(ClientGetSchema, event)
); );
const data = await readValidatedBody( const data = await readValidatedBody(

2
src/server/api/client/[clientId]/qrcode.svg.get.ts

@ -6,7 +6,7 @@ export default definePermissionEventHandler(
async ({ event, checkPermissions }) => { async ({ event, checkPermissions }) => {
const { clientId } = await getValidatedRouterParams( const { clientId } = await getValidatedRouterParams(
event, event,
validateZod(ClientGetSchema) validateZod(ClientGetSchema, event)
); );
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);

2
src/server/api/client/index.post.ts

@ -6,7 +6,7 @@ export default definePermissionEventHandler(
async ({ event }) => { async ({ event }) => {
const { name, expiresAt } = await readValidatedBody( const { name, expiresAt } = await readValidatedBody(
event, event,
validateZod(ClientCreateSchema) validateZod(ClientCreateSchema, event)
); );
await Database.clients.create({ name, expiresAt }); await Database.clients.create({ name, expiresAt });

7
src/server/api/me/index.post.ts

@ -3,11 +3,14 @@ import { UserUpdateSchema } from '#db/repositories/user/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
'me', 'me',
'update', 'update',
async ({ event, user }) => { async ({ event, user, checkPermissions }) => {
const { name, email } = await readValidatedBody( const { name, email } = await readValidatedBody(
event, event,
validateZod(UserUpdateSchema) validateZod(UserUpdateSchema, event)
); );
checkPermissions(user);
await Database.users.update(user.id, name, email); await Database.users.update(user.id, name, email);
return { success: true }; return { success: true };
} }

7
src/server/api/me/password.post.ts

@ -3,11 +3,14 @@ import { UserUpdatePasswordSchema } from '#db/repositories/user/types';
export default definePermissionEventHandler( export default definePermissionEventHandler(
'me', 'me',
'update', 'update',
async ({ event, user }) => { async ({ event, user, checkPermissions }) => {
const { newPassword, currentPassword } = await readValidatedBody( const { newPassword, currentPassword } = await readValidatedBody(
event, event,
validateZod(UserUpdatePasswordSchema) validateZod(UserUpdatePasswordSchema, event)
); );
checkPermissions(user);
await Database.users.updatePassword(user.id, currentPassword, newPassword); await Database.users.updatePassword(user.id, currentPassword, newPassword);
return { success: true }; return { success: true };
} }

2
src/server/api/setup/4.post.ts

@ -6,6 +6,8 @@ export default defineSetupEventHandler(async ({ event }) => {
validateZod(UserSetupSchema, event) validateZod(UserSetupSchema, event)
); );
// TODO: validate setup step
await Database.users.create(username, password); await Database.users.create(username, password);
await Database.general.setSetupStep(5); await Database.general.setSetupStep(5);
return { success: true }; return { success: true };

1
src/server/api/setup/5.post.ts

@ -5,6 +5,7 @@ export default defineSetupEventHandler(async ({ event }) => {
event, event,
validateZod(UserConfigSetupSchema, event) validateZod(UserConfigSetupSchema, event)
); );
// TODO: validate setup step
await Database.userConfigs.updateHostPort(host, port); await Database.userConfigs.updateHostPort(host, port);
await Database.general.setSetupStep(0); await Database.general.setSetupStep(0);
return { success: true }; return { success: true };

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

@ -3,8 +3,6 @@ import z from 'zod';
import type { client } from './schema'; import type { client } from './schema';
export type ID = string;
export type ClientType = InferSelectModel<typeof client>; export type ClientType = InferSelectModel<typeof client>;
export type CreateClientType = Omit< export type CreateClientType = Omit<
@ -18,28 +16,28 @@ export type UpdateClientType = Omit<
>; >;
const name = z const name = z
.string({ message: 'zod.client.name' }) .string({ message: t('zod.client.name') })
.min(1, 'zod.client.nameMin') .min(1, t('zod.client.name'))
.pipe(safeStringRefine); .pipe(safeStringRefine);
const expiresAt = z const expiresAt = z
.string({ message: 'zod.client.expireDate' }) .string({ message: t('zod.client.expiresAt') })
.min(1, 'zod.client.expireDateMin') .min(1, t('zod.client.expiresAt'))
.pipe(safeStringRefine) .pipe(safeStringRefine)
.nullable(); .nullable();
const address4 = z const address4 = z
.string({ message: 'zod.client.address4' }) .string({ message: t('zod.client.address4') })
.min(1, { message: 'zod.client.address4Min' }) .min(1, { message: t('zod.client.address4') })
.pipe(safeStringRefine); .pipe(safeStringRefine);
const address6 = z const address6 = z
.string({ message: 'zod.client.address6' }) .string({ message: t('zod.client.address6') })
.min(1, { message: 'zod.client.address6Min' }) .min(1, { message: t('zod.client.address6') })
.pipe(safeStringRefine); .pipe(safeStringRefine);
const serverAllowedIps = z.array(AddressSchema, { const serverAllowedIps = z.array(AddressSchema, {
message: 'zod.serverAllowedIps', message: t('zod.client.serverAllowedIps'),
}); });
export const ClientCreateSchema = z.object({ export const ClientCreateSchema = z.object({
@ -65,7 +63,7 @@ export const ClientUpdateSchema = schemaForType<UpdateClientType>()(
); );
// TODO: investigate if coerce is bad // TODO: investigate if coerce is bad
const clientId = z.number({ message: 'zod.client.id', coerce: true }); const clientId = z.number({ message: t('zod.client.id'), coerce: true });
export const ClientGetSchema = z.object({ export const ClientGetSchema = z.object({
clientId: clientId, clientId: clientId,

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

@ -4,11 +4,13 @@ import z from 'zod';
export type GeneralType = InferSelectModel<typeof general>; export type GeneralType = InferSelectModel<typeof general>;
const sessionTimeout = z.number({ message: 'zod.general.sessionTimeout' }); const sessionTimeout = z.number({ message: t('zod.general.sessionTimeout') });
const metricsEnabled = z.boolean({ message: 'zod.general.metricsEnabled' });
const metricsEnabled = z.boolean({ message: t('zod.general.metricsEnabled') });
const metricsPassword = z const metricsPassword = z
.string({ message: 'zod.general.metricsPassword' }) .string({ message: t('zod.general.metricsPassword') })
.min(1, { message: 'zod.general.metricsPasswordMin' }) .min(1, { message: t('zod.general.metricsPassword') })
// TODO: validate argon2 regex? // TODO: validate argon2 regex?
.nullable(); .nullable();

2
src/server/database/repositories/hooks/types.ts

@ -6,7 +6,7 @@ export type HooksType = InferSelectModel<typeof hooks>;
export type HooksUpdateType = Omit<HooksType, 'id' | 'createdAt' | 'updatedAt'>; export type HooksUpdateType = Omit<HooksType, 'id' | 'createdAt' | 'updatedAt'>;
const hook = z.string({ message: 'zod.hook' }).pipe(safeStringRefine); const hook = z.string({ message: t('zod.hook') }).pipe(safeStringRefine);
export const HooksUpdateSchema = schemaForType<HooksUpdateType>()( export const HooksUpdateSchema = schemaForType<HooksUpdateType>()(
z.object({ z.object({

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

@ -15,13 +15,13 @@ export type InterfaceUpdateType = Omit<
>; >;
const device = z const device = z
.string({ message: 'zod.interface.device' }) .string({ message: t('zod.interface.device') })
.min(1, 'zod.interface.deviceMin') .min(1, t('zod.interface.device'))
.pipe(safeStringRefine); .pipe(safeStringRefine);
const cidr = z const cidr = z
.string({ message: 'zod.interface.cidr' }) .string({ message: t('zod.interface.cidr') })
.min(1, { message: 'zod.interface.cidrMin' }) .min(1, { message: t('zod.interface.cidr') })
.pipe(safeStringRefine); .pipe(safeStringRefine);
export const InterfaceUpdateSchema = schemaForType<InterfaceUpdateType>()( export const InterfaceUpdateSchema = schemaForType<InterfaceUpdateType>()(

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

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

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

@ -5,32 +5,20 @@ import z from 'zod';
export type UserType = InferSelectModel<typeof user>; export type UserType = InferSelectModel<typeof user>;
const username = z const username = z
.string({ message: 'zod.user.username' }) .string({ message: t('zod.user.username') })
.min(8, 'zod.user.usernameMin') .min(8, t('zod.user.username'))
.pipe(safeStringRefine); .pipe(safeStringRefine);
const password = z const password = z
.string({ message: 'zod.user.password' }) .string({ message: t('zod.user.password') })
.min(12, 'zod.user.passwordMin') .min(12, t('zod.user.password'))
.regex(/[A-Z]/, 'zod.user.passwordUppercase') .regex(/[A-Z]/, t('zod.user.passwordUppercase'))
.regex(/[a-z]/, 'zod.user.passwordLowercase') .regex(/[a-z]/, t('zod.user.passwordLowercase'))
.regex(/\d/, 'zod.user.passwordNumber') .regex(/\d/, t('zod.user.passwordNumber'))
.regex(/[!@#$%^&*(),.?":{}|<>]/, 'zod.user.passwordSpecial') .regex(/[!@#$%^&*(),.?":{}|<>]/, t('zod.user.passwordSpecial'))
.pipe(safeStringRefine); .pipe(safeStringRefine);
const remember = z.boolean({ message: 'zod.user.remember' }); const remember = z.boolean({ message: t('zod.user.remember') });
const name = z
.string({ message: 'zod.user.name' })
.min(1, 'zod.user.nameMin')
.pipe(safeStringRefine);
const email = z
.string({ message: 'zod.user.email' })
.min(5, 'zod.user.emailMin')
.email({ message: 'zod.user.emailInvalid' })
.pipe(safeStringRefine)
.nullable();
export const UserLoginSchema = z.object( export const UserLoginSchema = z.object(
{ {
@ -41,9 +29,11 @@ export const UserLoginSchema = z.object(
{ message: objectMessage } { message: objectMessage }
); );
const accept = z.boolean().refine((val) => val === true, { const accept = z
message: 'zod.user.accept', .boolean({ message: t('zod.user.accept') })
}); .refine((val) => val === true, {
message: t('zod.user.acceptTrue'),
});
export const UserSetupSchema = z.object( export const UserSetupSchema = z.object(
{ {
@ -54,6 +44,18 @@ export const UserSetupSchema = z.object(
{ message: objectMessage } { 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( export const UserUpdateSchema = z.object(
{ {
name: name, name: name,
@ -72,5 +74,5 @@ export const UserUpdatePasswordSchema = z
{ message: objectMessage } { message: objectMessage }
) )
.refine((val) => val.newPassword === val.confirmPassword, { .refine((val) => val.newPassword === val.confirmPassword, {
message: 'zod.user.passwordMatch', message: t('zod.user.passwordMatch'),
}); });

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

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

2
src/server/routes/cnf/[oneTimeLink].ts

@ -3,7 +3,7 @@ import { OneTimeLinkGetSchema } from '#db/repositories/oneTimeLink/types';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const { oneTimeLink } = await getValidatedRouterParams( const { oneTimeLink } = await getValidatedRouterParams(
event, event,
validateZod(OneTimeLinkGetSchema) validateZod(OneTimeLinkGetSchema, event)
); );
const clients = await WireGuard.getAllClients(); const clients = await WireGuard.getAllClients();
const client = clients.find( const client = clients.find(

111
src/server/utils/types.ts

@ -2,46 +2,53 @@ import type { ZodSchema } from 'zod';
import z from 'zod'; import z from 'zod';
import type { H3Event, EventHandlerRequest } from 'h3'; import type { H3Event, EventHandlerRequest } from 'h3';
export const objectMessage = 'zod.body'; /**
* return the string as is
*
* used for i18n ally
*/
export const t = (v: string) => v;
export const objectMessage = t('zod.body');
export const safeStringRefine = z export const safeStringRefine = z
.string() .string()
.refine( .refine(
(v) => v !== '__proto__' && v !== 'constructor' && v !== 'prototype', (v) => v !== '__proto__' && v !== 'constructor' && v !== 'prototype',
{ message: 'zod.stringMalformed' } { message: t('zod.stringMalformed') }
); );
// TODO: create custom getValidatedRouterParams and readValidatedBody wrapper // TODO: create custom getValidatedRouterParams and readValidatedBody wrapper
export const EnabledSchema = z.boolean({ message: 'zod.enabled' }); export const EnabledSchema = z.boolean({ message: t('zod.enabled') });
export const MtuSchema = z export const MtuSchema = z
.number({ message: 'zod.mtu' }) .number({ message: t('zod.mtu') })
.min(1280, { message: 'zod.mtuMin' }) .min(1280, { message: t('zod.mtu') })
.max(9000, { message: 'zod.mtuMax' }); .max(9000, { message: t('zod.mtu') });
export const PortSchema = z export const PortSchema = z
.number({ message: 'zod.port' }) .number({ message: t('zod.port') })
.min(1, { message: 'zod.portMin' }) .min(1, { message: t('zod.port') })
.max(65535, { message: 'zod.portMax' }); .max(65535, { message: t('zod.port') });
export const PersistentKeepaliveSchema = z export const PersistentKeepaliveSchema = z
.number({ message: 'zod.persistentKeepalive' }) .number({ message: t('zod.persistentKeepalive') })
.min(0, 'zod.persistentKeepaliveMin') .min(0, t('zod.persistentKeepalive'))
.max(65535, 'zod.persistentKeepaliveMax'); .max(65535, t('zod.persistentKeepalive'));
export const AddressSchema = z export const AddressSchema = z
.string({ message: 'zod.address' }) .string({ message: t('zod.address') })
.min(1, { message: 'zod.addressMin' }) .min(1, { message: t('zod.address') })
.pipe(safeStringRefine); .pipe(safeStringRefine);
export const DnsSchema = z export const DnsSchema = z
.array(AddressSchema, { message: 'zod.dns' }) .array(AddressSchema, { message: t('zod.dns') })
.min(1, 'zod.dnsMin'); .min(1, t('zod.dns'));
export const AllowedIpsSchema = z export const AllowedIpsSchema = z
.array(AddressSchema, { message: 'zod.allowedIps' }) .array(AddressSchema, { message: t('zod.allowedIps') })
.min(1, { message: 'zod.allowedIpsMin' }); .min(1, { message: t('zod.allowedIps') });
export const schemaForType = export const schemaForType =
<T>() => <T>() =>
@ -52,26 +59,78 @@ export const schemaForType =
export function validateZod<T>( export function validateZod<T>(
schema: ZodSchema<T>, schema: ZodSchema<T>,
event?: H3Event<EventHandlerRequest> event: H3Event<EventHandlerRequest>
) { ) {
return async (data: unknown) => { return async (data: unknown) => {
let t: null | ((key: string) => string) = null;
if (event) {
t = await useTranslation(event);
}
try { try {
return await schema.parseAsync(data); return await schema.parseAsync(data);
} catch (error) { } catch (error) {
let message = 'Unexpected Error'; let message = 'Unexpected Error';
if (error instanceof z.ZodError) { if (error instanceof z.ZodError) {
const t = await useTranslation(event);
message = error.issues message = error.issues
.map((v) => { .map((v) => {
let m = v.message; let m = v.message;
if (t) { 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;
case 'number':
newMessage = t('zod.generic.validNumber', [
t(v.message),
]);
break;
case 'array':
newMessage = t('zod.generic.validArray', [
t(v.message),
]);
break;
}
}
break;
}
}
}
if (newMessage) {
m = newMessage;
} else {
m = t(v.message);
}
} }
return m; return m;

Loading…
Cancel
Save