feat: Release version 3.1.4 - Mode terrain et génération PDF

 Nouvelles fonctionnalités:
- Ajout du mode terrain pour utilisation mobile hors connexion
- Génération automatique de reçus PDF avec template personnalisé
- Révision complète du système de cartes avec amélioration des performances

🔧 Améliorations techniques:
- Refactoring du module chat avec architecture simplifiée
- Optimisation du système de sécurité NIST SP 800-63B
- Amélioration de la gestion des secteurs géographiques
- Support UTF-8 étendu pour les noms d'utilisateurs

📱 Application mobile:
- Nouveau mode terrain dans user_field_mode_page
- Interface utilisateur adaptative pour conditions difficiles
- Synchronisation offline améliorée

🗺️ Cartographie:
- Optimisation des performances MapBox
- Meilleure gestion des tuiles hors ligne
- Amélioration de l'affichage des secteurs

📄 Documentation:
- Ajout guide Android (ANDROID-GUIDE.md)
- Documentation sécurité API (API-SECURITY.md)
- Guide module chat (CHAT_MODULE.md)

🐛 Corrections:
- Résolution des erreurs 400 lors de la création d'utilisateurs
- Correction de la validation des noms d'utilisateurs
- Fix des problèmes de synchronisation chat

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-19 19:38:03 +02:00
parent 4f7247eb2d
commit 3443277d4a
185 changed files with 109354 additions and 102937 deletions

View File

