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

334
api/docs/API-SECURITY.md Normal file
View File

@@ -0,0 +1,334 @@
# API Security & Performance Monitoring
## 📋 Vue d'ensemble
Système complet de sécurité et monitoring pour l'API GeoSector implémenté et opérationnel.
### ✅ Fonctionnalités implémentées
- **Détection d'intrusions** : Brute force, SQL injection, patterns de scan
- **Monitoring des performances** : Temps de réponse, utilisation mémoire, requêtes DB
- **Alertes email intelligentes** : Throttling, niveaux de priorité
- **Blocage d'IP automatique** : Temporaire ou permanent
- **Traçabilité complète** : Historique pour audit et analyse
## 🏗️ Architecture
### Tables de base de données (préfixe `sec_`)
```sql
-- 4 tables créées dans scripts/sql/create_security_tables.sql
sec_alerts -- Alertes de sécurité
sec_performance_metrics -- Métriques de performance
sec_failed_login_attempts -- Tentatives de connexion échouées
sec_blocked_ips -- IPs bloquées
```
### Services PHP implémentés
```
src/Services/Security/
├── AlertService.php # Gestion centralisée des alertes
├── EmailThrottler.php # Anti-spam pour emails
├── SecurityMonitor.php # Détection des menaces
├── PerformanceMonitor.php # Monitoring des temps
└── IPBlocker.php # Gestion des blocages IP
```
### Contrôleur d'administration
```
src/Controllers/SecurityController.php # Interface d'administration
```
## 🚀 Installation
### 1. Créer les tables
```bash
# Exécuter le script SQL sur chaque environnement
mysql -u root -p geo_app < scripts/sql/create_security_tables.sql
```
### 2. Configurer le cron de purge
```bash
# Ajouter dans crontab (crontab -e)
0 2 * * * /usr/bin/php /var/www/geosector/api/scripts/cron/cleanup_security_data.php >> /var/log/security_cleanup.log 2>&1
```
### 3. Tester l'installation
```bash
php test_security.php
```
## 🔒 Fonctionnement
### Détection automatique
Le système détecte et bloque automatiquement :
- **Brute force** : 5 tentatives échouées en 5 minutes → IP bloquée 1h
- **SQL injection** : Patterns suspects → IP bloquée définitivement
- **Scan de vulnérabilités** : Accès aux fichiers sensibles → IP bloquée 1h
- **Rate limiting** : Plus de 60 requêtes/minute → Rejet temporaire
### Monitoring de performance
Chaque requête est automatiquement monitorée :
```php
// Dans index.php
PerformanceMonitor::startRequest();
// ... traitement ...
PerformanceMonitor::endRequest($endpoint, $method, $statusCode);
```
### Alertes email
Configuration des niveaux :
- **INFO** : Log uniquement
- **WARNING** : Email avec throttling 1h
- **ERROR** : Email avec throttling 15min
- **CRITICAL** : Email avec throttling 5min
- **SECURITY** : Email immédiat, priorité haute
## 📊 Endpoints d'administration
Tous les endpoints nécessitent une authentification admin (role >= 2) :
```
GET /api/admin/metrics # Métriques de performance
GET /api/admin/alerts # Alertes actives
POST /api/admin/alerts/:id/resolve # Résoudre une alerte
GET /api/admin/blocked-ips # IPs bloquées
POST /api/admin/unblock-ip # Débloquer une IP
POST /api/admin/block-ip # Bloquer une IP manuellement
GET /api/admin/security-report # Rapport complet
POST /api/admin/cleanup # Nettoyer les anciennes données
POST /api/admin/test-alert # Tester les alertes
```
## 🔧 Configuration
### Seuils par défaut (modifiables dans les services)
```php
// PerformanceMonitor.php
const DEFAULT_THRESHOLDS = [
'response_time_warning' => 1000, // 1 seconde
'response_time_critical' => 3000, // 3 secondes
'db_time_warning' => 500, // 500ms
'db_time_critical' => 1000, // 1 seconde
'memory_warning' => 64, // 64 MB
'memory_critical' => 128 // 128 MB
];
// SecurityMonitor.php
- Brute force : 5 tentatives en 5 minutes
- Rate limit : 60 requêtes par minute
- 404 pattern : 10 erreurs 404 en 10 minutes
// EmailThrottler.php
const DEFAULT_CONFIG = [
'max_per_hour' => 10,
'max_per_day' => 50,
'digest_after' => 5,
'cooldown_minutes' => 60
];
```
### Rétention des données
Configurée dans `scripts/cron/cleanup_security_data.php` :
```php
$RETENTION_DAYS = [
'performance_metrics' => 30, // 30 jours
'failed_login_attempts' => 7, // 7 jours
'resolved_alerts' => 90, // 90 jours
'expired_blocks' => 0 // Déblocage immédiat
];
```
## 📈 Métriques surveillées
### Performance
- Temps de réponse total
- Temps cumulé des requêtes DB
- Nombre de requêtes DB
- Utilisation mémoire (pic et moyenne)
- Codes HTTP de réponse
### Sécurité
- Tentatives de connexion échouées
- IPs bloquées (temporaires/permanentes)
- Patterns d'attaque détectés
- Alertes par type et niveau
## 🛡️ Patterns de détection
### SQL Injection
```php
// Patterns détectés dans SecurityMonitor.php
- UNION SELECT
- DROP TABLE
- INSERT INTO
- UPDATE SET
- DELETE FROM
- Script tags
- OR 1=1
- Commentaires SQL (--)
```
### Fichiers sensibles
```php
// Patterns de scan détectés
- admin, administrator
- wp-admin, phpmyadmin
- .git, .env
- config.php
- backup, .sql, .zip
- shell.php, eval.php
```
## 📝 Exemples d'utilisation
### Déclencher une alerte manuelle
```php
use App\Services\Security\AlertService;
AlertService::trigger('CUSTOM_ALERT', [
'message' => 'Événement important détecté',
'details' => ['user' => $userId, 'action' => $action]
], 'WARNING');
```
### Bloquer une IP manuellement
```php
use App\Services\Security\IPBlocker;
// Blocage temporaire (1 heure)
IPBlocker::block('192.168.1.100', 3600, 'Comportement suspect');
// Blocage permanent
IPBlocker::blockPermanent('192.168.1.100', 'Attaque confirmée');
```
### Obtenir les statistiques
```php
use App\Services\Security\SecurityMonitor;
use App\Services\Security\PerformanceMonitor;
$securityStats = SecurityMonitor::getSecurityStats();
$perfStats = PerformanceMonitor::getStats(null, 24); // 24h
```
## ⚠️ Points d'attention
### RGPD
- Les IPs sont des données personnelles
- Durée de conservation limitée (voir rétention)
- Anonymisation après traitement
### Performance
- Overhead < 5ms par requête
- Optimisation des tables avec index
- Purge automatique des anciennes données
### Sécurité
- Pas d'exposition de données sensibles dans les alertes
- Chiffrement des données utilisateur
- Whitelist pour IPs de confiance (localhost)
## 🔄 Maintenance
### Quotidienne (cron)
```bash
# Purge automatique à 2h du matin
0 2 * * * php /var/www/geosector/api/scripts/cron/cleanup_security_data.php
```
### Hebdomadaire
- Vérifier les alertes actives
- Analyser les tendances de performance
- Ajuster les seuils si nécessaire
### Mensuelle
- Analyser le rapport de sécurité
- Mettre à jour les IPs whitelist/blacklist
- Optimiser les tables si nécessaire
## 🐛 Dépannage
### Les tables n'existent pas
```bash
# Créer les tables
mysql -u root -p geo_app < scripts/sql/create_security_tables.sql
```
### Pas d'alertes email
- Vérifier la configuration email dans `AppConfig`
- Vérifier les logs : `tail -f logs/geosector-*.log`
- Tester avec : `POST /api/admin/test-alert`
### IP bloquée par erreur
```bash
# Via API
curl -X POST https://dapp.geosector.fr/api/admin/unblock-ip \
-H "Authorization: Bearer TOKEN" \
-d '{"ip": "192.168.1.100"}'
# Via MySQL
UPDATE sec_blocked_ips SET unblocked_at = NOW() WHERE ip_address = '192.168.1.100';
```
## 📚 Ressources
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [PHP Security Best Practices](https://www.php.net/manual/en/security.php)
- Code source : `/src/Services/Security/`
- Tests : `test_security.php`
- Logs : `/logs/geosector-*.log`
## 🎯 Statut d'implémentation
**Phase 1** : Infrastructure de base - COMPLÉTÉ
- Tables créées avec préfixe `sec_`
- Services PHP implémentés
- Intégration dans index.php et Database.php
**Phase 2** : Monitoring de Performance - COMPLÉTÉ
- Chronométrage automatique des requêtes
- Monitoring des requêtes DB
- Alertes sur dégradation
**Phase 3** : Détection d'intrusions - COMPLÉTÉ
- Détection brute force
- Détection SQL injection
- Blocage IP automatique
**Phase 4** : Alertes Email - COMPLÉTÉ
- Service d'alertes avec throttling
- Templates d'emails
- Niveaux de priorité
**Phase 5** : Administration - COMPLÉTÉ
- Endpoints d'administration
- Interface de gestion
- Rapports de sécurité
**Phase 6** : Maintenance - COMPLÉTÉ
- Script de purge automatique
- Optimisation des tables
- Documentation complète
---
*Dernière mise à jour : 2025-01-17*
*Version : 1.0.0*

419
api/docs/CHAT_MODULE.md Normal file
View File

@@ -0,0 +1,419 @@
# Module Chat - Documentation API
## Vue d'ensemble
Le module Chat permet aux utilisateurs de l'application GeoSector de communiquer entre eux via une messagerie intégrée. Il supporte les conversations privées, de groupe et les diffusions (broadcast).
## Architecture
### Tables de base de données
- `chat_rooms` : Salles de conversation
- `chat_messages` : Messages échangés
- `chat_participants` : Participants aux conversations
- `chat_read_receipts` : Accusés de lecture
### Permissions par rôle
| Rôle | Permissions |
|------|------------|
| **1 - Utilisateur** | Conversations privées et groupes avec membres de son entité |
| **2 - Admin entité** | Toutes conversations de son entité + création de diffusions |
| **> 2 - Super admin** | Accès total à toutes les conversations |
## Flux d'utilisation du module Chat
### 📱 Vue d'ensemble du flux
Le module Chat fonctionne en mode **chargement dynamique** : les données sont récupérées à la demande, pas toutes en une fois au login.
### 1. Au login (`/api/login`)
La réponse du login contient un objet `chat` avec les informations de base :
```json
{
"status": "success",
"user": {...},
"amicale": {...},
"chat": {
"total_rooms": 5, // Nombre total de conversations
"unread_messages": 12, // Total messages non lus
"chat_enabled": true, // Module activé pour cet utilisateur
"last_active_room": { // Dernière conversation active
"id": "uuid-room-123",
"title": "Discussion équipe",
"type": "group",
"last_message": "À demain !",
"last_message_at": "2025-01-17 18:30:00"
}
}
}
```
→ Permet d'afficher un **badge de notification** et de savoir si le chat est disponible
### 2. Ouverture de la page Chat
#### Étape 1 : Chargement initial
```
GET /api/chat/rooms
```
→ Récupère la liste des conversations avec aperçu du dernier message
#### Étape 2 : Sélection d'une conversation
```
GET /api/chat/rooms/{room_id}/messages?limit=50
```
→ Charge les 50 derniers messages (pagination disponible)
#### Étape 3 : Marquage comme lu
```
POST /api/chat/rooms/{room_id}/read
```
→ Met à jour les compteurs de messages non lus
### 3. Actions utilisateur
| Action | Endpoint | Description |
|--------|----------|-------------|
| **Envoyer un message** | `POST /api/chat/rooms/{id}/messages` | Envoie et retourne le message créé |
| **Créer une conversation** | `POST /api/chat/rooms` | Crée une nouvelle room |
| **Obtenir les destinataires** | `GET /api/chat/recipients` | Liste des contacts disponibles |
| **Charger plus de messages** | `GET /api/chat/rooms/{id}/messages?before={msg_id}` | Pagination |
### 4. Stratégies de rafraîchissement
#### Polling (recommandé pour débuter)
- Rafraîchir `/api/chat/rooms` toutes les 30 secondes
- Rafraîchir les messages de la conversation active toutes les 10 secondes
#### Pull to refresh
- Permettre à l'utilisateur de rafraîchir manuellement
#### Lifecycle events
- Recharger quand l'app revient au premier plan
- Rafraîchir après envoi d'un message
### 5. Exemple d'implémentation Flutter
```dart
class ChatService {
Timer? _roomsTimer;
Timer? _messagesTimer;
// 1. Au login, stocker les infos de base
void initFromLogin(Map<String, dynamic> chatData) {
_unreadCount = chatData['unread_messages'];
_chatEnabled = chatData['chat_enabled'];
notifyListeners();
}
// 2. À l'ouverture du chat
Future<void> openChatPage() async {
// Charger les conversations
final rooms = await api.get('/api/chat/rooms');
_rooms = rooms['rooms'];
// Démarrer le polling
_startPolling();
}
// 3. Sélection d'une conversation
Future<void> selectRoom(String roomId) async {
// Charger les messages
final response = await api.get('/api/chat/rooms/$roomId/messages');
_currentMessages = response['messages'];
// Marquer comme lu
await api.post('/api/chat/rooms/$roomId/read');
// Rafraîchir plus fréquemment cette conversation
_startMessagePolling(roomId);
}
// 4. Polling automatique
void _startPolling() {
_roomsTimer = Timer.periodic(Duration(seconds: 30), (_) {
_refreshRooms();
});
}
// 5. Nettoyage
void dispose() {
_roomsTimer?.cancel();
_messagesTimer?.cancel();
}
}
```
## Endpoints API
### 1. GET /api/chat/rooms
**Description** : Récupère la liste des conversations de l'utilisateur
**Réponse** :
```json
{
"status": "success",
"rooms": [
{
"id": "uuid-room-1",
"title": "Discussion équipe",
"type": "group",
"created_at": "2025-01-17 10:00:00",
"created_by": 123,
"updated_at": "2025-01-17 14:30:00",
"last_message": "Bonjour tout le monde",
"last_message_at": "2025-01-17 14:30:00",
"unread_count": 3,
"participant_count": 5,
"participants": [
{
"user_id": 123,
"name": "Jean Dupont",
"first_name": "Jean",
"is_admin": true
}
]
}
]
}
```
### 2. POST /api/chat/rooms
**Description** : Crée une nouvelle conversation
**Body** :
```json
{
"type": "private|group|broadcast",
"title": "Titre optionnel (requis pour group/broadcast)",
"participants": [456, 789], // IDs des participants
"initial_message": "Message initial optionnel"
}
```
**Règles** :
- `private` : Maximum 2 participants (incluant le créateur)
- `group` : Plusieurs participants possibles
- `broadcast` : Réservé aux admins (rôle >= 2)
**Réponse** :
```json
{
"status": "success",
"room": {
"id": "uuid-new-room",
"title": "Nouvelle conversation",
"type": "group",
"created_at": "2025-01-17 15:00:00",
"participants": [...]
},
"existing": false // true si conversation privée existante trouvée
}
```
### 3. GET /api/chat/rooms/{id}/messages
**Description** : Récupère les messages d'une conversation
**Paramètres** :
- `limit` : Nombre de messages (défaut: 50, max: 100)
- `before` : ID du message pour pagination
**Réponse** :
```json
{
"status": "success",
"messages": [
{
"id": "uuid-message-1",
"content": "Bonjour !",
"sender_id": 123,
"sender_name": "Jean Dupont",
"sender_first_name": "Jean",
"sent_at": "2025-01-17 14:00:00",
"edited_at": null,
"is_deleted": false,
"is_read": true,
"is_mine": false,
"read_count": 3
}
],
"has_more": true
}
```
### 4. POST /api/chat/rooms/{id}/messages
**Description** : Envoie un message dans une conversation
**Body** :
```json
{
"content": "Contenu du message (max 5000 caractères)"
}
```
**Réponse** :
```json
{
"status": "success",
"message": {
"id": "uuid-new-message",
"content": "Message envoyé",
"sender_id": 123,
"sender_name": "Jean Dupont",
"sent_at": "2025-01-17 15:30:00",
"is_mine": true,
"is_read": false,
"read_count": 0
}
}
```
### 5. POST /api/chat/rooms/{id}/read
**Description** : Marque les messages comme lus
**Body (optionnel)** :
```json
{
"message_ids": ["uuid-1", "uuid-2"] // Si omis, marque tous les messages
}
```
**Réponse** :
```json
{
"status": "success",
"unread_count": 0 // Nombre de messages non lus restants
}
```
### 6. GET /api/chat/recipients
**Description** : Liste des destinataires possibles pour créer une conversation
**Réponse** :
```json
{
"status": "success",
"recipients": [
{
"id": 456,
"name": "Marie Martin",
"first_name": "Marie",
"role": 1,
"entite_id": 5
}
],
"recipients_by_entity": {
"Amicale de Grenoble": [
{...}
],
"Amicale de Lyon": [
{...}
]
}
}
```
## Fonctionnalités clés
### 1. Types de conversations
#### Private (Conversation privée)
- Entre 2 utilisateurs uniquement
- Détection automatique de conversation existante
- Pas de titre requis
#### Group (Groupe)
- Plusieurs participants
- Titre optionnel mais recommandé
- Admin de groupe (créateur)
#### Broadcast (Diffusion)
- Réservé aux admins (rôle >= 2)
- Communication unidirectionnelle possible
- Pour annonces importantes
### 2. Gestion des permissions
Le système vérifie automatiquement :
- L'appartenance à une conversation avant lecture/écriture
- Les droits de création selon le type de conversation
- La visibilité des destinataires selon le rôle
### 3. Statuts de lecture
- **Accusés de lecture individuels** : Chaque message peut être marqué comme lu
- **Compteur de non-lus** : Par conversation et global
- **Last read** : Timestamp de dernière lecture par participant
### 4. Optimisations
- **Pagination** : Chargement progressif des messages
- **Index optimisés** : Pour les requêtes fréquentes
- **Vue SQL** : Pour récupération rapide du dernier message
## Sécurité
### Chiffrement
- Les noms d'utilisateurs sont stockés chiffrés (AES-256)
- Déchiffrement à la volée lors de la lecture
### Validation
- Longueur maximale des messages : 5000 caractères
- Trim automatique du contenu
- Vérification des permissions à chaque action
### Isolation
- Les utilisateurs ne voient que leurs conversations autorisées
- Filtrage par entité selon le rôle
- Soft delete pour conservation de l'historique
## Migration
Exécuter le script SQL :
```bash
mysql -u root -p geo_app < scripts/sql/create_chat_tables.sql
```
## Évolutions futures possibles
1. **Notifications push** : Intégration avec Firebase/WebSocket
2. **Fichiers joints** : Support d'images et documents
3. **Réactions** : Emojis sur les messages
4. **Mentions** : @username pour notifier
5. **Recherche** : Dans l'historique des messages
6. **Chiffrement E2E** : Pour conversations sensibles
7. **Statuts de présence** : En ligne/Hors ligne
8. **Indicateur de frappe** : "X est en train d'écrire..."
## Tests
### Cas de test recommandés
1. **Création de conversation privée**
- Vérifier la détection de conversation existante
- Tester avec utilisateurs de différentes entités
2. **Envoi de messages**
- Messages avec caractères UTF-8 (émojis, accents)
- Messages très longs (limite 5000)
- Messages vides (doivent être rejetés)
3. **Marquage comme lu**
- Marquer messages spécifiques
- Marquer tous les messages d'une room
- Vérifier les compteurs
4. **Permissions**
- Utilisateur simple ne peut pas créer de broadcast
- Accès refusé aux conversations non autorisées
- Filtrage correct des destinataires
## Support
Pour toute question ou problème :
- Vérifier les logs dans `/logs/`
- Consulter les tables `chat_*` en base de données
- Tester avec les scripts de test fournis

View File

@@ -0,0 +1,176 @@
# Correction des erreurs 400 lors de la création d'utilisateurs
## Problème identifié
Un administrateur (fk_role=2) rencontrait des erreurs 400 répétées lors de tentatives de création de membre, menant à un bannissement par fail2ban :
- 17:09:39 - POST /api/users HTTP/1.1 400 (Bad Request)
- 17:10:44 - POST /api/users/check-username HTTP/1.1 400 (Bad Request)
- 17:11:21 - POST /api/users HTTP/1.1 400 (Bad Request)
## Causes identifiées
### 1. Conflit de routage (CRITIQUE)
**Problème:** La route `/api/users/check-username` était déclarée APRÈS la route générique `/api/users` dans Router.php, causant une mauvaise interprétation où "check-username" était traité comme un ID utilisateur.
**Solution:** Déplacer la déclaration de la route spécifique AVANT les routes avec paramètres.
### 2. Messages d'erreur non informatifs
**Problème:** Les erreurs 400 retournaient des messages génériques sans détails sur le champ problématique.
**Solution:** Ajout de messages d'erreur détaillés incluant :
- Le champ en erreur (`field`)
- La valeur problématique (`value`)
- Le format attendu (`format`)
- La raison de l'erreur (`reason`)
### 3. Manque de logs de débogage
**Problème:** Aucun log n'était généré pour tracer les erreurs de validation.
**Solution:** Ajout de logs détaillés à chaque point de validation.
## Modifications apportées
### 1. Router.php (ligne 36-44)
```php
// AVANT (incorrect)
$this->post('users', ['UserController', 'createUser']);
$this->post('users/check-username', ['UserController', 'checkUsername']);
// APRÈS (correct)
$this->post('users/check-username', ['UserController', 'checkUsername']); // Route spécifique en premier
$this->post('users', ['UserController', 'createUser']);
```
### 2. UserController.php - Amélioration des validations
#### Validation de l'email
```php
// Réponse améliorée
Response::json([
'status' => 'error',
'message' => 'Email requis',
'field' => 'email' // Indique clairement le champ problématique
], 400);
```
#### Validation du username manuel
```php
// Réponse améliorée
Response::json([
'status' => 'error',
'message' => 'Le nom d\'utilisateur est requis pour cette entité',
'field' => 'username',
'reason' => 'L\'entité requiert la saisie manuelle des identifiants'
], 400);
```
#### Format du username
```php
// Réponse améliorée
Response::json([
'status' => 'error',
'message' => 'Format du nom d\'utilisateur invalide',
'field' => 'username',
'format' => '10-30 caractères, commence par une lettre, caractères autorisés: a-z, 0-9, ., -, _',
'value' => $username // Montre la valeur soumise
], 400);
```
### 3. Ajout de logs détaillés
Chaque point de validation génère maintenant un log avec :
- Le type d'erreur
- L'utilisateur qui fait la requête
- Les données reçues (sans données sensibles)
- Le contexte de l'erreur
Exemple :
```php
LogService::log('Erreur création utilisateur : Format username invalide', [
'level' => 'warning',
'createdBy' => $currentUserId,
'email' => $email,
'username' => $username,
'username_length' => strlen($username)
]);
```
## Cas d'erreur 400 possibles
### Pour /api/users (création)
1. **Email manquant ou vide**
- Message: "Email requis"
- Field: "email"
2. **Nom manquant ou vide**
- Message: "Nom requis"
- Field: "name"
3. **Format email invalide**
- Message: "Format d'email invalide"
- Field: "email"
- Value: [email soumis]
4. **Username manuel requis mais manquant** (si chk_username_manuel=1)
- Message: "Le nom d'utilisateur est requis pour cette entité"
- Field: "username"
- Reason: "L'entité requiert la saisie manuelle des identifiants"
5. **Format username invalide**
- Message: "Format du nom d'utilisateur invalide"
- Field: "username"
- Format: "10-30 caractères, commence par une lettre..."
- Value: [username soumis]
6. **Mot de passe manuel requis mais manquant** (si chk_mdp_manuel=1)
- Message: "Le mot de passe est requis pour cette entité"
- Field: "password"
- Reason: "L'entité requiert la saisie manuelle des mots de passe"
### Pour /api/users/check-username
1. **Username manquant**
- Message: "Username requis pour la vérification"
- Field: "username"
2. **Format username invalide**
- Message: "Format invalide"
- Field: "username"
- Format: "10-30 caractères, commence par une lettre..."
- Value: [username soumis]
## Test de la solution
Un script de test a été créé : `/tests/test_user_creation.php`
Il teste tous les cas d'erreur possibles et vérifie que :
1. Les codes HTTP sont corrects
2. Les messages d'erreur sont informatifs
3. Les champs en erreur sont identifiés
## Recommandations pour éviter le bannissement fail2ban
1. **Côté client (application Flutter)** :
- Valider les données AVANT l'envoi
- Afficher clairement les erreurs à l'utilisateur
- Implémenter un délai entre les tentatives (rate limiting côté client)
2. **Côté API** :
- Les messages d'erreur détaillés permettent maintenant de corriger rapidement les problèmes
- Les logs permettent de diagnostiquer les problèmes récurrents
3. **Configuration fail2ban** :
- Considérer d'augmenter le seuil pour les erreurs 400 (ex: 5 tentatives au lieu de 3)
- Exclure certaines IP de confiance si nécessaire
## Suivi des logs
Les logs sont maintenant générés dans :
- `/logs/geosector-[environment]-[date].log` : Logs généraux avec détails des erreurs
Format des logs :
```
timestamp;browser;os;client_type;level;metadata;message
```
Les erreurs de validation sont loggées avec le niveau "warning" pour permettre un suivi sans être critiques.

View File

@@ -779,7 +779,7 @@ fetch('/api/endpoint', {
## Changements récents
### Version 3.0.7 (Janvier 2025)
### Version 3.0.7 (Août 2025)
#### 1. Implémentation complète de la norme NIST SP 800-63B pour les mots de passe
- **Nouveau service :** `PasswordSecurityService` pour la gestion sécurisée des mots de passe
@@ -797,7 +797,7 @@ fetch('/api/endpoint', {
- **Choix client :** Permet d'avoir un mot de passe identique au nom d'utilisateur
- **Pas de vérification contextuelle :** Aucune vérification nom/email dans le mot de passe
### Version 3.0.6 (Janvier 2025)
### Version 3.0.6 (Août 2025)
#### 1. Correction des rôles administrateurs
- **Avant :** Les administrateurs d'amicale devaient avoir `fk_role > 2`
@@ -836,3 +836,28 @@ fetch('/api/endpoint', {
- **Format d'envoi des images :** Base64 data URL pour compatibilité multiplateforme
- **Structure de réponse enrichie :** Le logo est inclus dans l'objet `amicale` lors du login
- **Optimisation :** Pas de requête HTTP supplémentaire nécessaire pour afficher le logo
### Version 3.0.8 (Janvier 2025)
#### 1. Système de génération automatique de reçus fiscaux pour les dons
- **Nouveau service :** `ReceiptService` pour la génération automatique de reçus PDF
- **Déclencheurs automatiques :**
- Création d'un passage avec `fk_type=1` (don) et email valide
- Mise à jour d'un passage en don si `nom_recu` est vide/null
- **Caractéristiques techniques :**
- PDF ultra-légers (< 5KB) générés en format natif sans librairie externe
- Support des caractères accentués avec conversion automatique
- Stockage structuré : `/uploads/entites/{entite_id}/recus/{operation_id}/`
- Enregistrement dans la table `medias` avec catégorie `recu`
- **Queue d'envoi email :**
- Envoi automatique par email avec pièce jointe PDF
- Format MIME multipart pour compatibilité maximale
- Gestion dans la table `email_queue` avec statut de suivi
- **Nouvelle route API :**
- `GET /api/passages/{id}/receipt` : Récupération du PDF d'un reçu
- Retourne le PDF en base64 ou téléchargement direct selon Accept header
- **Champs base de données utilisés :**
- `nom_recu` : Nom du fichier PDF généré
- `date_creat_recu` : Date de génération du reçu
- `date_sent_recu` : Date d'envoi par email
- `chk_email_sent` : Indicateur d'envoi réussi

View File

@@ -0,0 +1,135 @@
# Changements de validation des usernames - Version ultra-souple
## Date : 17 janvier 2025
## Contexte
Suite aux problèmes d'erreurs 400 et au besoin d'avoir une approche plus moderne et inclusive, les règles de validation des usernames ont été assouplies pour accepter tous les caractères UTF-8, similaire à l'approche NIST pour les mots de passe.
## Anciennes règles (trop restrictives)
- ❌ 10-30 caractères
- ❌ Doit commencer par une lettre minuscule
- ❌ Seulement : a-z, 0-9, ., -, _
- ❌ Pas d'espaces
- ❌ Pas de majuscules
- ❌ Pas d'accents ou caractères spéciaux
## Nouvelles règles (ultra-souples)
-**8-30 caractères UTF-8**
-**Tous caractères acceptés** :
- Lettres (majuscules/minuscules)
- Chiffres
- Espaces
- Caractères spéciaux (!@#$%^&*()_+-=[]{}|;:'"<>,.?/)
- Accents (é, è, à, ñ, ü, etc.)
- Émojis (😀, 🎉, ❤️, etc.)
- Caractères non-latins (中文, العربية, Русский, etc.)
-**Sensible à la casse** (Jean ≠ jean)
-**Trim automatique** des espaces début/fin
-**Unicité vérifiée** dans toute la base
## Exemples de usernames valides
### Noms classiques
- `Jean-Pierre`
- `Marie Claire` (avec espace)
- `O'Connor`
- `José García`
### Avec chiffres et caractères spéciaux
- `admin2024`
- `user@company`
- `test_user#1`
- `Marie*123!`
### International
- `李明` (chinois)
- `محمد` (arabe)
- `Владимир` (russe)
- `さくら` (japonais)
- `Παύλος` (grec)
### Modernes/Fun
- `🦄Unicorn`
- `Player_1 🎮`
- `☕Coffee.Lover`
- `2024_User`
## Exemples de usernames invalides
- `short` ❌ (moins de 8 caractères)
- ` ` ❌ (espaces seulement)
- `very_long_username_that_exceeds_thirty_chars` ❌ (plus de 30 caractères)
## Modifications techniques
### 1. Code PHP (UserController.php)
```php
// Avant (restrictif)
if (!preg_match('/^[a-z][a-z0-9._-]{9,29}$/', $username))
// Après (ultra-souple)
$username = trim($data['username']);
$usernameLength = mb_strlen($username, 'UTF-8');
if ($usernameLength < 8) {
// Erreur : trop court
}
if ($usernameLength > 30) {
// Erreur : trop long
}
// C'est tout ! Pas d'autre validation
```
### 2. Base de données
```sql
-- Script à exécuter : scripts/sql/migration_username_utf8_support.sql
ALTER TABLE `users`
MODIFY COLUMN `encrypted_user_name` varchar(255) DEFAULT '';
```
### 3. Messages d'erreur simplifiés
- Avant : "Format du nom d'utilisateur invalide (10-30 caractères, commence par une lettre, caractères autorisés: a-z, 0-9, ., -, _)"
- Après :
- "Identifiant trop court" + "Minimum 8 caractères"
- "Identifiant trop long" + "Maximum 30 caractères"
- "Identifiant déjà utilisé"
## Impact sur l'expérience utilisateur
### Avantages
1. **Inclusivité** : Support de toutes les langues et cultures
2. **Modernité** : Permet les émojis et caractères spéciaux
3. **Simplicité** : Règles faciles à comprendre (juste la longueur)
4. **Flexibilité** : Les utilisateurs peuvent choisir l'identifiant qu'ils veulent
5. **Moins d'erreurs** : Moins de rejets pour format invalide
### Points d'attention
1. **Support client** : Former le support aux nouveaux formats possibles
2. **Affichage** : S'assurer que l'UI supporte bien l'UTF-8
3. **Recherche** : La recherche d'utilisateurs doit gérer la casse et l'UTF-8
4. **Export** : Vérifier que les exports CSV/Excel gèrent bien l'UTF-8
## Sécurité
### Pas d'impact sur la sécurité
- ✅ Les usernames sont toujours chiffrés en base (AES-256-CBC)
- ✅ L'unicité est toujours vérifiée
- ✅ Les injections SQL sont impossibles (prepared statements)
- ✅ Le trim empêche les espaces invisibles
### Recommandations
- Continuer à générer automatiquement des usernames simples (ASCII) pour éviter les problèmes
- Mais permettre la saisie manuelle de tout format
- Logger les usernames "exotiques" pour détecter d'éventuels abus
## Tests
- Script de test disponible : `/tests/test_username_validation.php`
- Teste tous les cas limites et formats internationaux
## Rollback si nécessaire
Si besoin de revenir en arrière :
1. Restaurer l'ancienne validation dans UserController
2. Les usernames UTF-8 existants continueront de fonctionner
3. Seuls les nouveaux seront restreints
## Conclusion
Cette approche ultra-souple aligne les usernames sur les standards modernes d'inclusivité et d'accessibilité, tout en maintenant la sécurité grâce au chiffrement et à la validation de l'unicité.

BIN
api/docs/_logo_recu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
api/docs/_recu_template.pdf Normal file

Binary file not shown.

View File

@@ -18,168 +18,39 @@
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE TABLE `chat_anonymous_users` (
`id` varchar(50) NOT NULL,
`device_id` varchar(100) NOT NULL,
`name` varchar(100) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`converted_to_user_id` int(10) unsigned DEFAULT NULL,
`metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),
PRIMARY KEY (`id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_converted_user` (`converted_to_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
-- Tables préfixées "chat_"
CREATE TABLE chat_rooms (
id VARCHAR(36) PRIMARY KEY,
title VARCHAR(255),
type ENUM('private', 'group', 'broadcast'),
created_at TIMESTAMP,
created_by INT
);
CREATE TABLE `chat_attachments` (
`id` varchar(50) NOT NULL,
`fk_message` varchar(50) NOT NULL,
`file_name` varchar(255) NOT NULL,
`file_path` varchar(500) NOT NULL,
`file_type` varchar(100) NOT NULL,
`file_size` int(10) unsigned NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_message` (`fk_message`),
CONSTRAINT `fk_chat_attachments_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE chat_messages (
id VARCHAR(36) PRIMARY KEY,
room_id VARCHAR(36),
content TEXT,
sender_id INT,
sent_at TIMESTAMP,
FOREIGN KEY (room_id) REFERENCES chat_rooms(id)
);
CREATE TABLE `chat_audience_targets` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_room` varchar(50) NOT NULL,
`target_type` enum('role','entity','all','combined') NOT NULL DEFAULT 'all',
`target_id` varchar(50) DEFAULT NULL,
`role_filter` varchar(20) DEFAULT NULL,
`entity_filter` varchar(50) DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_type` (`target_type`),
CONSTRAINT `fk_chat_audience_targets_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE chat_participants (
room_id VARCHAR(36),
user_id INT,
role INT,
entite_id INT,
joined_at TIMESTAMP,
PRIMARY KEY (room_id, user_id)
);
CREATE TABLE `chat_broadcast_lists` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_room` varchar(50) NOT NULL,
`name` varchar(100) NOT NULL,
`description` text DEFAULT NULL,
`fk_user_creator` int(10) unsigned NOT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_user_creator` (`fk_user_creator`),
CONSTRAINT `fk_chat_broadcast_lists_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_messages` (
`id` varchar(50) NOT NULL,
`fk_room` varchar(50) NOT NULL,
`fk_user` int(10) unsigned DEFAULT NULL,
`sender_type` enum('user','anonymous','system') NOT NULL DEFAULT 'user',
`content` text DEFAULT NULL,
`content_type` enum('text','image','file') NOT NULL DEFAULT 'text',
`date_sent` timestamp NOT NULL DEFAULT current_timestamp(),
`date_delivered` timestamp NULL DEFAULT NULL,
`date_read` timestamp NULL DEFAULT NULL,
`statut` enum('envoye','livre','lu','error') NOT NULL DEFAULT 'envoye',
`is_announcement` tinyint(1) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_user` (`fk_user`),
KEY `idx_date` (`date_sent`),
KEY `idx_status` (`statut`),
KEY `idx_messages_unread` (`fk_room`,`statut`),
CONSTRAINT `fk_chat_messages_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_notifications` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`fk_user` int(10) unsigned NOT NULL,
`fk_message` varchar(50) DEFAULT NULL,
`fk_room` varchar(50) DEFAULT NULL,
`type` varchar(50) NOT NULL,
`contenu` text DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
`date_lecture` timestamp NULL DEFAULT NULL,
`statut` enum('non_lue','lue') NOT NULL DEFAULT 'non_lue',
PRIMARY KEY (`id`),
KEY `idx_user` (`fk_user`),
KEY `idx_message` (`fk_message`),
KEY `idx_room` (`fk_room`),
KEY `idx_statut` (`statut`),
KEY `idx_notifications_unread` (`fk_user`,`statut`),
CONSTRAINT `fk_chat_notifications_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_chat_notifications_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_offline_queue` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`operation_type` varchar(50) NOT NULL,
`operation_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`operation_data`)),
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`processed_at` timestamp NULL DEFAULT NULL,
`status` enum('pending','processing','completed','failed') NOT NULL DEFAULT 'pending',
`error_message` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_participants` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_room` varchar(50) NOT NULL,
`id_user` int(10) unsigned DEFAULT NULL,
`anonymous_id` varchar(50) DEFAULT NULL,
`role` enum('administrateur','participant','en_lecture_seule') NOT NULL DEFAULT 'participant',
`date_ajout` timestamp NOT NULL DEFAULT current_timestamp(),
`notification_activee` tinyint(1) unsigned NOT NULL DEFAULT 1,
`last_read_message_id` varchar(50) DEFAULT NULL,
`via_target` tinyint(1) unsigned NOT NULL DEFAULT 0,
`can_reply` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uc_room_user` (`id_room`,`id_user`),
KEY `idx_room` (`id_room`),
KEY `idx_user` (`id_user`),
KEY `idx_anonymous_id` (`anonymous_id`),
KEY `idx_participants_active` (`id_room`,`id_user`,`notification_activee`),
CONSTRAINT `fk_chat_participants_room` FOREIGN KEY (`id_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_read_messages` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`fk_message` varchar(50) NOT NULL,
`fk_user` int(10) unsigned NOT NULL,
`date_read` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `uc_message_user` (`fk_message`,`fk_user`),
KEY `idx_message` (`fk_message`),
KEY `idx_user` (`fk_user`),
CONSTRAINT `fk_chat_read_messages_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_rooms` (
`id` varchar(50) NOT NULL,
`type` enum('privee','groupe','liste_diffusion','broadcast','announcement') NOT NULL,
`title` varchar(100) DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
`fk_user` int(10) unsigned NOT NULL,
`fk_entite` int(10) unsigned DEFAULT NULL,
`statut` enum('active','archive') NOT NULL DEFAULT 'active',
`description` text DEFAULT NULL,
`reply_permission` enum('all','admins_only','sender_only','none') NOT NULL DEFAULT 'all',
`is_pinned` tinyint(1) unsigned NOT NULL DEFAULT 0,
`expiry_date` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_user` (`fk_user`),
KEY `idx_entite` (`fk_entite`),
KEY `idx_type` (`type`),
KEY `idx_statut` (`statut`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE chat_read_receipts (
message_id VARCHAR(36),
user_id INT,
read_at TIMESTAMP,
PRIMARY KEY (message_id, user_id)
);
CREATE TABLE `email_counter` (
`id` int(10) unsigned NOT NULL DEFAULT 1,

BIN
api/docs/recu_537254062.pdf Normal file

Binary file not shown.

BIN
api/docs/recu_972506460.pdf Normal file

Binary file not shown.