fix(security): validate text preferences with Zod schema before saving
- commit
- d7a3f1f5afe9e8dde4cb4a99be76867858756b98
- author
- Kit · · 2026-02-16 11:03 UTC
- parent
- 5794674
- signature
- SSH Unverified
SHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6TkThe key is unknown
1 file · +16 -9
M src/routes/api/user/text-preferences/+server.ts +16 -9
| @@ -3,6 +3,7 @@ import type { RequestHandler } from './$types'; | |||
| 3 | 3 | import convex from '$lib/db/convex.server'; | |
| 4 | 4 | import { DEFAULT_USER_PREFERENCES, type UserTextPreferences } from '$lib/types/text-formatting'; | |
| 5 | 5 | import { validateSessionToken } from '$lib/api/session.server'; | |
| 6 | + | import { sanitizeStyleData } from '$lib/validation/text-formatting'; | |
| 6 | 7 | ||
| 7 | 8 | const isKitHttpError = (err: unknown): err is { status: number; body: unknown } => { | |
| 8 | 9 | return typeof err === 'object' && err !== null && 'status' in err && 'body' in err; | |
| @@ -73,21 +74,27 @@ export const POST: RequestHandler = async ({ cookies, request }) => { | |||
| 73 | 74 | ||
| 74 | 75 | // Parse request body | |
| 75 | 76 | const body = await request.json(); | |
| 76 | - | const { defaultStyle, allowFormatting, maxMessageLength } = body as UserTextPreferences; | |
| 77 | - | ||
| 78 | - | // Validate required fields | |
| 79 | - | if ( | |
| 80 | - | !defaultStyle || | |
| 81 | - | typeof allowFormatting !== 'boolean' || | |
| 82 | - | typeof maxMessageLength !== 'number' | |
| 83 | - | ) { | |
| 77 | + | const { allowFormatting, maxMessageLength } = body as { | |
| 78 | + | allowFormatting: unknown; | |
| 79 | + | maxMessageLength: unknown; | |
| 80 | + | defaultStyle: unknown; | |
| 81 | + | }; | |
| 82 | + | ||
| 83 | + | // Validate types | |
| 84 | + | if (typeof allowFormatting !== 'boolean' || typeof maxMessageLength !== 'number') { | |
| 84 | 85 | throw error(400, 'Invalid preferences data'); | |
| 85 | 86 | } | |
| 86 | 87 | ||
| 88 | + | // Validate and sanitize style data through existing Zod schema | |
| 89 | + | const validatedStyle = sanitizeStyleData(body.defaultStyle); | |
| 90 | + | if (!validatedStyle) { | |
| 91 | + | throw error(400, 'Invalid style data'); | |
| 92 | + | } | |
| 93 | + | ||
| 87 | 94 | // Save preferences via Convex | |
| 88 | 95 | const result = await convex.textPreferences.save( | |
| 89 | 96 | user.id, | |
| 90 | - | defaultStyle, | |
| 97 | + | validatedStyle, | |
| 91 | 98 | allowFormatting, | |
| 92 | 99 | maxMessageLength | |
| 93 | 100 | ); | |
| @@ -3,6 +3,7 @@ import type { RequestHandler } from './$types'; | |||||
| 3 | import convex from '$lib/db/convex.server'; | 3 | import convex from '$lib/db/convex.server'; | ||
| 4 | import { DEFAULT_USER_PREFERENCES, type UserTextPreferences } from '$lib/types/text-formatting'; | 4 | import { DEFAULT_USER_PREFERENCES, type UserTextPreferences } from '$lib/types/text-formatting'; | ||
| 5 | import { validateSessionToken } from '$lib/api/session.server'; | 5 | import { validateSessionToken } from '$lib/api/session.server'; | ||
| 6 | + | import { sanitizeStyleData } from '$lib/validation/text-formatting'; | |||
| 6 | 7 | ||||
| 7 | const isKitHttpError = (err: unknown): err is { status: number; body: unknown } => { | 8 | const isKitHttpError = (err: unknown): err is { status: number; body: unknown } => { | ||
| 8 | return typeof err === 'object' && err !== null && 'status' in err && 'body' in err; | 9 | return typeof err === 'object' && err !== null && 'status' in err && 'body' in err; | ||
| @@ -73,21 +74,27 @@ export const POST: RequestHandler = async ({ cookies, request }) => { | |||||
| 73 | 74 | ||||
| 74 | // Parse request body | 75 | // Parse request body | ||
| 75 | const body = await request.json(); | 76 | const body = await request.json(); | ||
| 76 | - | const { defaultStyle, allowFormatting, maxMessageLength } = body as UserTextPreferences; | 77 | + | const { allowFormatting, maxMessageLength } = body as { |
| 77 | - | 78 | + | allowFormatting: unknown; | |
| 78 | - | // Validate required fields | 79 | + | maxMessageLength: unknown; |
| 79 | - | if ( | 80 | + | defaultStyle: unknown; |
| 80 | - | !defaultStyle || | 81 | + | }; |
| 81 | - | typeof allowFormatting !== 'boolean' || | 82 | + | |
| 82 | - | typeof maxMessageLength !== 'number' | 83 | + | // Validate types |
| 83 | - | ) { | 84 | + | if (typeof allowFormatting !== 'boolean' || typeof maxMessageLength !== 'number') { |
| 84 | throw error(400, 'Invalid preferences data'); | 85 | throw error(400, 'Invalid preferences data'); | ||
| 85 | } | 86 | } | ||
| 86 | 87 | ||||
| 88 | + | // Validate and sanitize style data through existing Zod schema | |||
| 89 | + | const validatedStyle = sanitizeStyleData(body.defaultStyle); | |||
| 90 | + | if (!validatedStyle) { | |||
| 91 | + | throw error(400, 'Invalid style data'); | |||
| 92 | + | } | |||
| 93 | + | ||||
| 87 | // Save preferences via Convex | 94 | // Save preferences via Convex | ||
| 88 | const result = await convex.textPreferences.save( | 95 | const result = await convex.textPreferences.save( | ||
| 89 | user.id, | 96 | user.id, | ||
| 90 | - | defaultStyle, | 97 | + | validatedStyle, |
| 91 | allowFormatting, | 98 | allowFormatting, | ||
| 92 | maxMessageLength | 99 | maxMessageLength | ||
| 93 | ); | 100 | ); | ||