Some checks failed
Deploy / deploy (push) Has been cancelled
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>
241 lines
7.2 KiB
JavaScript
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;
|
|
}; |