generated from nhcarrigan/template
3668a67a62
This resolves issue #65 by addressing multiple problems that were preventing base64-encoded cover images from being uploaded: 1. Increased Fastify body limit from 1MB to 10MB to accommodate base64-encoded images 2. Removed duplicate coverImage string length validation that was blocking base64 data URLs 3. Fixed base64 size calculation to properly extract and measure just the base64 data portion 4. Updated validateDataUrl regex to allow whitespace in base64 strings 5. Added proper error handling with 400 status codes and helpful error messages instead of 500 errors Users can now successfully upload cover images as base64 data URLs up to 5MB (decoded size) and will receive clear validation error messages if uploads fail. Closes #65
96 lines
2.2 KiB
TypeScript
96 lines
2.2 KiB
TypeScript
/**
|
|
* @copyright 2026 NHCarrigan
|
|
* @license Naomi's Public License
|
|
* @author Naomi Carrigan
|
|
*/
|
|
|
|
/**
|
|
* Validates that a URL is a proper base64 data string.
|
|
* Allows whitespace in the base64 data which may occur during transmission.
|
|
*/
|
|
export function validateDataUrl(url: string): boolean {
|
|
return /^data:image\/(jpeg|png|gif|webp|svg\+xml);base64,[A-Za-z0-9+/=\s]+$/.test(url);
|
|
}
|
|
|
|
/**
|
|
* Validates that a URL is safe and points to an allowed protocol.
|
|
* Prevents javascript:, data:, vbscript:, and file: URLs.
|
|
*/
|
|
export function validateUrl(url: string): boolean {
|
|
if (!url) {
|
|
return true; // Empty URLs are acceptable for optional fields
|
|
}
|
|
|
|
// Check for dangerous protocols
|
|
const dangerousProtocols = /^(javascript|data|vbscript|file):/i;
|
|
if (dangerousProtocols.test(url)) {
|
|
return false;
|
|
}
|
|
|
|
// Must be a valid URL format
|
|
try {
|
|
const parsedUrl = new URL(url);
|
|
// Only allow http and https protocols
|
|
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates string length is within acceptable bounds.
|
|
*/
|
|
export function validateStringLength(
|
|
value: string | undefined,
|
|
maxLength: number
|
|
): boolean {
|
|
if (!value) {
|
|
return true;
|
|
}
|
|
return value.length <= maxLength;
|
|
}
|
|
|
|
/**
|
|
* Validates that a slug contains only safe characters (alphanumeric, hyphens, underscores).
|
|
*/
|
|
export function validateSlug(slug: string | undefined): boolean {
|
|
if (!slug) {
|
|
return true;
|
|
}
|
|
// Allow alphanumeric, hyphens, underscores only
|
|
const slugPattern = /^[a-z0-9-_]+$/i;
|
|
return slugPattern.test(slug) && slug.length <= 50;
|
|
}
|
|
|
|
/**
|
|
* Validates rating is within acceptable range.
|
|
*/
|
|
export function validateRating(rating: number | undefined): boolean {
|
|
if (rating === undefined) {
|
|
return true;
|
|
}
|
|
return Number.isInteger(rating) && rating >= 0 && rating <= 10;
|
|
}
|
|
|
|
/**
|
|
* Maximum string lengths for various fields.
|
|
*/
|
|
export const MAX_LENGTHS = {
|
|
TITLE: 500,
|
|
AUTHOR: 200,
|
|
DESCRIPTION: 5000,
|
|
BIO: 1000,
|
|
SLUG: 50,
|
|
URL: 2048,
|
|
DISPLAY_NAME: 100,
|
|
USERNAME: 100,
|
|
COMMENT_CONTENT: 10000,
|
|
NOTES: 5000,
|
|
TAGS: 50, // per tag
|
|
ISBN: 50,
|
|
DATA_URL: 5 * 1024 * 1024, // 5MB in bytes (not chars)
|
|
} as const;
|