// Validate Canadian postal code format // Full Canadian postal code validation with proper FSA/LDU rules function validatePostalCode(postalCode) { // Remove whitespace and convert to uppercase const cleaned = postalCode.replace(/\s/g, '').toUpperCase(); // Must be exactly 6 characters if (cleaned.length !== 6) return false; // Pattern: A1A 1A1 where A is letter and 1 is digit const pattern = /^[A-Z]\d[A-Z]\d[A-Z]\d$/; if (!pattern.test(cleaned)) return false; // First character cannot be D, F, I, O, Q, U, W, or Z const invalidFirstChars = ['D', 'F', 'I', 'O', 'Q', 'U', 'W', 'Z']; if (invalidFirstChars.includes(cleaned[0])) return false; // Second position (LDU) cannot be 0 if (cleaned[1] === '0') return false; // Third character cannot be D, F, I, O, Q, or U const invalidThirdChars = ['D', 'F', 'I', 'O', 'Q', 'U']; if (invalidThirdChars.includes(cleaned[2])) return false; // Fifth character cannot be D, F, I, O, Q, or U if (invalidThirdChars.includes(cleaned[4])) return false; return true; } // Validate Alberta postal code (starts with T) function validateAlbertaPostalCode(postalCode) { const formatted = postalCode.replace(/\s/g, '').toUpperCase(); return formatted.startsWith('T') && validatePostalCode(postalCode); } // Validate email format with stricter rules function validateEmail(email) { // RFC 5322 simplified email validation const regex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; if (!regex.test(email)) return false; // Additional checks const parts = email.split('@'); if (parts.length !== 2) return false; const [localPart, domain] = parts; // Local part max 64 characters if (localPart.length > 64) return false; // Domain must have at least one dot and valid TLD if (!domain.includes('.')) return false; // Check for consecutive dots if (email.includes('..')) return false; return true; } // Format postal code to standard format (A1A 1A1) function formatPostalCode(postalCode) { const cleaned = postalCode.replace(/\s/g, '').toUpperCase(); if (cleaned.length === 6) { return `${cleaned.slice(0, 3)} ${cleaned.slice(3)}`; } return cleaned; } // Sanitize string input to prevent XSS and injection attacks function sanitizeString(str) { if (typeof str !== 'string') return str; return str .replace(/[<>]/g, '') // Remove angle brackets .replace(/javascript:/gi, '') // Remove javascript: protocol .replace(/on\w+\s*=/gi, '') // Remove event handlers .replace(/eval\s*\(/gi, '') // Remove eval calls .trim() .substring(0, 1000); // Limit length } // Sanitize HTML content for email templates function sanitizeHtmlContent(html) { if (typeof html !== 'string') return html; // Remove dangerous tags and attributes let sanitized = html .replace(/)<[^<]*)*<\/script>/gi, '') .replace(/)<[^<]*)*<\/iframe>/gi, '') .replace(/)<[^<]*)*<\/object>/gi, '') .replace(/]*>/gi, '') .replace(/javascript:/gi, '') .replace(/on\w+\s*=/gi, ''); return sanitized; } // Validate SQL/NoSQL injection attempts in where clauses function validateWhereClause(whereClause) { if (typeof whereClause !== 'string') return false; // Check for SQL injection patterns const suspiciousPatterns = [ /;\s*drop\s+/i, /;\s*delete\s+/i, /;\s*update\s+/i, /;\s*insert\s+/i, /union\s+select/i, /exec\s*\(/i, /execute\s*\(/i, /--/, /\/\*/, /xp_/i, /sp_/i ]; return !suspiciousPatterns.some(pattern => pattern.test(whereClause)); } // Validate required fields in request body function validateRequiredFields(body, requiredFields) { const errors = []; requiredFields.forEach(field => { if (!body[field] || (typeof body[field] === 'string' && body[field].trim() === '')) { errors.push(`${field} is required`); } }); return errors; } // Check if string contains potentially harmful content function containsSuspiciousContent(str) { const suspiciousPatterns = [ /