diff --git a/package.json b/package.json index 610df079..5d8e09cf 100644 --- a/package.json +++ b/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.6.0" -} \ No newline at end of file + "packageManager": "pnpm@9.7.0" +} diff --git a/src/package.json b/src/package.json index 07c40a46..957ca52c 100644 --- a/src/package.json +++ b/src/package.json @@ -28,7 +28,8 @@ "tailwindcss": "^3.4.7", "timeago.js": "^4.0.2", "vue": "latest", - "vue3-apexcharts": "^1.5.3" + "vue3-apexcharts": "^1.5.3", + "zod": "^3.23.8" }, "devDependencies": { "@nuxt/eslint-config": "^0.5.0", @@ -41,5 +42,5 @@ "typescript": "^5.5.4", "vue-tsc": "^2.0.29" }, - "packageManager": "pnpm@9.6.0" + "packageManager": "pnpm@9.7.0" } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index 801a17a7..05208630 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -43,6 +43,9 @@ importers: vue3-apexcharts: specifier: ^1.5.3 version: 1.5.3(apexcharts@3.51.0)(vue@3.4.35(typescript@5.5.4)) + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@nuxt/eslint-config': specifier: ^0.5.0 @@ -7405,6 +7408,12 @@ packages: } engines: { node: '>= 14' } + zod@3.23.8: + resolution: + { + integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==, + } + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -12213,3 +12222,5 @@ snapshots: archiver-utils: 5.0.2 compress-commons: 6.0.2 readable-stream: 4.5.2 + + zod@3.23.8: {} diff --git a/src/server/api/session.post.ts b/src/server/api/session.post.ts index c166fb79..c267712c 100644 --- a/src/server/api/session.post.ts +++ b/src/server/api/session.post.ts @@ -1,6 +1,9 @@ export default defineEventHandler(async (event) => { const session = await useWGSession(event); - const { password } = await readBody(event); + const { password } = await readValidatedBody( + event, + validateZod(passwordType) + ); if (!REQUIRES_PASSWORD) { // if no password is required, the API should never be called. diff --git a/src/server/api/wireguard/client/[clientId]/address.put.ts b/src/server/api/wireguard/client/[clientId]/address.put.ts index ac5676dc..eb834521 100644 --- a/src/server/api/wireguard/client/[clientId]/address.put.ts +++ b/src/server/api/wireguard/client/[clientId]/address.put.ts @@ -1,13 +1,9 @@ export default defineEventHandler(async (event) => { - const clientId = getRouterParam(event, 'clientId'); - if ( - clientId === '__proto__' || - clientId === 'constructor' || - clientId === 'prototype' - ) { - throw createError({ statusCode: 403 }); - } - const { address } = await readBody(event); + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + const { address } = await readValidatedBody(event, validateZod(addressType)); await WireGuard.updateClientAddress({ clientId, address }); return { success: true }; }); diff --git a/src/server/api/wireguard/client/[clientId]/configuration.get.ts b/src/server/api/wireguard/client/[clientId]/configuration.get.ts index 78fd04db..2a404f08 100644 --- a/src/server/api/wireguard/client/[clientId]/configuration.get.ts +++ b/src/server/api/wireguard/client/[clientId]/configuration.get.ts @@ -1,5 +1,8 @@ export default defineEventHandler(async (event) => { - const clientId = getRouterParam(event, 'clientId'); + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); const client = await WireGuard.getClient({ clientId }); const config = await WireGuard.getClientConfiguration({ clientId }); const configName = client.name diff --git a/src/server/api/wireguard/client/[clientId]/disable.post.ts b/src/server/api/wireguard/client/[clientId]/disable.post.ts index 50aece6b..14d1bc68 100644 --- a/src/server/api/wireguard/client/[clientId]/disable.post.ts +++ b/src/server/api/wireguard/client/[clientId]/disable.post.ts @@ -1,12 +1,8 @@ export default defineEventHandler(async (event) => { - const clientId = getRouterParam(event, 'clientId'); - if ( - clientId === '__proto__' || - clientId === 'constructor' || - clientId === 'prototype' - ) { - throw createError({ statusCode: 403 }); - } + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); await WireGuard.disableClient({ clientId }); return { success: true }; }); diff --git a/src/server/api/wireguard/client/[clientId]/enable.post.ts b/src/server/api/wireguard/client/[clientId]/enable.post.ts index 4cd510f2..1064a872 100644 --- a/src/server/api/wireguard/client/[clientId]/enable.post.ts +++ b/src/server/api/wireguard/client/[clientId]/enable.post.ts @@ -1,12 +1,8 @@ export default defineEventHandler(async (event) => { - const clientId = getRouterParam(event, 'clientId'); - if ( - clientId === '__proto__' || - clientId === 'constructor' || - clientId === 'prototype' - ) { - throw createError({ statusCode: 403 }); - } + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); await WireGuard.enableClient({ clientId }); return { success: true }; }); diff --git a/src/server/api/wireguard/client/[clientId]/index.delete.ts b/src/server/api/wireguard/client/[clientId]/index.delete.ts index 60f45785..68a1d678 100644 --- a/src/server/api/wireguard/client/[clientId]/index.delete.ts +++ b/src/server/api/wireguard/client/[clientId]/index.delete.ts @@ -1,5 +1,8 @@ export default defineEventHandler(async (event) => { - const clientId = getRouterParam(event, 'clientId'); + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); await WireGuard.deleteClient({ clientId }); return { success: true }; }); diff --git a/src/server/api/wireguard/client/[clientId]/name.put.ts b/src/server/api/wireguard/client/[clientId]/name.put.ts index 4069ac7c..0cbffd3b 100644 --- a/src/server/api/wireguard/client/[clientId]/name.put.ts +++ b/src/server/api/wireguard/client/[clientId]/name.put.ts @@ -1,13 +1,9 @@ export default defineEventHandler(async (event) => { - const clientId = getRouterParam(event, 'clientId'); - if ( - clientId === '__proto__' || - clientId === 'constructor' || - clientId === 'prototype' - ) { - throw createError({ statusCode: 403 }); - } - const { name } = await readBody(event); + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); + const { name } = await readValidatedBody(event, validateZod(nameType)); await WireGuard.updateClientName({ clientId, name }); return { success: true }; }); diff --git a/src/server/api/wireguard/client/[clientId]/qrcode.svg.get.ts b/src/server/api/wireguard/client/[clientId]/qrcode.svg.get.ts index ca1b2601..dc21fb84 100644 --- a/src/server/api/wireguard/client/[clientId]/qrcode.svg.get.ts +++ b/src/server/api/wireguard/client/[clientId]/qrcode.svg.get.ts @@ -1,5 +1,8 @@ export default defineEventHandler(async (event) => { - const clientId = getRouterParam(event, 'clientId'); + const { clientId } = await getValidatedRouterParams( + event, + validateZod(clientIdType) + ); const svg = await WireGuard.getClientQRCodeSVG({ clientId }); setHeader(event, 'Content-Type', 'image/svg+xml'); return svg; diff --git a/src/server/api/wireguard/client/index.post.ts b/src/server/api/wireguard/client/index.post.ts index 83d5439c..5b9d3320 100644 --- a/src/server/api/wireguard/client/index.post.ts +++ b/src/server/api/wireguard/client/index.post.ts @@ -1,5 +1,5 @@ export default defineEventHandler(async (event) => { - const { name } = await readBody(event); + const { name } = await readValidatedBody(event, validateZod(nameType)); await WireGuard.createClient({ name }); return { success: true }; }); diff --git a/src/server/api/wireguard/restore.put.ts b/src/server/api/wireguard/restore.put.ts index 709c1ece..0d902c4e 100644 --- a/src/server/api/wireguard/restore.put.ts +++ b/src/server/api/wireguard/restore.put.ts @@ -1,5 +1,5 @@ export default defineEventHandler(async (event) => { - const { file } = await readBody(event); + const { file } = await readValidatedBody(event, validateZod(fileType)); await WireGuard.restoreConfiguration(file); return { success: true }; }); diff --git a/src/server/utils/types.ts b/src/server/utils/types.ts new file mode 100644 index 00000000..254a5ba7 --- /dev/null +++ b/src/server/utils/types.ts @@ -0,0 +1,65 @@ +import type { ZodSchema } from 'zod'; +import { z, ZodError } from 'zod'; + +const safeStringRefine = z + .string() + .refine( + (v) => v !== '__proto__' && v !== 'constructor' && v !== 'prototype', + { message: 'String is malformed' } + ); + +const id = z + .string() + .uuid('Client ID must be a valid UUID') + .and(safeStringRefine); + +const address = z + .string({ message: 'Address must be a valid string' }) + .and(safeStringRefine); + +const name = z + .string({ message: 'Name must be a valid string' }) + .min(1, 'Name must be at least 1 Character') + .and(safeStringRefine); + +const file = z + .string({ message: 'File must be a valid string' }) + .and(safeStringRefine); + +const password = z + .string({ message: 'Password must be a valid string' }) + .and(safeStringRefine); + +export const clientIdType = z.object({ + clientId: id, +}); + +export const addressType = z.object({ + address: address, +}); + +export const nameType = z.object({ + name: name, +}); + +export const fileType = z.object({ + file: file, +}); + +export const passwordType = z.object({ + password: password, +}); + +export function validateZod(schema: ZodSchema) { + return async (data: unknown) => { + try { + return await schema.parseAsync(data); + } catch (error) { + let message = 'Unexpected Error'; + if (error instanceof ZodError) { + message = error.issues.map((v) => v.message).join('; '); + } + throw new Error(message); + } + }; +}