import Joi from 'joi'; // Consent validation schemas export const consentSchema = Joi.object({ type: Joi.string() .valid('marketing', 'analytics', 'data_processing', 'third_party_sharing', 'cookies') .required(), purposes: Joi.array() .items(Joi.string()) .min(1) .required(), duration: Joi.number() .integer() .min(1) .max(730) // Max 2 years .default(365), renewable: Joi.boolean() .default(true), location: Joi.object({ country: Joi.string().length(2), region: Joi.string(), city: Joi.string() }), preferences: Joi.object({ channels: Joi.array().items( Joi.string().valid('email', 'sms', 'push', 'in_app') ), frequency: Joi.string().valid('always', 'daily', 'weekly', 'monthly'), topics: Joi.array().items(Joi.string()) }) }); // Data request validation schemas export const dataRequestSchema = Joi.object({ userId: Joi.string() .required(), type: Joi.string() .valid('access', 'portability', 'deletion', 'rectification', 'restriction', 'objection') .required(), requestDetails: Joi.object({ scope: Joi.string() .valid('all', 'specific') .default('all'), dataCategories: Joi.when('scope', { is: 'specific', then: Joi.array().items(Joi.string()).min(1).required(), otherwise: Joi.array().items(Joi.string()) }), format: Joi.string() .valid('json', 'csv', 'xml') .default('json'), delivery: Joi.string() .valid('download', 'email') .default('download'), corrections: Joi.when('type', { is: 'rectification', then: Joi.object().required(), otherwise: Joi.object() }), restrictions: Joi.when('type', { is: 'restriction', then: Joi.array().items(Joi.object({ purpose: Joi.string().required(), restriction: Joi.string().required() })).required(), otherwise: Joi.array() }), objections: Joi.when('type', { is: 'objection', then: Joi.array().items(Joi.object({ purpose: Joi.string().required(), reason: Joi.string().required() })).required(), otherwise: Joi.array() }) }).required(), reason: Joi.string() .max(500), regulation: Joi.string() .valid('gdpr', 'ccpa', 'lgpd', 'other') .default('gdpr') }); // Violation validation schemas export const violationSchema = Joi.object({ type: Joi.string() .valid( 'unauthorized_access', 'data_leak', 'consent_expired', 'retention_exceeded', 'unauthorized_sharing', 'security_breach', 'non_compliance', 'data_access_denied', 'notification_failure' ) .required(), severity: Joi.string() .valid('low', 'medium', 'high', 'critical') .required(), details: Joi.object({ description: Joi.string() .required(), affectedUsers: Joi.number() .integer() .min(0) .required(), affectedData: Joi.array() .items(Joi.string()) .required(), regulation: Joi.string() .valid('gdpr', 'ccpa', 'lgpd', 'other') .required(), articles: Joi.array() .items(Joi.string()), detectionMethod: Joi.string() .valid('automated', 'manual', 'user_report', 'audit') .required() }).required(), remediation: Joi.object({ status: Joi.string() .valid('pending', 'in_progress', 'completed') .default('pending'), actions: Joi.array().items(Joi.object({ action: Joi.string().required(), description: Joi.string(), targetDate: Joi.date(), status: Joi.string().valid('pending', 'completed').default('pending') })) }) }); // Validation middleware export const validateConsent = (req, res, next) => { const { error } = consentSchema.validate(req.body); if (error) { return res.status(400).json({ success: false, error: 'Validation error', details: error.details.map(d => d.message) }); } next(); }; export const validateDataRequest = (req, res, next) => { const { error } = dataRequestSchema.validate(req.body); if (error) { return res.status(400).json({ success: false, error: 'Validation error', details: error.details.map(d => d.message) }); } next(); }; export const validateViolation = (req, res, next) => { const { error } = violationSchema.validate(req.body); if (error) { return res.status(400).json({ success: false, error: 'Validation error', details: error.details.map(d => d.message) }); } next(); }; // Additional validation helpers export const validateEmail = (email) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; export const validatePhoneNumber = (phone) => { const phoneRegex = /^\+?[1-9]\d{1,14}$/; return phoneRegex.test(phone); }; export const validateIPAddress = (ip) => { const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; const ipv6Regex = /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i; return ipv4Regex.test(ip) || ipv6Regex.test(ip); }; export const validateDateRange = (startDate, endDate) => { const start = new Date(startDate); const end = new Date(endDate); return start <= end && start <= new Date(); }; export const validateRegulation = (regulation, country) => { const regulationMap = { gdpr: ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB', 'IS', 'LI', 'NO'], ccpa: ['US-CA'], lgpd: ['BR'] }; if (!regulationMap[regulation]) return false; if (regulation === 'ccpa' && country === 'US') { // CCPA only applies to California return true; } return regulationMap[regulation].includes(country); }; // Sanitization helpers export const sanitizeInput = (input) => { if (typeof input !== 'string') return input; // Remove potential XSS vectors return input .replace(/[<>]/g, '') .replace(/javascript:/gi, '') .replace(/on\w+=/gi, '') .trim(); }; export const sanitizeObject = (obj) => { const sanitized = {}; for (const [key, value] of Object.entries(obj)) { if (typeof value === 'string') { sanitized[key] = sanitizeInput(value); } else if (typeof value === 'object' && value !== null) { sanitized[key] = Array.isArray(value) ? value.map(item => typeof item === 'string' ? sanitizeInput(item) : item) : sanitizeObject(value); } else { sanitized[key] = value; } } return sanitized; };