@@ -0,0 +1,150 @@
#!/usr/bin/env php
<?php
/**
* Script de nettoyage des données de sécurité
* À exécuter via cron quotidiennement
* Exemple crontab: 0 2 * * * /usr/bin/php /var/www/geosector/api/scripts/cron/cleanup_security_data.php
*/
declare(strict_types=1);
// Configuration
require_once __DIR__ . '/../../bootstrap.php';
require_once __DIR__ . '/../../src/Config/AppConfig.php';
require_once __DIR__ . '/../../src/Core/Database.php';
// Initialiser la configuration
$appConfig = AppConfig::getInstance();
$config = $appConfig->getFullConfig();
// Initialiser la base de données
Database::init($config['database']);
$db = Database::getInstance();
// Configuration de rétention (en jours)
$RETENTION_DAYS = [
'performance_metrics' => 30, // Garder 30 jours de métriques
'failed_login_attempts' => 7, // Garder 7 jours de tentatives
'resolved_alerts' => 90, // Garder 90 jours d'alertes résolues
'expired_blocks' => 0 // Débloquer immédiatement les IPs expirées
];
echo "[" . date('Y-m-d H:i:s') . "] Début du nettoyage des données de sécurité\n";
try {
$totalDeleted = 0;
// 1. Nettoyer les métriques de performance
echo "- Nettoyage des métriques de performance (>" . $RETENTION_DAYS['performance_metrics'] . " jours)...\n";
$stmt = $db->prepare('
DELETE FROM sec_performance_metrics
WHERE created_at < DATE_SUB(NOW(), INTERVAL :days DAY)
');
$stmt->execute(['days' => $RETENTION_DAYS['performance_metrics']]);
$deleted = $stmt->rowCount();
echo "$deleted lignes supprimées\n";
$totalDeleted += $deleted;
// 2. Nettoyer les tentatives de login échouées
echo "- Nettoyage des tentatives de login (>" . $RETENTION_DAYS['failed_login_attempts'] . " jours)...\n";
$stmt = $db->prepare('
DELETE FROM sec_failed_login_attempts
WHERE attempt_time < DATE_SUB(NOW(), INTERVAL :days DAY)
');
$stmt->execute(['days' => $RETENTION_DAYS['failed_login_attempts']]);
$deleted = $stmt->rowCount();
echo "$deleted lignes supprimées\n";
$totalDeleted += $deleted;
// 3. Nettoyer les alertes résolues
echo "- Nettoyage des alertes résolues (>" . $RETENTION_DAYS['resolved_alerts'] . " jours)...\n";
$stmt = $db->prepare('
DELETE FROM sec_alerts
WHERE resolved = 1
AND resolved_at < DATE_SUB(NOW(), INTERVAL :days DAY)
');
$stmt->execute(['days' => $RETENTION_DAYS['resolved_alerts']]);
$deleted = $stmt->rowCount();
echo "$deleted lignes supprimées\n";
$totalDeleted += $deleted;
// 4. Débloquer les IPs expirées
echo "- Déblocage des IPs expirées...\n";
$stmt = $db->prepare('
UPDATE sec_blocked_ips
SET unblocked_at = NOW()
WHERE blocked_until <= NOW()
AND unblocked_at IS NULL
AND permanent = 0
');
$stmt->execute();
$unblocked = $stmt->rowCount();
echo "$unblocked IPs débloquées\n";
// 5. Supprimer les anciennes IPs débloquées (optionnel, garder 180 jours d'historique)
echo "- Suppression des anciennes IPs débloquées (>180 jours)...\n";
$stmt = $db->prepare('
DELETE FROM sec_blocked_ips
WHERE unblocked_at IS NOT NULL
AND unblocked_at < DATE_SUB(NOW(), INTERVAL 180 DAY)
');
$stmt->execute();
$deleted = $stmt->rowCount();
echo "$deleted lignes supprimées\n";
$totalDeleted += $deleted;
// 6. Optimiser les tables (optionnel, peut être long sur de grosses tables)
if ($totalDeleted > 1000) {
echo "- Optimisation des tables...\n";
$tables = [
'sec_performance_metrics',
'sec_failed_login_attempts',
'sec_alerts',
'sec_blocked_ips'
];
foreach ($tables as $table) {
try {
$db->exec("OPTIMIZE TABLE $table");
echo " → Table $table optimisée\n";
} catch (Exception $e) {
echo " ⚠ Impossible d'optimiser $table: " . $e->getMessage() . "\n";
}
}
}
// 7. Statistiques finales
echo "\n=== RÉSUMÉ ===\n";
echo "Total supprimé: $totalDeleted lignes\n";
echo "IPs débloquées: $unblocked\n";
// Obtenir les statistiques actuelles
$stats = [];
$tables = [
'sec_alerts' => "SELECT COUNT(*) as total, SUM(resolved = 0) as active FROM sec_alerts",
'sec_performance_metrics' => "SELECT COUNT(*) as total FROM sec_performance_metrics",
'sec_failed_login_attempts' => "SELECT COUNT(*) as total FROM sec_failed_login_attempts",
'sec_blocked_ips' => "SELECT COUNT(*) as total, SUM(permanent = 1) as permanent FROM sec_blocked_ips WHERE unblocked_at IS NULL"
];
echo "\nÉtat actuel des tables:\n";
foreach ($tables as $table => $query) {
$result = $db->query($query)->fetch(PDO::FETCH_ASSOC);
if ($table === 'sec_alerts') {
echo "- $table: {$result['total']} total, {$result['active']} actives\n";
} elseif ($table === 'sec_blocked_ips') {
$permanent = $result['permanent'] ?? 0;
echo "- $table: {$result['total']} bloquées, $permanent permanentes\n";
} else {
echo "- $table: {$result['total']} enregistrements\n";
}
}
echo "\n[" . date('Y-m-d H:i:s') . "] Nettoyage terminé avec succès\n";
} catch (Exception $e) {
echo "\n❌ ERREUR: " . $e->getMessage() . "\n";
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
exit(1);
}

View File

@@ -0,0 +1,249 @@
#!/usr/bin/env php
<?php
/**
* Script d'initialisation des tables de sécurité
* Crée les tables si elles n'existent pas
*/
declare(strict_types=1);
require_once __DIR__ . '/../../bootstrap.php';
require_once __DIR__ . '/../../src/Config/AppConfig.php';
require_once __DIR__ . '/../../src/Core/Database.php';
// Initialiser la configuration
$appConfig = AppConfig::getInstance();
$config = $appConfig->getFullConfig();
// Initialiser la base de données
Database::init($config['database']);
$db = Database::getInstance();
echo "\n========================================\n";
echo " CRÉATION DES TABLES DE SÉCURITÉ\n";
echo "========================================\n\n";
try {
// Désactiver temporairement le mode strict pour les clés étrangères
$db->exec("SET FOREIGN_KEY_CHECKS = 0");
// 1. Table des alertes
echo "1. Création de la table sec_alerts...\n";
$db->exec("
CREATE TABLE IF NOT EXISTS `sec_alerts` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`alert_type` VARCHAR(50) NOT NULL COMMENT 'Type d\'alerte (BRUTE_FORCE, SQL_ERROR, etc.)',
`alert_level` ENUM('INFO', 'WARNING', 'ERROR', 'CRITICAL', 'SECURITY') NOT NULL DEFAULT 'INFO',
`ip_address` VARCHAR(45) DEFAULT NULL COMMENT 'Adresse IP source',
`user_id` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID utilisateur si connecté',
`username` VARCHAR(255) DEFAULT NULL COMMENT 'Username tenté ou utilisé',
`endpoint` VARCHAR(255) DEFAULT NULL COMMENT 'Endpoint API concerné',
`method` VARCHAR(10) DEFAULT NULL COMMENT 'Méthode HTTP',
`details` JSON DEFAULT NULL COMMENT 'Détails additionnels en JSON',
`occurrences` INT(11) DEFAULT 1 COMMENT 'Nombre d\'occurrences',
`first_seen` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_seen` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`email_sent` TINYINT(1) DEFAULT 0 COMMENT 'Email d\'alerte envoyé',
`email_sent_at` TIMESTAMP NULL DEFAULT NULL,
`resolved` TINYINT(1) DEFAULT 0 COMMENT 'Alerte résolue',
`resolved_at` TIMESTAMP NULL DEFAULT NULL,
`resolved_by` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID admin qui a résolu',
`notes` TEXT DEFAULT NULL COMMENT 'Notes de résolution',
KEY `idx_ip` (`ip_address`),
KEY `idx_type_time` (`alert_type`, `last_seen`),
KEY `idx_level` (`alert_level`),
KEY `idx_resolved` (`resolved`),
KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Alertes de sécurité et monitoring'
");
echo " ✓ Table sec_alerts créée\n";
// 2. Table des métriques de performance (SANS PARTITIONNEMENT)
echo "2. Création de la table sec_performance_metrics...\n";
$db->exec("
CREATE TABLE IF NOT EXISTS `sec_performance_metrics` (
`id` BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`endpoint` VARCHAR(255) NOT NULL COMMENT 'Endpoint API',
`method` VARCHAR(10) NOT NULL COMMENT 'Méthode HTTP',
`response_time_ms` INT(11) NOT NULL COMMENT 'Temps de réponse total en ms',
`db_time_ms` INT(11) DEFAULT 0 COMMENT 'Temps cumulé des requêtes DB en ms',
`db_queries_count` INT(11) DEFAULT 0 COMMENT 'Nombre de requêtes DB',
`memory_peak_mb` FLOAT DEFAULT NULL COMMENT 'Pic mémoire en MB',
`memory_start_mb` FLOAT DEFAULT NULL COMMENT 'Mémoire au début en MB',
`http_status` INT(11) DEFAULT NULL COMMENT 'Code HTTP de réponse',
`user_id` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID utilisateur si connecté',
`ip_address` VARCHAR(45) DEFAULT NULL COMMENT 'Adresse IP',
`user_agent` TEXT DEFAULT NULL COMMENT 'User Agent complet',
`request_size` INT(11) DEFAULT NULL COMMENT 'Taille de la requête en octets',
`response_size` INT(11) DEFAULT NULL COMMENT 'Taille de la réponse en octets',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `idx_endpoint_time` (`endpoint`, `created_at`),
KEY `idx_response_time` (`response_time_ms`),
KEY `idx_created` (`created_at`),
KEY `idx_status` (`http_status`),
KEY `idx_user` (`user_id`),
KEY `idx_date_endpoint` (`created_at`, `endpoint`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Métriques de performance des requêtes'
");
echo " ✓ Table sec_performance_metrics créée\n";
// 3. Table des tentatives de login échouées
echo "3. Création de la table sec_failed_login_attempts...\n";
$db->exec("
CREATE TABLE IF NOT EXISTS `sec_failed_login_attempts` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(255) DEFAULT NULL COMMENT 'Username tenté',
`encrypted_username` VARCHAR(255) DEFAULT NULL COMMENT 'Username chiffré si trouvé',
`ip_address` VARCHAR(45) NOT NULL COMMENT 'Adresse IP',
`user_agent` TEXT DEFAULT NULL COMMENT 'User Agent',
`attempt_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`error_type` VARCHAR(50) DEFAULT NULL COMMENT 'Type d\'erreur (invalid_password, user_not_found, etc.)',
`country_code` VARCHAR(2) DEFAULT NULL COMMENT 'Code pays de l\'IP (si géoloc activée)',
KEY `idx_ip_time` (`ip_address`, `attempt_time`),
KEY `idx_username` (`username`),
KEY `idx_encrypted_username` (`encrypted_username`),
KEY `idx_time` (`attempt_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tentatives de connexion échouées'
");
echo " ✓ Table sec_failed_login_attempts créée\n";
// 4. Table des IPs bloquées
echo "4. Création de la table sec_blocked_ips...\n";
$db->exec("
CREATE TABLE IF NOT EXISTS `sec_blocked_ips` (
`ip_address` VARCHAR(45) NOT NULL PRIMARY KEY COMMENT 'Adresse IP bloquée',
`reason` VARCHAR(255) NOT NULL COMMENT 'Raison du blocage',
`details` JSON DEFAULT NULL COMMENT 'Détails additionnels',
`blocked_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`blocked_until` TIMESTAMP NOT NULL COMMENT 'Bloqué jusqu\'à',
`blocked_by` VARCHAR(50) DEFAULT 'system' COMMENT 'Qui a bloqué (system ou user ID)',
`permanent` TINYINT(1) DEFAULT 0 COMMENT 'Blocage permanent',
`unblocked_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Date de déblocage effectif',
`unblocked_by` INT(11) UNSIGNED DEFAULT NULL COMMENT 'Qui a débloqué',
`block_count` INT(11) DEFAULT 1 COMMENT 'Nombre de fois bloquée',
KEY `idx_blocked_until` (`blocked_until`),
KEY `idx_permanent` (`permanent`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IPs bloquées temporairement ou définitivement'
");
echo " ✓ Table sec_blocked_ips créée\n";
// 5. Créer les vues
echo "5. Création des vues...\n";
// Vue pour les alertes actives
$db->exec("
CREATE OR REPLACE VIEW sec_active_alerts AS
SELECT
a.*,
u.encrypted_name as user_name,
r.encrypted_name as resolver_name
FROM sec_alerts a
LEFT JOIN users u ON a.user_id = u.id
LEFT JOIN users r ON a.resolved_by = r.id
WHERE a.resolved = 0
OR (a.resolved = 1 AND a.resolved_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR))
ORDER BY
CASE a.alert_level
WHEN 'SECURITY' THEN 1
WHEN 'CRITICAL' THEN 2
WHEN 'ERROR' THEN 3
WHEN 'WARNING' THEN 4
WHEN 'INFO' THEN 5
END,
a.last_seen DESC
");
echo " ✓ Vue sec_active_alerts créée\n";
// Vue pour les IPs suspectes
$db->exec("
CREATE OR REPLACE VIEW sec_suspicious_ips AS
SELECT
ip_address,
COUNT(*) as total_attempts,
COUNT(DISTINCT username) as unique_usernames,
MIN(attempt_time) as first_attempt,
MAX(attempt_time) as last_attempt,
TIMESTAMPDIFF(MINUTE, MIN(attempt_time), MAX(attempt_time)) as timespan_minutes
FROM sec_failed_login_attempts
WHERE attempt_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR)
GROUP BY ip_address
HAVING total_attempts >= 5
OR unique_usernames >= 3
ORDER BY total_attempts DESC
");
echo " ✓ Vue sec_suspicious_ips créée\n";
// 6. Créer les index additionnels
echo "6. Création des index additionnels...\n";
// Index pour les requêtes fréquentes
$db->exec("CREATE INDEX IF NOT EXISTS idx_sec_metrics_recent ON sec_performance_metrics(created_at DESC, endpoint)");
$db->exec("CREATE INDEX IF NOT EXISTS idx_sec_alerts_recent ON sec_alerts(last_seen DESC, alert_level)");
$db->exec("CREATE INDEX IF NOT EXISTS idx_sec_failed_recent ON sec_failed_login_attempts(attempt_time DESC, ip_address)");
echo " ✓ Index créés\n";
// 7. Créer la procédure de nettoyage
echo "7. Création de la procédure de nettoyage...\n";
$db->exec("DROP PROCEDURE IF EXISTS sec_cleanup_old_data");
$db->exec("
CREATE PROCEDURE sec_cleanup_old_data(IN days_to_keep INT)
BEGIN
-- Nettoyer les métriques de performance
DELETE FROM sec_performance_metrics
WHERE created_at < DATE_SUB(NOW(), INTERVAL days_to_keep DAY);
-- Nettoyer les tentatives de login
DELETE FROM sec_failed_login_attempts
WHERE attempt_time < DATE_SUB(NOW(), INTERVAL days_to_keep DAY);
-- Nettoyer les alertes résolues
DELETE FROM sec_alerts
WHERE resolved = 1
AND resolved_at < DATE_SUB(NOW(), INTERVAL days_to_keep DAY);
-- Retourner le nombre de lignes supprimées
SELECT ROW_COUNT() as deleted_rows;
END
");
echo " ✓ Procédure sec_cleanup_old_data créée\n";
// Réactiver les clés étrangères
$db->exec("SET FOREIGN_KEY_CHECKS = 1");
// 8. Vérifier que tout est créé
echo "\n8. Vérification finale...\n";
$tables = ['sec_alerts', 'sec_performance_metrics', 'sec_failed_login_attempts', 'sec_blocked_ips'];
$allOk = true;
foreach ($tables as $table) {
$stmt = $db->query("SELECT COUNT(*) as count FROM $table");
if ($stmt) {
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo " ✓ Table $table : OK ({$result['count']} enregistrements)\n";
} else {
echo " ✗ Table $table : ERREUR\n";
$allOk = false;
}
}
if ($allOk) {
echo "\n========================================\n";
echo "✅ TOUTES LES TABLES ONT ÉTÉ CRÉÉES AVEC SUCCÈS\n";
echo "========================================\n\n";
echo "Le système de sécurité est maintenant prêt à être utilisé.\n";
echo "Vous pouvez tester avec : php test_security.php\n\n";
} else {
echo "\n⚠️ Certaines tables n'ont pas pu être créées.\n";
echo "Vérifiez les erreurs ci-dessus.\n\n";
}
} catch (PDOException $e) {
echo "\n❌ ERREUR SQL : " . $e->getMessage() . "\n\n";
echo "Code d'erreur : " . $e->getCode() . "\n";
echo "Vérifiez les permissions et la configuration de la base de données.\n\n";
exit(1);
} catch (Exception $e) {
echo "\n❌ ERREUR : " . $e->getMessage() . "\n\n";
exit(1);
}

View File

@@ -0,0 +1,157 @@
-- Script de création des tables pour le module Chat
-- Date : 2025-01-17
-- Version : 1.0
-- Tables préfixées "chat_" pour le module de messagerie
-- ============================================
-- SUPPRESSION DES TABLES EXISTANTES
-- ============================================
-- Attention : Ceci supprimera toutes les données existantes du chat !
-- Désactiver temporairement les contraintes de clés étrangères
SET FOREIGN_KEY_CHECKS = 0;
-- Supprimer la vue si elle existe
DROP VIEW IF EXISTS chat_rooms_with_last_message;
-- Supprimer les tables dans l'ordre inverse des dépendances
DROP TABLE IF EXISTS `chat_read_receipts`;
DROP TABLE IF EXISTS `chat_participants`;
DROP TABLE IF EXISTS `chat_messages`;
DROP TABLE IF EXISTS `chat_rooms`;
-- Supprimer toute autre table commençant par chat_ qui pourrait exister
-- Note : Cette procédure supprime dynamiquement toutes les tables avec le préfixe chat_
DELIMITER $$
DROP PROCEDURE IF EXISTS drop_chat_tables$$
CREATE PROCEDURE drop_chat_tables()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE tableName VARCHAR(255);
DECLARE cur CURSOR FOR
SELECT table_name
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name LIKE 'chat_%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO tableName;
IF done THEN
LEAVE read_loop;
END IF;
SET @sql = CONCAT('DROP TABLE IF EXISTS `', tableName, '`');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur;
END$$
DELIMITER ;
-- Exécuter la procédure
CALL drop_chat_tables();
-- Supprimer la procédure après utilisation
DROP PROCEDURE IF EXISTS drop_chat_tables;
-- Réactiver les contraintes de clés étrangères
SET FOREIGN_KEY_CHECKS = 1;
-- ============================================
-- CRÉATION DES NOUVELLES TABLES
-- ============================================
-- Table des salles de conversation
CREATE TABLE IF NOT EXISTS `chat_rooms` (
`id` VARCHAR(36) NOT NULL PRIMARY KEY COMMENT 'UUID de la salle',
`title` VARCHAR(255) DEFAULT NULL COMMENT 'Titre de la conversation',
`type` ENUM('private', 'group', 'broadcast') NOT NULL DEFAULT 'private' COMMENT 'Type de conversation',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date de création',
`created_by` INT(11) UNSIGNED NOT NULL COMMENT 'ID du créateur',
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT 'Dernière modification',
`is_active` TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'Conversation active',
KEY `idx_created_by` (`created_by`),
KEY `idx_type` (`type`),
KEY `idx_created_at` (`created_at`),
CONSTRAINT `fk_chat_rooms_creator` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Salles de conversation';
-- Table des messages
CREATE TABLE IF NOT EXISTS `chat_messages` (
`id` VARCHAR(36) NOT NULL PRIMARY KEY COMMENT 'UUID du message',
`room_id` VARCHAR(36) NOT NULL COMMENT 'ID de la salle',
`content` TEXT NOT NULL COMMENT 'Contenu du message',
`sender_id` INT(11) UNSIGNED NOT NULL COMMENT 'ID de l\'expéditeur',
`sent_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date d\'envoi',
`edited_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Date de modification',
`is_deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Message supprimé',
KEY `idx_room_id` (`room_id`),
KEY `idx_sender_id` (`sender_id`),
KEY `idx_sent_at` (`sent_at`),
KEY `idx_room_sent` (`room_id`, `sent_at`),
CONSTRAINT `fk_chat_messages_room` FOREIGN KEY (`room_id`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_chat_messages_sender` FOREIGN KEY (`sender_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Messages du chat';
-- Table des participants
CREATE TABLE IF NOT EXISTS `chat_participants` (
`room_id` VARCHAR(36) NOT NULL COMMENT 'ID de la salle',
`user_id` INT(11) UNSIGNED NOT NULL COMMENT 'ID de l\'utilisateur',
`role` INT(11) DEFAULT NULL COMMENT 'Rôle de l\'utilisateur (fk_role)',
`entite_id` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID de l\'entité',
`joined_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date d\'adhésion',
`left_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Date de départ',
`is_admin` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Admin de la salle',
`last_read_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Dernière lecture',
PRIMARY KEY (`room_id`, `user_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_entite_id` (`entite_id`),
KEY `idx_joined_at` (`joined_at`),
CONSTRAINT `fk_chat_participants_room` FOREIGN KEY (`room_id`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_chat_participants_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_chat_participants_entite` FOREIGN KEY (`entite_id`) REFERENCES `entites` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Participants aux conversations';
-- Table des accusés de lecture
CREATE TABLE IF NOT EXISTS `chat_read_receipts` (
`message_id` VARCHAR(36) NOT NULL COMMENT 'ID du message',
`user_id` INT(11) UNSIGNED NOT NULL COMMENT 'ID de l\'utilisateur',
`read_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date de lecture',
PRIMARY KEY (`message_id`, `user_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_read_at` (`read_at`),
CONSTRAINT `fk_chat_read_message` FOREIGN KEY (`message_id`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_chat_read_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Accusés de lecture';
-- Index supplémentaires pour les performances
CREATE INDEX idx_chat_active_rooms ON chat_rooms(is_active, created_at DESC);
CREATE INDEX idx_chat_user_rooms ON chat_participants(user_id, left_at, joined_at DESC);
CREATE INDEX idx_chat_unread ON chat_messages(room_id, sent_at) WHERE id NOT IN (SELECT message_id FROM chat_read_receipts);
-- Vue pour faciliter la récupération des conversations avec le dernier message
CREATE OR REPLACE VIEW chat_rooms_with_last_message AS
SELECT
r.*,
m.content as last_message_content,
m.sender_id as last_message_sender,
m.sent_at as last_message_at,
u.encrypted_name as last_message_sender_name
FROM chat_rooms r
LEFT JOIN (
SELECT m1.*
FROM chat_messages m1
INNER JOIN (
SELECT room_id, MAX(sent_at) as max_sent_at
FROM chat_messages
WHERE is_deleted = 0
GROUP BY room_id
) m2 ON m1.room_id = m2.room_id AND m1.sent_at = m2.max_sent_at
) m ON r.id = m.room_id
LEFT JOIN users u ON m.sender_id = u.id
WHERE r.is_active = 1;

View File

@@ -0,0 +1,123 @@
-- Script de création des tables pour le module Security & Monitoring
-- Date : 2025-01-17
-- Version : 1.0
-- Préfixe : sec_ (security)
-- ============================================
-- SUPPRESSION DES TABLES EXISTANTES (OPTIONNEL)
-- ============================================
-- Décommenter si vous voulez recréer les tables
-- DROP TABLE IF EXISTS `sec_blocked_ips`;
-- DROP TABLE IF EXISTS `sec_failed_login_attempts`;
-- DROP TABLE IF EXISTS `sec_performance_metrics`;
-- DROP TABLE IF EXISTS `sec_alerts`;
-- ============================================
-- CRÉATION DES TABLES DE SÉCURITÉ ET MONITORING
-- ============================================
-- Table principale des alertes de sécurité
CREATE TABLE IF NOT EXISTS `sec_alerts` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`alert_type` VARCHAR(50) NOT NULL COMMENT 'Type d\'alerte (BRUTE_FORCE, SQL_ERROR, etc.)',
`alert_level` ENUM('INFO', 'WARNING', 'ERROR', 'CRITICAL', 'SECURITY') NOT NULL DEFAULT 'INFO',
`ip_address` VARCHAR(45) DEFAULT NULL COMMENT 'Adresse IP source',
`user_id` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID utilisateur si connecté',
`username` VARCHAR(255) DEFAULT NULL COMMENT 'Username tenté ou utilisé',
`endpoint` VARCHAR(255) DEFAULT NULL COMMENT 'Endpoint API concerné',
`method` VARCHAR(10) DEFAULT NULL COMMENT 'Méthode HTTP',
`details` JSON DEFAULT NULL COMMENT 'Détails additionnels en JSON',
`occurrences` INT(11) DEFAULT 1 COMMENT 'Nombre d\'occurrences',
`first_seen` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_seen` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`email_sent` TINYINT(1) DEFAULT 0 COMMENT 'Email d\'alerte envoyé',
`email_sent_at` TIMESTAMP NULL DEFAULT NULL,
`resolved` TINYINT(1) DEFAULT 0 COMMENT 'Alerte résolue',
`resolved_at` TIMESTAMP NULL DEFAULT NULL,
`resolved_by` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID admin qui a résolu',
`notes` TEXT DEFAULT NULL COMMENT 'Notes de résolution',
KEY `idx_ip` (`ip_address`),
KEY `idx_type_time` (`alert_type`, `last_seen`),
KEY `idx_level` (`alert_level`),
KEY `idx_resolved` (`resolved`),
KEY `idx_user` (`user_id`),
CONSTRAINT `fk_sec_alerts_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_sec_alerts_resolver` FOREIGN KEY (`resolved_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Alertes de sécurité et monitoring';
-- Table des métriques de performance
CREATE TABLE IF NOT EXISTS `sec_performance_metrics` (
`id` BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`endpoint` VARCHAR(255) NOT NULL COMMENT 'Endpoint API',
`method` VARCHAR(10) NOT NULL COMMENT 'Méthode HTTP',
`response_time_ms` INT(11) NOT NULL COMMENT 'Temps de réponse total en ms',
`db_time_ms` INT(11) DEFAULT 0 COMMENT 'Temps cumulé des requêtes DB en ms',
`db_queries_count` INT(11) DEFAULT 0 COMMENT 'Nombre de requêtes DB',
`memory_peak_mb` FLOAT DEFAULT NULL COMMENT 'Pic mémoire en MB',
`memory_start_mb` FLOAT DEFAULT NULL COMMENT 'Mémoire au début en MB',
`http_status` INT(11) DEFAULT NULL COMMENT 'Code HTTP de réponse',
`user_id` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID utilisateur si connecté',
`ip_address` VARCHAR(45) DEFAULT NULL COMMENT 'Adresse IP',
`user_agent` TEXT DEFAULT NULL COMMENT 'User Agent complet',
`request_size` INT(11) DEFAULT NULL COMMENT 'Taille de la requête en octets',
`response_size` INT(11) DEFAULT NULL COMMENT 'Taille de la réponse en octets',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `idx_endpoint_time` (`endpoint`, `created_at`),
KEY `idx_response_time` (`response_time_ms`),
KEY `idx_created` (`created_at`),
KEY `idx_status` (`http_status`),
KEY `idx_user` (`user_id`),
KEY `idx_date_endpoint` (`created_at`, `endpoint`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Métriques de performance des requêtes';
-- Table des tentatives de login échouées
CREATE TABLE IF NOT EXISTS `sec_failed_login_attempts` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(255) DEFAULT NULL COMMENT 'Username tenté',
`encrypted_username` VARCHAR(255) DEFAULT NULL COMMENT 'Username chiffré si trouvé',
`ip_address` VARCHAR(45) NOT NULL COMMENT 'Adresse IP',
`user_agent` TEXT DEFAULT NULL COMMENT 'User Agent',
`attempt_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`error_type` VARCHAR(50) DEFAULT NULL COMMENT 'Type d\'erreur (invalid_password, user_not_found, etc.)',
`country_code` VARCHAR(2) DEFAULT NULL COMMENT 'Code pays de l\'IP (si géoloc activée)',
KEY `idx_ip_time` (`ip_address`, `attempt_time`),
KEY `idx_username` (`username`),
KEY `idx_encrypted_username` (`encrypted_username`),
KEY `idx_time` (`attempt_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tentatives de connexion échouées';
-- Table des IPs bloquées
CREATE TABLE IF NOT EXISTS `sec_blocked_ips` (
`ip_address` VARCHAR(45) NOT NULL PRIMARY KEY COMMENT 'Adresse IP bloquée',
`reason` VARCHAR(255) NOT NULL COMMENT 'Raison du blocage',
`details` JSON DEFAULT NULL COMMENT 'Détails additionnels',
`blocked_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`blocked_until` TIMESTAMP NOT NULL COMMENT 'Bloqué jusqu\'à',
`blocked_by` VARCHAR(50) DEFAULT 'system' COMMENT 'Qui a bloqué (system ou user ID)',
`permanent` TINYINT(1) DEFAULT 0 COMMENT 'Blocage permanent',
`unblocked_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Date de déblocage effectif',
`unblocked_by` INT(11) UNSIGNED DEFAULT NULL COMMENT 'Qui a débloqué',
`block_count` INT(11) DEFAULT 1 COMMENT 'Nombre de fois bloquée',
KEY `idx_blocked_until` (`blocked_until`),
KEY `idx_permanent` (`permanent`),
CONSTRAINT `fk_sec_blocked_unblocked_by` FOREIGN KEY (`unblocked_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IPs bloquées temporairement ou définitivement';
-- ============================================
-- INDEX ADDITIONNELS POUR PERFORMANCES
-- ============================================
-- Index pour requêtes de monitoring fréquentes
CREATE INDEX idx_sec_metrics_recent ON sec_performance_metrics(created_at DESC, endpoint);
CREATE INDEX idx_sec_alerts_recent ON sec_alerts(last_seen DESC, alert_level);
CREATE INDEX idx_sec_failed_recent ON sec_failed_login_attempts(attempt_time DESC, ip_address);
-- ============================================
-- FIN DU SCRIPT
-- ============================================
-- Note: La purge des données anciennes doit être gérée par:
-- 1. Un cron qui appelle l'endpoint API /api/admin/cleanup
-- 2. Ou directement via les méthodes cleanup des services PHP

View File

@@ -0,0 +1,35 @@
-- Migration pour supporter les usernames UTF-8 avec jusqu'à 30 caractères
-- Date : 2025-01-17
-- Objectif : Permettre des usernames plus souples (émojis, accents, espaces, etc.)
-- IMPORTANT : Faire une sauvegarde avant d'exécuter ce script !
-- mysqldump -u root -p geo_app > backup_geo_app_$(date +%Y%m%d).sql
-- Augmenter la taille de la colonne encrypted_user_name pour supporter
-- les usernames UTF-8 de 30 caractères maximum une fois chiffrés
-- Un username de 30 caractères UTF-8 peut faire jusqu'à 120 octets
-- Après chiffrement AES-256-CBC + base64, cela peut atteindre ~200 caractères
ALTER TABLE `users`
MODIFY COLUMN `encrypted_user_name` varchar(255) DEFAULT ''
COMMENT 'Username chiffré - Supporte UTF-8 30 caractères maximum';
-- Vérifier que la modification a bien été appliquée
SELECT
COLUMN_NAME,
COLUMN_TYPE,
CHARACTER_MAXIMUM_LENGTH,
COLUMN_COMMENT
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'users'
AND COLUMN_NAME = 'encrypted_user_name';
-- Note : Les nouvelles règles de validation des usernames sont :
-- - Minimum : 8 caractères UTF-8
-- - Maximum : 30 caractères UTF-8
-- - Accepte TOUS les caractères (lettres, chiffres, espaces, émojis, accents, etc.)
-- - Trim automatique des espaces en début/fin
-- - Unicité vérifiée dans toute la base