Files
telegram-management-system/marketing-agent/services/compliance-guard/src/utils/validation.js
你的用户名 237c7802e5
Some checks failed
Deploy / deploy (push) Has been cancelled
Initial commit: Telegram Management System
Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 15:37:50 +08:00

241 lines
7.2 KiB
JavaScript

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;
};