From c2c1660064b19bf6fe89799681f4239d8166dde6 Mon Sep 17 00:00:00 2001 From: bytedream Date: Sat, 19 Oct 2024 18:07:11 +0200 Subject: [PATCH] use zod schemes for validation --- src/routes/admin/admin/+server.ts | 76 ++++++++-------------- src/routes/admin/admin/schema.ts | 19 ++++++ src/routes/admin/login/+server.ts | 23 +++---- src/routes/admin/login/schema.ts | 6 ++ src/routes/admin/logout/+server.ts | 3 +- src/routes/admin/reports/+server.ts | 58 ++++++++--------- src/routes/admin/reports/HeaderBar.svelte | 1 - src/routes/admin/reports/schema.ts | 30 +++++++++ src/routes/admin/users/+server.ts | 78 ++++++++--------------- src/routes/admin/users/schema.ts | 36 +++++++++++ src/routes/api/report/+server.ts | 9 ++- src/routes/api/report/schema.ts | 7 ++ 12 files changed, 193 insertions(+), 153 deletions(-) create mode 100644 src/routes/admin/admin/schema.ts create mode 100644 src/routes/admin/login/schema.ts create mode 100644 src/routes/admin/reports/schema.ts create mode 100644 src/routes/admin/users/schema.ts create mode 100644 src/routes/api/report/schema.ts diff --git a/src/routes/admin/admin/+server.ts b/src/routes/admin/admin/+server.ts index d99a432..ac480ea 100644 --- a/src/routes/admin/admin/+server.ts +++ b/src/routes/admin/admin/+server.ts @@ -2,64 +2,47 @@ import type { RequestHandler } from '@sveltejs/kit'; import { Permissions } from '$lib/permissions'; import { deleteAllUserSessions, getSession, updateAllUserSessions } from '$lib/server/session'; import { Admin } from '$lib/server/database'; +import { AdminDeleteSchema, AdminEditSchema, AdminListSchema } from './schema'; export const POST = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.AdminWrite] }) == null) { - return new Response(null, { - status: 401 - }); + return new Response(null, { status: 401 }); } - const data = await request.json(); - const username = data['username'] as string | null; - const password = data['password'] as string | null; - const permissions = data['permissions'] as number | null; + const parseResult = await AdminListSchema.safeParseAsync(await request.json()); + if (!parseResult.success) return new Response(null, { status: 400 }); + const data = parseResult.data; - if (username == null || password == null || permissions == null) { - return new Response(null, { - status: 400 - }); + if (data.username == null || data.password == null || data.permissions == null) { + return new Response(null, { status: 400 }); } const admin = await Admin.create({ - username: username, - password: password, - permissions: new Permissions(permissions) + username: data.username, + password: data.password, + permissions: new Permissions(data.permissions) }); delete admin.dataValues.password; - return new Response(JSON.stringify(admin), { - status: 201 - }); + return new Response(JSON.stringify(admin), { status: 201 }); }) satisfies RequestHandler; export const PATCH = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.AdminWrite] }) == null) { - return new Response(null, { - status: 401 - }); + return new Response(null, { status: 401 }); } - const data = await request.json(); - const id = data['id'] as string | null; + const parseResult = await AdminEditSchema.safeParseAsync(await request.json()); + if (!parseResult.success) return new Response(null, { status: 400 }); + const data = parseResult.data; - if (id == null) { - return new Response(null, { - status: 400 - }); - } + const user = await Admin.findOne({ where: { id: data.id } }); + if (!user) return new Response(null, { status: 400 }); - const user = await Admin.findOne({ where: { id: id } }); - if (!user) { - return new Response(null, { - status: 400 - }); - } - - if (data['username']) user.username = data['username']; - if (data['password']) user.password = data['password']; - if (data['permissions']) user.permissions = new Permissions(data['permissions']); + if (data.username) user.username = data.username; + if (data.password) user.password = data.password; + if (data.permissions) user.permissions = new Permissions(data.permissions); await user.save(); updateAllUserSessions(user.id, { permissions: user.permissions }); @@ -69,22 +52,15 @@ export const PATCH = (async ({ request, cookies }) => { export const DELETE = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.AdminWrite] }) == null) { - return new Response(null, { - status: 401 - }); + return new Response(null, { status: 401 }); } - const data = await request.json(); - const id = data['id'] as number | null; + const parseResult = await AdminDeleteSchema.safeParseAsync(await request.json()); + if (!parseResult.success) return new Response(null, { status: 400 }); + const data = parseResult.data; - if (id == null) { - return new Response(null, { - status: 400 - }); - } - - await Admin.destroy({ where: { id: id } }); - deleteAllUserSessions(id); + await Admin.destroy({ where: { id: data.id } }); + deleteAllUserSessions(data.id); return new Response(); }) satisfies RequestHandler; diff --git a/src/routes/admin/admin/schema.ts b/src/routes/admin/admin/schema.ts new file mode 100644 index 0000000..6f59664 --- /dev/null +++ b/src/routes/admin/admin/schema.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +export const AdminListSchema = z.object({ + username: z.string(), + password: z.string(), + permissions: z.number() +}); + +export const AdminEditSchema = z.object({ + id: z.number(), + + username: z.string().nullish(), + password: z.string().nullish(), + permissions: z.number().nullish() +}); + +export const AdminDeleteSchema = z.object({ + id: z.number() +}); diff --git a/src/routes/admin/login/+server.ts b/src/routes/admin/login/+server.ts index 2ac4efa..9687638 100644 --- a/src/routes/admin/login/+server.ts +++ b/src/routes/admin/login/+server.ts @@ -4,23 +4,20 @@ import { env as publicEnv } from '$env/dynamic/public'; import { env } from '$env/dynamic/private'; import { addSession, sessionCookieName } from '$lib/server/session'; import { Permissions } from '$lib/permissions'; +import { LoginSchema } from './schema'; export const POST = (async ({ request, cookies }) => { - const data = await request.formData(); - const username = data.get('username') as string | null; - const password = data.get('password') as string | null; - - if (username == null || password == null) { - return new Response(null, { - status: 401 - }); + const parseResult = await LoginSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { + return new Response(null, { status: 400 }); } + const data = parseResult.data; if ( env.ADMIN_USER && env.ADMIN_PASSWORD && - username == env.ADMIN_USER && - password == env.ADMIN_PASSWORD + data.username == env.ADMIN_USER && + data.password == env.ADMIN_PASSWORD ) { cookies.set( sessionCookieName, @@ -35,8 +32,8 @@ export const POST = (async ({ request, cookies }) => { return new Response(); } - const user = await Admin.findOne({ where: { username: username } }); - if (user && user.validatePassword(password)) { + const user = await Admin.findOne({ where: { username: data.username } }); + if (user && user.validatePassword(data.password)) { cookies.set(sessionCookieName, addSession(user), { path: `${publicEnv.PUBLIC_BASE_PATH}/admin`, maxAge: 60 * 60 * 24 * 90, @@ -45,7 +42,7 @@ export const POST = (async ({ request, cookies }) => { }); return new Response(); } else { - console.log(`failed login attempt for user ${username}`); + console.log(`failed login attempt for user ${data.username}`); return new Response(null, { status: 401 }); diff --git a/src/routes/admin/login/schema.ts b/src/routes/admin/login/schema.ts new file mode 100644 index 0000000..c6d4c4e --- /dev/null +++ b/src/routes/admin/login/schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const LoginSchema = z.object({ + username: z.string(), + password: z.string() +}); diff --git a/src/routes/admin/logout/+server.ts b/src/routes/admin/logout/+server.ts index 4787fbb..dad389d 100644 --- a/src/routes/admin/logout/+server.ts +++ b/src/routes/admin/logout/+server.ts @@ -1,5 +1,6 @@ import type { RequestHandler } from '@sveltejs/kit'; import { deleteSession, getSession, sessionCookieName } from '$lib/server/session'; +import { env as publicEnv } from '$env/dynamic/public'; export const POST = (async ({ cookies }) => { if (getSession(cookies) == null) { @@ -9,7 +10,7 @@ export const POST = (async ({ cookies }) => { } deleteSession(cookies); - cookies.delete(sessionCookieName); + cookies.delete(sessionCookieName, { path: `${publicEnv.PUBLIC_BASE_PATH}/admin` }); return new Response(); }) satisfies RequestHandler; diff --git a/src/routes/admin/reports/+server.ts b/src/routes/admin/reports/+server.ts index 3745ee1..0897c42 100644 --- a/src/routes/admin/reports/+server.ts +++ b/src/routes/admin/reports/+server.ts @@ -7,6 +7,7 @@ import { Op } from 'sequelize'; import { env } from '$env/dynamic/private'; import crypto from 'crypto'; import { webhookUserReported } from '$lib/server/webhook'; +import { ReportAddSchema, ReportEditSchema, ReportListSchema } from './schema'; export const POST = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.ReportRead] }) == null) { @@ -15,21 +16,18 @@ export const POST = (async ({ request, cookies }) => { }); } - const data: { - limit: number | null; - from: number | null; - - draft: boolean | null; - status: 'none' | 'review' | 'reviewed' | null; - reporter: string | null; - reported: string | null; - - hash: string | null; - } = await request.json(); + const parseResult = await ReportListSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { + console.log(parseResult.error); + return new Response(null, { + status: 400 + }); + } + const data = parseResult.data; let reportFindOptions: Attributes = {}; - if (data.draft != null) reportFindOptions.draft = data.draft; - reportFindOptions.status = data.status == null ? ['none', 'review'] : data.status; + reportFindOptions.draft = data.draft; + reportFindOptions.status = data.status ?? ['none', 'review']; if (data.reporter != null) { const reporter_ids = await User.findAll({ attributes: ['id'], @@ -97,17 +95,13 @@ export const PATCH = (async ({ request, cookies }) => { }); } - const data: { - id: number; - reported: string | null; - auditor: number; - notice: string | null; - statement: string | null; - status: 'none' | 'review' | 'reviewed' | null; - strike_reason: number | null; - } = await request.json(); - - if (data.id === null || data.auditor === null) return new Response(null, { status: 400 }); + const parseResult = await ReportEditSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { + return new Response(null, { + status: 400 + }); + } + const data = parseResult.data; const report = await Report.findOne({ where: { id: data.id } }); const admin = await Admin.findOne({ where: { id: data.auditor } }); @@ -175,15 +169,13 @@ export const PUT = (async ({ request, cookies }) => { }); } - const data: { - reporter: string; - reported: string | null; - reason: string; - body: string | null; - } = await request.json(); - - if (data.reporter == null || data.reason == null || data.body === undefined) - return new Response(null, { status: 400 }); + const parseResult = await ReportAddSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { + return new Response(null, { + status: 400 + }); + } + const data = parseResult.data; const reporter = await User.findOne({ where: { uuid: data.reporter } }); const reported = data.reported ? await User.findOne({ where: { uuid: data.reported } }) : null; diff --git a/src/routes/admin/reports/HeaderBar.svelte b/src/routes/admin/reports/HeaderBar.svelte index d3ebbfe..d2f9b52 100644 --- a/src/routes/admin/reports/HeaderBar.svelte +++ b/src/routes/admin/reports/HeaderBar.svelte @@ -26,6 +26,5 @@ diff --git a/src/routes/admin/reports/schema.ts b/src/routes/admin/reports/schema.ts new file mode 100644 index 0000000..48a690b --- /dev/null +++ b/src/routes/admin/reports/schema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod'; + +export const ReportListSchema = z.object({ + limit: z.number().nullish(), + from: z.number().nullish(), + + status: z.enum(['none', 'review', 'reviewed']).nullish(), + reporter: z.string().nullish(), + reported: z.string().nullish(), + draft: z.boolean().nullish(), + + hash: z.string().nullish() +}); + +export const ReportEditSchema = z.object({ + id: z.number(), + reported: z.string().nullish(), + auditor: z.number(), + notice: z.string().nullish(), + statement: z.string().nullish(), + status: z.enum(['none', 'review', 'reviewed']).nullish(), + strike_reason: z.number().nullish() +}); + +export const ReportAddSchema = z.object({ + reporter: z.string(), + reported: z.string().nullish(), + reason: z.string(), + body: z.string().nullish() +}); diff --git a/src/routes/admin/users/+server.ts b/src/routes/admin/users/+server.ts index 6f996de..7784724 100644 --- a/src/routes/admin/users/+server.ts +++ b/src/routes/admin/users/+server.ts @@ -4,6 +4,7 @@ import { error, type RequestHandler } from '@sveltejs/kit'; import { User } from '$lib/server/database'; import { type Attributes, Op } from 'sequelize'; import { ApiError, getJavaUuid, getNoAuthUuid, UserNotFoundError } from '$lib/server/minecraft'; +import { UserAddSchema, UserDeleteSchema, UserEditSchema, UserListSchema } from './schema'; export const POST = (async ({ request, cookies }) => { if (getSession(cookies, { permissions: [Permissions.UserRead] }) == null) { @@ -12,16 +13,11 @@ export const POST = (async ({ request, cookies }) => { }); } - const data: { - limit: number | null; - from: number | null; - - name: string | null; - playertype: 'java' | 'bedrock' | 'noauth' | null; - - search: string | null; - slim: boolean | null; - } = await request.json(); + const parseResult = await UserListSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { + return new Response(null, { status: 400 }); + } + const data = parseResult.data; const usersFindOptions: Attributes = {}; if (data.name) { @@ -60,30 +56,26 @@ export const PATCH = (async ({ request, cookies }) => { }); } - const data = await request.json(); - const id = data['id'] as string | null; - - if (id == null) { - return new Response(null, { - status: 400 - }); + const parseResult = await UserEditSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { + return new Response(null, { status: 400 }); } + const data = parseResult.data; - const user = await User.findOne({ where: { id: id } }); + const user = await User.findOne({ where: { id: data.id } }); if (!user) { return new Response(null, { status: 400 }); } - if (data['firstname']) user.firstname = data['firstname']; - if (data['lastname']) user.lastname = data['lastname']; - if (data['birthday']) user.birthday = data['birthday']; - if (data['telephone']) user.telephone = data['telephone']; - if (data['username']) user.username = data['username']; - if (data['playertype']) user.playertype = data['playertype']; - if (data['password']) user.password = data['password']; - if (data['uuid']) user.uuid = data['uuid']; + if (data.firstname) user.firstname = data.firstname; + if (data.lastname) user.lastname = data.lastname; + if (data.birthday) user.birthday = data.birthday; + if (data.telephone) user.telephone = data.telephone; + if (data.username) user.username = data.username; + if (data.playertype) user.playertype = data.playertype; + if (data.uuid) user.uuid = data.uuid; await user.save(); return new Response(); @@ -96,26 +88,11 @@ export const PUT = (async ({ request, cookies }) => { }); } - const data: { - firstname: string; - lastname: string; - - birthday: string; - telephone: string; - - username: string; - playertype: string; - } = await request.json(); - - if ( - data.firstname == null || - data.lastname == null || - data.birthday == null || - data.username == null || - data.playertype == null - ) { + const parseResult = await UserAddSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { return new Response(null, { status: 400 }); } + const data = parseResult.data; let uuid: string | null; try { @@ -180,16 +157,13 @@ export const DELETE = (async ({ request, cookies }) => { }); } - const data = await request.json(); - const id = (data['id'] as number) || null; - - if (id == null) { - return new Response(null, { - status: 400 - }); + const parseResult = await UserDeleteSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { + return new Response(null, { status: 400 }); } + const data = parseResult.data; - await User.destroy({ where: { id: id } }); + await User.destroy({ where: { id: data.id } }); return new Response(); }) satisfies RequestHandler; diff --git a/src/routes/admin/users/schema.ts b/src/routes/admin/users/schema.ts new file mode 100644 index 0000000..a97fa6a --- /dev/null +++ b/src/routes/admin/users/schema.ts @@ -0,0 +1,36 @@ +import { z } from 'zod'; + +export const UserListSchema = z.object({ + limit: z.number().nullish(), + from: z.number().nullish(), + + name: z.string().nullish(), + playertype: z.enum(['java', 'bedrock', 'noauth']).nullish(), + + search: z.string().nullish(), + slim: z.boolean().nullish() +}); + +export const UserEditSchema = z.object({ + id: z.number(), + firstname: z.string().nullish(), + lastname: z.string().nullish(), + birthday: z.coerce.date().nullish(), + telephone: z.string().nullish(), + username: z.string().nullish(), + playertype: z.enum(['java', 'bedrock', 'noauth']).nullish(), + uuid: z.string().nullish() +}); + +export const UserAddSchema = z.object({ + firstname: z.string(), + lastname: z.string(), + birthday: z.coerce.date(), + telephone: z.string().nullish(), + username: z.string(), + playertype: z.enum(['java', 'bedrock', 'noauth']) +}); + +export const UserDeleteSchema = z.object({ + id: z.number() +}); diff --git a/src/routes/api/report/+server.ts b/src/routes/api/report/+server.ts index 4b1a104..60ec654 100644 --- a/src/routes/api/report/+server.ts +++ b/src/routes/api/report/+server.ts @@ -3,14 +3,17 @@ import { Report, User } from '$lib/server/database'; import * as crypto from 'crypto'; import { env as public_env } from '$env/dynamic/public'; import { env } from '$env/dynamic/private'; +import { ReportAddSchema } from './schema'; export const POST = (async ({ request, url }) => { if (env.REPORT_SECRET && url.searchParams.get('secret') !== env.REPORT_SECRET) return new Response(null, { status: 401 }); - const data: { reporter: string; reported: string | null; reason: string } = await request.json(); - - if (data.reporter == null || data.reason == null) return new Response(null, { status: 400 }); + const parseResult = await ReportAddSchema.safeParseAsync(await request.json()); + if (!parseResult.success) { + return new Response(null, { status: 400 }); + } + const data = parseResult.data; const reporter = await User.findOne({ where: { uuid: data.reporter } }); const reported = data.reported diff --git a/src/routes/api/report/schema.ts b/src/routes/api/report/schema.ts new file mode 100644 index 0000000..278d3cb --- /dev/null +++ b/src/routes/api/report/schema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const ReportAddSchema = z.object({ + reporter: z.string(), + reported: z.string().nullish(), + reason: z.string() +});