import { Response, NextFunction } from 'express'; import { sanitizeFilename } from './validation'; // Validation rules (same as existing validation but for JSON) 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 }, }; // Valid aspect ratios supported by Gemini SDK const VALID_ASPECT_RATIOS = [ '1:1', // Square (1024x1024) '2:3', // Portrait (832x1248) '3:2', // Landscape (1248x832) '3:4', // Portrait (864x1184) '4:3', // Landscape (1184x864) '4:5', // Portrait (896x1152) '5:4', // Landscape (1152x896) '9:16', // Vertical (768x1344) '16:9', // Widescreen (1344x768) '21:9', // Ultrawide (1536x672) ] as const; /** * Validate the text-to-image JSON request */ export const validateTextToImageRequest = ( req: any, res: Response, next: NextFunction, ): void | Response => { const timestamp = new Date().toISOString(); const { prompt, filename, aspectRatio, autoEnhance, enhancementOptions } = req.body; const errors: string[] = []; console.log(`[${timestamp}] [${req.requestId}] Validating text-to-image JSON request`); // Validate that request body exists if (!req.body || typeof req.body !== 'object') { return res.status(400).json({ success: false, error: 'Request body must be valid JSON', message: 'Invalid request format', }); } // Set defaults before validation // Default autoEnhance to true if not explicitly set if (req.body.autoEnhance === undefined) { req.body.autoEnhance = true; } // Default template to "photorealistic" in enhancementOptions if (req.body.enhancementOptions && !req.body.enhancementOptions.template) { req.body.enhancementOptions.template = 'photorealistic'; } else if (!req.body.enhancementOptions && req.body.autoEnhance !== false) { // If autoEnhance is true (default) and no enhancementOptions, create it with default template req.body.enhancementOptions = { template: 'photorealistic' }; } // 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 aspectRatio (optional, defaults to "1:1") if (aspectRatio !== undefined) { if (typeof aspectRatio !== 'string') { errors.push('aspectRatio must be a string'); } else if (!VALID_ASPECT_RATIOS.includes(aspectRatio as any)) { errors.push(`Invalid aspectRatio. Must be one of: ${VALID_ASPECT_RATIOS.join(', ')}`); } } // 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 { template } = enhancementOptions; // Validate template parameter if ( template !== undefined && ![ 'photorealistic', 'illustration', 'minimalist', 'sticker', 'product', 'comic', 'general', ].includes(template) ) { errors.push( 'Invalid template in enhancementOptions. Must be one of: photorealistic, illustration, minimalist, sticker, product, comic, general', ); } } } // Validate meta (optional object) if (req.body.meta !== undefined) { if (typeof req.body.meta !== 'object' || Array.isArray(req.body.meta)) { errors.push('meta must be an object'); } else if (req.body.meta.tags !== undefined) { if (!Array.isArray(req.body.meta.tags)) { errors.push('meta.tags must be an array'); } else { // Validate each tag is a string for (const tag of req.body.meta.tags) { if (typeof tag !== 'string') { errors.push('Each tag in meta.tags must be a string'); break; } } } } } // Check for XSS attempts in prompt const xssPatterns = [/