ihm_client/src/services/message-validator.ts
NicolasCantu f628a64ad0 fix: Increase maxMessageSize for large handshake messages
**Motivations :**
- Fix WebSocket message validation for very large handshake messages (11MB+)
- Allow processing of extensive peer lists in handshake content

**Modifications :**
- Increased maxMessageSize from 1MB to 50MB
- Maintained security while allowing large legitimate messages

**Pages affectées :**
- src/services/message-validator.ts - Increased message size limit
2025-10-23 19:31:23 +02:00

356 lines
9.6 KiB
TypeScript

/**
* MessageValidator - Validation et sanitisation des messages
*/
export interface ValidationResult {
isValid: boolean;
errors: string[];
sanitizedData?: any;
}
export interface WebSocketMessage {
flag: string;
content: any;
timestamp?: number;
id?: string;
}
export class MessageValidator {
private static instance: MessageValidator;
private maxMessageSize = 50 * 1024 * 1024; // 50MB for large handshake messages
private maxStringLength = 100000; // Increased for large handshake messages
private allowedFlags = ['Handshake', 'NewTx', 'Cipher', 'Commit'];
private constructor() {}
public static getInstance(): MessageValidator {
if (!MessageValidator.instance) {
MessageValidator.instance = new MessageValidator();
}
return MessageValidator.instance;
}
/**
* Valide un message WebSocket
*/
validateWebSocketMessage(data: any): ValidationResult {
const errors: string[] = [];
// Vérifier le type de données
if (typeof data !== 'string') {
errors.push('Message must be a string');
return { isValid: false, errors };
}
// Vérifier la taille du message
if (data.length > this.maxMessageSize) {
errors.push(`Message too large: ${data.length} bytes (max: ${this.maxMessageSize})`);
return { isValid: false, errors };
}
// Parser le JSON
let parsedMessage: any;
try {
parsedMessage = JSON.parse(data);
} catch (error) {
errors.push('Invalid JSON format');
return { isValid: false, errors };
}
// Valider la structure du message
const structureValidation = this.validateMessageStructure(parsedMessage);
if (!structureValidation.isValid) {
return structureValidation;
}
// Sanitiser le contenu
const sanitizedContent = this.sanitizeContent(parsedMessage.content);
return {
isValid: true,
errors: [],
sanitizedData: {
...parsedMessage,
content: sanitizedContent,
timestamp: Date.now()
}
};
}
/**
* Valide la structure d'un message
*/
private validateMessageStructure(message: any): ValidationResult {
const errors: string[] = [];
// Vérifier que c'est un objet
if (typeof message !== 'object' || message === null) {
errors.push('Message must be an object');
return { isValid: false, errors };
}
// Vérifier la présence du flag
if (!message.flag || typeof message.flag !== 'string') {
errors.push('Message must have a valid flag');
return { isValid: false, errors };
}
// Vérifier que le flag est autorisé
if (!this.allowedFlags.includes(message.flag)) {
errors.push(`Invalid flag: ${message.flag}. Allowed: ${this.allowedFlags.join(', ')}`);
return { isValid: false, errors };
}
// Vérifier la présence du contenu
if (message.content === undefined || message.content === null) {
errors.push('Message must have content');
return { isValid: false, errors };
}
// Vérifier le type du contenu - peut être un objet ou une string JSON
if (typeof message.content === 'string') {
// Parser le contenu JSON si c'est une string
try {
message.content = JSON.parse(message.content);
} catch (error) {
errors.push('Content must be valid JSON if it is a string');
return { isValid: false, errors };
}
} else if (typeof message.content !== 'object') {
errors.push('Content must be an object or valid JSON string');
return { isValid: false, errors };
}
return { isValid: true, errors: [] };
}
/**
* Sanitise le contenu d'un message
*/
private sanitizeContent(content: any): any {
if (typeof content !== 'object' || content === null) {
return content;
}
const sanitized: any = {};
for (const [key, value] of Object.entries(content)) {
// Vérifier la longueur des clés
if (key.length > this.maxStringLength) {
continue; // Ignorer les clés trop longues
}
// Sanitiser les valeurs
if (typeof value === 'string') {
sanitized[key] = this.sanitizeString(value);
} else if (typeof value === 'object' && value !== null) {
sanitized[key] = this.sanitizeContent(value);
} else {
sanitized[key] = value;
}
}
return sanitized;
}
/**
* Sanitise une chaîne de caractères
*/
private sanitizeString(str: string): string {
// Vérifier la longueur
if (str.length > this.maxStringLength) {
return str.substring(0, this.maxStringLength) + '...';
}
// Supprimer les caractères de contrôle dangereux
return str.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
}
/**
* Valide un message de pairing
*/
validatePairingMessage(message: any): ValidationResult {
const errors: string[] = [];
if (!message.type) {
errors.push('Pairing message must have a type');
return { isValid: false, errors };
}
const allowedTypes = [
'PAIRING_4WORDS_CREATE',
'PAIRING_4WORDS_JOIN',
'PAIRING_4WORDS_WORDS_GENERATED',
'PAIRING_4WORDS_STATUS_UPDATE',
'PAIRING_4WORDS_SUCCESS',
'PAIRING_4WORDS_ERROR'
];
if (!allowedTypes.includes(message.type)) {
errors.push(`Invalid pairing message type: ${message.type}`);
return { isValid: false, errors };
}
// Validation spécifique selon le type
switch (message.type) {
case 'PAIRING_4WORDS_JOIN':
if (!message.words || typeof message.words !== 'string') {
errors.push('PAIRING_4WORDS_JOIN requires words');
}
if (message.words && message.words.length > 100) {
errors.push('Words too long');
}
break;
}
return { isValid: errors.length === 0, errors };
}
/**
* Valide un message de processus
*/
validateProcessMessage(message: any): ValidationResult {
const errors: string[] = [];
if (!message.type) {
errors.push('Process message must have a type');
return { isValid: false, errors };
}
const allowedTypes = [
'CREATE_PROCESS',
'UPDATE_PROCESS',
'GET_PROCESSES',
'GET_MY_PROCESSES'
];
if (!allowedTypes.includes(message.type)) {
errors.push(`Invalid process message type: ${message.type}`);
return { isValid: false, errors };
}
return { isValid: errors.length === 0, errors };
}
/**
* Valide un message de données
*/
validateDataMessage(message: any): ValidationResult {
const errors: string[] = [];
if (!message.type) {
errors.push('Data message must have a type');
return { isValid: false, errors };
}
const allowedTypes = [
'RETRIEVE_DATA',
'STORE_DATA',
'DELETE_DATA'
];
if (!allowedTypes.includes(message.type)) {
errors.push(`Invalid data message type: ${message.type}`);
return { isValid: false, errors };
}
// Vérifier la présence des données requises
if (message.type === 'STORE_DATA' && !message.data) {
errors.push('STORE_DATA requires data');
}
if (message.type === 'RETRIEVE_DATA' && !message.key) {
errors.push('RETRIEVE_DATA requires key');
}
return { isValid: errors.length === 0, errors };
}
/**
* Valide un message de token
*/
validateTokenMessage(message: any): ValidationResult {
const errors: string[] = [];
if (!message.type) {
errors.push('Token message must have a type');
return { isValid: false, errors };
}
const allowedTypes = [
'VALIDATE_TOKEN',
'RENEW_TOKEN',
'REVOKE_TOKEN'
];
if (!allowedTypes.includes(message.type)) {
errors.push(`Invalid token message type: ${message.type}`);
return { isValid: false, errors };
}
// Vérifier la présence du token
if (!message.token) {
errors.push('Token message requires token');
return { isValid: false, errors };
}
// Valider le format du token
if (typeof message.token !== 'string' || message.token.length < 10) {
errors.push('Invalid token format');
return { isValid: false, errors };
}
return { isValid: errors.length === 0, errors };
}
/**
* Valide un message générique
*/
validateGenericMessage(message: any): ValidationResult {
const errors: string[] = [];
if (!message.type) {
errors.push('Message must have a type');
return { isValid: false, errors };
}
// Vérifier la longueur du type
if (typeof message.type !== 'string' || message.type.length > 100) {
errors.push('Invalid message type');
return { isValid: false, errors };
}
// Vérifier la présence de données si nécessaire
if (message.data && typeof message.data !== 'object') {
errors.push('Message data must be an object');
return { isValid: false, errors };
}
return { isValid: errors.length === 0, errors };
}
/**
* Valide un message en fonction de son type
*/
validateMessage(message: any): ValidationResult {
if (!message.type) {
return { isValid: false, errors: ['Message must have a type'] };
}
// Validation spécifique selon le type
if (message.type.startsWith('PAIRING_')) {
return this.validatePairingMessage(message);
} else if (message.type.includes('PROCESS')) {
return this.validateProcessMessage(message);
} else if (message.type.includes('DATA')) {
return this.validateDataMessage(message);
} else if (message.type.includes('TOKEN')) {
return this.validateTokenMessage(message);
} else {
return this.validateGenericMessage(message);
}
}
}
// Instance singleton pour l'application
export const messageValidator = MessageValidator.getInstance();