# Audit intégral – Frontend (Flutter) & Backend (Quarkus) **Date** : 4 février 2026 **Périmètre** : `afterwork` (Flutter), `mic-after-work-server-impl-quarkus-main` (Quarkus) **Références** : bonnes pratiques Quarkus REST, Flutter clean architecture, REST/JWT, WebSocket, Kafka (recherches web et documentation officielle). --- ## 1. Résumé exécutif | Domaine | État global | Points critiques | |--------|-------------|------------------| | **Sécurité API (auth/authz)** | Critique | Aucune vérification JWT ; userId pris de l’URL/body sans preuve d’identité | | **Couches backend** | Partiel | Resource accède parfois au repository ; validation incohérente (manuel vs Bean Validation) | | **Gestion d’erreurs backend** | Partiel | Réponse d’erreur JSON construite à la main (risque d’injection) ; exceptions métier non gérées | | **Frontend – auth** | Critique | Aucun en-tête `Authorization` sur les requêtes API | | **Frontend – architecture** | Correct | data/domain/presentation présents ; pas de couche use-case systématique | | **WebSocket** | Correct | Heartbeat + reconnexion présents ; pas de backoff exponentiel côté Flutter | --- ## 2. Backend (Quarkus) – Analyse détaillée ### 2.1 Architecture des couches **Recommandation (bonnes pratiques Quarkus)** : Resource (REST, DTO) → Service (métier) → Repository (persistance). La resource ne doit pas appeler le repository pour de la logique métier. **Constat :** - **MessageResource** (lignes 98–104, 168–174) : appelle directement `usersRepository.findById(userId)` pour vérifier l’existence de l’utilisateur, au lieu de déléguer au service (ex. `messageService.getUserConversations(userId)` qui lèverait `UserNotFoundException`). - **MessageResource** injecte `UsersRepository` en plus de `MessageService` → mélange des responsabilités et duplication de la règle “utilisateur doit exister”. **Recommandation :** Déplacer la résolution/utilisateur dans `MessageService` et faire lever `UserNotFoundException` ; supprimer l’injection de `UsersRepository` dans `MessageResource`. --- ### 2.2 Validation des entrées **Recommandation :** Utiliser Bean Validation (Hibernate Validator) sur les DTO avec `@Valid` sur les paramètres des endpoints. Éviter la validation manuelle dans la resource. **Constat :** - **SendMessageRequestDTO** : pas d’annotations `@NotNull`, `@NotBlank` ; validation manuelle via `isValid()`. - **MessageResource.sendMessage** : pas de `@Valid` ; utilise `request.isValid()` et retourne 400 manuellement. - Autres ressources (UsersResource, EstablishmentResource, FriendshipResource, etc.) : utilisent correctement `@Valid` et DTO avec contraintes. **Recommandation :** Ajouter sur `SendMessageRequestDTO` les annotations (`@NotNull` pour senderId, recipientId, `@NotBlank` pour content, etc.) et appeler l’endpoint avec `@Valid SendMessageRequestDTO request`. Supprimer `isValid()` et le bloc manuel 400. --- ### 2.3 Authentification et autorisation **Recommandation (OWASP / JWT)** : Chaque endpoint protégé doit valider un JWT (ou session) et dériver l’identité du token. Les paramètres comme `userId` dans l’URL ne doivent pas être la seule source de vérité : vérifier que le sujet du token correspond à la ressource demandée. **Constat :** - Aucun usage de `@RolesAllowed`, `@PermitAll`, ni de filtre/filtre JWT dans le projet. - Les endpoints utilisent `userId` en `@PathParam` (ex. `/notifications/user/{userId}`, `/messages/conversations/{userId}`) ou dans le body (ex. `SendMessageRequestDTO.senderId`) sans aucune preuve que l’appelant est cet utilisateur. - Le commentaire dans `NotificationResource` indique : *“En production, le userId doit être dérivé du contexte d'authentification (JWT/session), pas de l'URL.”* → non implémenté. **Impact :** Un attaquant peut lire/modifier les données d’un autre utilisateur en devinant ou en énumérant des UUID. **Recommandation :** Introduire l’authentification JWT (ex. `quarkus-oidc` ou filtre custom), extraire le `userId` (ou subject) du token, et pour chaque endpoint : soit utiliser ce `userId` comme source de vérité, soit vérifier que le `userId` en path/body est égal au sujet du token (pour les rôles appropriés). --- ### 2.4 Gestion globale des exceptions **Recommandation :** Un seul point de sortie pour les erreurs (ExceptionMapper), réponses en JSON structuré (ex. `{"error": "..."}`) avec échappement correct. Gérer toutes les exceptions métier connues. **Constat :** - **GlobalExceptionHandler** : gère `BadRequestException`, `UserNotFoundException`, `EventNotFoundException`, `NotFoundException`, `UnauthorizedException`, `ServerException`, `RuntimeException`, et cas par défaut. - **FriendshipNotFoundException** et **EstablishmentHasDependenciesException** ne sont pas gérées explicitement → elles tombent dans `RuntimeException` ou “Unexpected error”, avec un message potentiellement générique ou une stack trace. - **buildResponse** (ligne 62–65) : `entity("{\"error\":\"" + message + "\"}")` Concaténation directe de `message` dans le JSON. Si `message` contient `"` ou `\`, le JSON est mal formé et peut poser des risques (injection / parsing côté client). Il faut sérialiser le message en JSON (ex. via Jackson/JSON-B) au lieu de concaténer une chaîne. **Recommandation :** 1) Ajouter des branches pour `FriendshipNotFoundException` (ex. 404) et `EstablishmentHasDependenciesException` (ex. 409 Conflict). 2) Remplacer la concaténation par un DTO d’erreur sérialisé (ex. `Map.of("error", message)` ou classe dédiée) avec le moteur JSON du framework. --- ### 2.5 Ressources qui gèrent les erreurs en local **Recommandation :** La resource ne doit pas faire de try/catch générique qui transforme tout en 500. Elle doit déléguer au service ; les exceptions métier doivent être mappées par le GlobalExceptionHandler. **Constat :** - **MessageResource** : plusieurs méthodes avec `try { ... } catch (Exception e) { return 500 ... }`. Les exceptions métier (ex. utilisateur inexistant, conversation inexistante) ne sont pas levées sous forme d’exceptions typées ; elles sont noyées dans un message générique 500. **Recommandation :** Faire lever par le service des exceptions métier (ex. `UserNotFoundException`, `NotFoundException`) et supprimer les try/catch larges dans la resource ; laisser le GlobalExceptionHandler produire 404/400/500 de façon cohérente. --- ### 2.6 Kafka (déjà traité) - Tuning prod (`max.poll.interval.ms`, `max.poll.records`, `session.timeout.ms`) déjà ajouté dans `application-prod.properties`. - Bonnes pratiques SmallRye : en cas d’échec critique après consommation, envisager `message.nack()` et stratégie de commit manuel si nécessaire (au-delà du scope de cet audit). --- ## 3. Frontend (Flutter) – Analyse détaillée ### 3.1 Structure (clean architecture) **Recommandation :** Séparation nette data / domain / presentation ; repositories en abstraction dans domain ; use cases optionnels mais utiles pour une logique métier réutilisable. **Constat :** - Présence de `data/` (datasources, models, repositories impl, services), `domain/` (entities, repositories abstraits, usecases partiels), `presentation/` (screens, state_management avec BLoC). - Les datasources sont bien séparés ; les repositories implémentent les contrats du domain. Use cases présents seulement pour une partie des flux (ex. `get_user`). **Verdict :** Conforme à une clean architecture légère. On peut étendre progressivement les use cases pour les flux critiques. --- ### 3.2 Appels API et authentification **Recommandation :** Toute requête vers une API protégée doit envoyer le token (ex. `Authorization: Bearer `). Le token doit être lu depuis un stockage sécurisé et rafraîchi si nécessaire. **Constat :** - Aucun datasource (user, notification, chat, event, social, reservation, establishment, etc.) n’ajoute d’en-tête `Authorization` ou `Bearer`. - Les headers utilisés sont principalement `Content-Type` et `Accept`. Aucune utilisation de `SecureStorage` (ou équivalent) pour récupérer un token et l’attacher aux requêtes. - L’API backend n’exige aujourd’hui pas de JWT ; en revanche, dès que l’auth sera activée côté backend, tous les appels devront envoyer le token. **Recommandation :** 1) Créer un client HTTP unique (wrapper ou interceptor) qui récupère le token (ex. depuis `SecureStorage`) et ajoute `Authorization: Bearer ` à chaque requête. 2) Utiliser ce client dans tous les datasources au lieu d’utiliser `http.Client` brut sans headers d’auth. 3) Gérer le cas “token absent ou expiré” (401) : redirection vers login ou refresh. --- ### 3.3 WebSocket (notifications et chat) **Recommandation (bonnes pratiques WebSocket)** : Heartbeat régulier, reconnexion avec backoff exponentiel, file d’attente des messages en cas de déconnexion si besoin. **Constat :** - **RealtimeNotificationService** et **ChatWebSocketService** : - Connexion avec `WebSocketChannel.connect`. - Heartbeat toutes les 30 s (`_heartbeatInterval`). - Reconnexion avec délai fixe (`_initialReconnectDelay = 5 s`) et plafond de tentatives (`_maxReconnectAttempts = 5`). - Pas de backoff exponentiel (délai constant entre les tentatives). Pour réduire la charge serveur en cas de panne, un backoff exponentiel est préférable. **Recommandation :** Conserver le heartbeat et la reconnexion ; ajouter un backoff exponentiel (ex. 2s, 4s, 8s, 16s, 30s) pour les tentatives de reconnexion, avec un plafond (ex. 30 s). --- ### 3.4 Gestion des erreurs et parsing - Les datasources gèrent les timeouts, `SocketException`, et codes HTTP (401, 404, etc.) et lèvent des exceptions métier (ex. `ServerException`, `UnauthorizedException`). C’est cohérent. - Vérifier que partout où l’on parse le body d’erreur, on utilise une clé unique (ex. `error` ou `message`) alignée avec le backend. Après correction du backend (réponse d’erreur en JSON structuré), adapter si nécessaire le parsing côté Flutter pour lire `error` ou `message`. --- ## 4. Tableau de synthèse des écarts | # | Composant | Écart | Sévérité | Action recommandée | |---|-----------|--------|----------|---------------------| | 1 | Backend | Aucune auth JWT ; userId pris de l’URL/body sans preuve | Critique | Introduire JWT et dériver userId du token | | 2 | Frontend | Aucun en-tête Authorization sur les requêtes API | Critique | Client HTTP centralisé avec Bearer token | | 3 | Backend | MessageResource : accès direct au repository + validation manuelle | Moyen | Déléguer au service ; Bean Validation sur SendMessageRequestDTO | | 4 | Backend | buildResponse : concaténation JSON pour le message d’erreur | Moyen | Utiliser un DTO/Map sérialisé en JSON | | 5 | Backend | FriendshipNotFoundException, EstablishmentHasDependenciesException non gérées dans GlobalExceptionHandler | Moyen | Ajouter les branches et codes HTTP appropriés | | 6 | Backend | MessageResource : try/catch générique qui masque les exceptions métier | Moyen | Lever des exceptions typées et laisser le handler global gérer | | 7 | Frontend | Reconnexion WebSocket avec délai fixe | Faible | Implémenter backoff exponentiel | --- ## 5. Bonnes pratiques croisées (références) - **Quarkus REST** : Resource → Service → Repository ; DTO + `@Valid` ; ExceptionMapper unique ; pas de logique métier dans la resource. - **Sécurité REST/JWT** : Vérifier le token sur chaque requête ; ne pas faire confiance au userId passé par le client pour l’autorisation. - **Flutter** : Clean architecture avec repositories abstraits ; couche data qui envoie toujours l’auth (client commun avec token). - **WebSocket** : Heartbeat + reconnexion avec backoff exponentiel pour limiter la charge et les reconnexions agressives. --- ## 6. Conclusion Les points les plus critiques concernent **l’authentification et l’autorisation** : côté backend, aucun contrôle sur l’identité de l’appelant ; côté frontend, aucun token n’est envoyé. La cohérence des couches (resource sans accès direct au repository pour la logique métier), la validation (Bean Validation partout, y compris chat), et la gestion d’erreurs (réponse JSON sûre, exceptions métier gérées centralement) sont à renforcer pour aligner le projet sur les bonnes pratiques et sécuriser la production. --- ## 7. Corrections appliquées (suite à l'audit) - **GlobalExceptionHandler** : Réponse d'erreur en JSON via ObjectMapper ; prise en charge de `FriendshipNotFoundException` (404) et `EstablishmentHasDependenciesException` (409). - **SendMessageRequestDTO** : Bean Validation ; suppression de `isValid()`. - **MessageResource** : `@Valid`, `UsersService` au lieu de `UsersRepository`, suppression des try/catch locaux. - **MessageService** : `NotFoundException` si conversation ou message absent. - **JWT** : `JwtService`, token au login (HS256), `UserAuthenticateResponseDTO.token`, config `afterwork.jwt.secret`. - **Frontend** : `SecureStorage.saveAuthToken`/`getAuthToken`, `ApiClient` (Authorization Bearer), tous datasources + FriendsRepositoryImpl ; sauvegarde du token à l'authentification. - **WebSocket** : Backoff exponentiel (2^attempt s, max 30 s) dans ChatWebSocketService et RealtimeNotificationService.