import { Response, NextFunction } from "express"; // Validation rules const VALIDATION_RULES = { prompt: { minLength: 3, maxLength: 2000, required: true, }, filename: { minLength: 1, maxLength: 100, required: true, pattern: /^[a-zA-Z0-9_-]+$/, // Only alphanumeric, underscore, hyphen }, }; /** * Sanitize filename to prevent directory traversal and invalid characters */ export const sanitizeFilename = (filename: string): string => { return filename .replace(/[^a-zA-Z0-9_-]/g, "_") // Replace invalid chars with underscore .replace(/_{2,}/g, "_") // Replace multiple underscores with single .replace(/^_+|_+$/g, "") // Remove leading/trailing underscores .substring(0, 100); // Limit length }; /** * Validate the generate image request */ export const validateGenerateRequest = ( req: any, res: Response, next: NextFunction, ): void | Response => { const timestamp = new Date().toISOString(); const { prompt, filename, autoEnhance, enhancementOptions } = req.body; const errors: string[] = []; console.log(`[${timestamp}] [${req.requestId}] Validating generate request`); // Validate prompt if (!prompt) { errors.push("Prompt is required"); } else if (typeof prompt !== "string") { errors.push("Prompt must be a string"); } else if (prompt.trim().length < VALIDATION_RULES.prompt.minLength) { errors.push( `Prompt must be at least ${VALIDATION_RULES.prompt.minLength} characters`, ); } else if (prompt.length > VALIDATION_RULES.prompt.maxLength) { errors.push( `Prompt must be less than ${VALIDATION_RULES.prompt.maxLength} characters`, ); } // Validate filename if (!filename) { errors.push("Filename is required"); } else if (typeof filename !== "string") { errors.push("Filename must be a string"); } else if (filename.trim().length < VALIDATION_RULES.filename.minLength) { errors.push("Filename cannot be empty"); } else if (filename.length > VALIDATION_RULES.filename.maxLength) { errors.push( `Filename must be less than ${VALIDATION_RULES.filename.maxLength} characters`, ); } else if (!VALIDATION_RULES.filename.pattern.test(filename)) { errors.push( "Filename can only contain letters, numbers, underscores, and hyphens", ); } // Validate autoEnhance (optional boolean) if (autoEnhance !== undefined && typeof autoEnhance !== "boolean") { errors.push("autoEnhance must be a boolean"); } // Validate enhancementOptions (optional object) if (enhancementOptions !== undefined) { if ( typeof enhancementOptions !== "object" || Array.isArray(enhancementOptions) ) { errors.push("enhancementOptions must be an object"); } else { const { imageStyle, aspectRatio, mood, lighting, cameraAngle, negativePrompts, } = enhancementOptions; if ( imageStyle !== undefined && ![ "photorealistic", "illustration", "minimalist", "sticker", "product", "comic", ].includes(imageStyle) ) { errors.push("Invalid imageStyle in enhancementOptions"); } if ( aspectRatio !== undefined && !["square", "portrait", "landscape", "wide", "ultrawide"].includes( aspectRatio, ) ) { errors.push("Invalid aspectRatio in enhancementOptions"); } if ( mood !== undefined && (typeof mood !== "string" || mood.length > 100) ) { errors.push("mood must be a string with max 100 characters"); } if ( lighting !== undefined && (typeof lighting !== "string" || lighting.length > 100) ) { errors.push("lighting must be a string with max 100 characters"); } if ( cameraAngle !== undefined && (typeof cameraAngle !== "string" || cameraAngle.length > 100) ) { errors.push("cameraAngle must be a string with max 100 characters"); } if (negativePrompts !== undefined) { if (!Array.isArray(negativePrompts) || negativePrompts.length > 10) { errors.push("negativePrompts must be an array with max 10 items"); } else { for (const item of negativePrompts) { if (typeof item !== "string" || item.length > 100) { errors.push( "Each negative prompt must be a string with max 100 characters", ); break; } } } } } } // Check for XSS attempts in prompt const xssPatterns = [ /