refactor(docs): réorganiser la documentation selon principes DDD

Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend.

**Structure cible:**
```
docs/domains/
├── README.md (Context Map)
├── _shared/ (Core Domain)
├── recommendation/ (Supporting Subdomain)
├── content/ (Supporting Subdomain)
├── moderation/ (Supporting Subdomain)
├── advertising/ (Generic Subdomain)
├── premium/ (Generic Subdomain)
└── monetization/ (Generic Subdomain)
```

**Changements effectués:**

Phase 1: Création de l'arborescence des 7 bounded contexts
Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/
Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/
Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/
Phase 5: Création des README.md pour chaque domaine
Phase 6: Déplacement des features Gherkin vers domains/*/features/
Phase 7: Création du Context Map (domains/README.md)
Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation
Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh)
Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/)

**Configuration des tests:**
- Makefile: godog run docs/domains/*/features/
- scripts/generate-bdd-docs.py: features_dir → docs/domains

**Avantages:**
 Cohésion forte: toute la doc d'un domaine au même endroit
 Couplage faible: domaines indépendants, dépendances explicites
 Navigabilité améliorée: README par domaine = entrée claire
 Alignement code/docs: miroir de backend/internal/
 Onboarding facilité: exploration domaine par domaine
 Tests BDD intégrés: features au plus près des règles métier

Voir docs/REFACTOR-DDD.md pour le plan complet.
This commit is contained in:
jpgiannetti
2026-02-07 17:15:02 +01:00
parent 78422bb2c0
commit 5e5fcf4714
227 changed files with 1413 additions and 1967 deletions

View File

@@ -0,0 +1,200 @@
# language: fr
@api @authentication @2fa @security @mvp
Fonctionnalité: Appareils de confiance et authentification à deux facteurs
En tant qu'utilisateur soucieux de la sécurité
Je veux gérer mes appareils de confiance et activer l'authentification à deux facteurs
Afin de protéger mon compte contre les accès non autorisés
Contexte:
Étant donné que le système supporte les méthodes 2FA suivantes:
| Méthode | Disponibilité | Recommandée |
| Application TOTP | Oui | Oui |
| SMS | Oui | Non |
| Email | Oui | Non |
| Clés de sécurité USB | Phase 2 | Oui |
Scénario: Activation de l'authentification à deux facteurs par TOTP
Étant donné un utilisateur "alice@roadwave.fr" sans 2FA activé
Quand l'utilisateur accède à "Mon compte > Sécurité > Authentification à deux facteurs"
Et clique sur "Activer l'authentification à deux facteurs"
Alors le système génère un QR code avec secret TOTP
Et affiche le secret en texte clair pour saisie manuelle
Et affiche les instructions: "Scannez ce QR code avec Google Authenticator, Authy ou Microsoft Authenticator"
Et l'utilisateur scanne le QR code avec son application TOTP
Et saisit le code à 6 chiffres généré par l'application
Alors le 2FA est activé
Et 10 codes de récupération à usage unique sont générés
Et les codes de récupération sont affichés avec avertissement: "Conservez ces codes en lieu sûr"
Et un événement "2FA_ENABLED" est enregistré
Et un email de confirmation est envoyé
Et la métrique "auth.2fa.enabled" est incrémentée
Scénario: Connexion avec 2FA depuis un nouvel appareil
Étant donné un utilisateur "bob@roadwave.fr" avec 2FA activé
Et aucun appareil de confiance enregistré
Quand l'utilisateur se connecte depuis un iPhone avec email/mot de passe corrects
Alors une page de vérification 2FA s'affiche
Et l'utilisateur saisit le code à 6 chiffres de son application TOTP
Et coche l'option "Faire confiance à cet appareil pour 30 jours"
Alors la connexion est réussie
Et l'iPhone est enregistré comme appareil de confiance
Et un token d'appareil de confiance est stocké localement (durée: 30 jours)
Et un événement "2FA_SUCCESS_NEW_TRUSTED_DEVICE" est enregistré
Et un email est envoyé: "Nouvel appareil de confiance ajouté: iPhone"
Et la métrique "auth.2fa.trusted_device.added" est incrémentée
Scénario: Connexion depuis un appareil de confiance existant
Étant donné un utilisateur "charlie@roadwave.fr" avec 2FA activé
Et un iPhone enregistré comme appareil de confiance il y a 10 jours
Quand l'utilisateur se connecte depuis cet iPhone avec email/mot de passe corrects
Alors la connexion est réussie immédiatement sans demander le code 2FA
Et un événement "LOGIN_TRUSTED_DEVICE" est enregistré
Et la date de dernière utilisation de l'appareil de confiance est mise à jour
Et la métrique "auth.2fa.trusted_device.used" est incrémentée
Scénario: Expiration automatique d'un appareil de confiance après 30 jours
Étant donné un utilisateur "david@roadwave.fr" avec 2FA activé
Et un iPad enregistré comme appareil de confiance il y a 31 jours
Quand l'utilisateur se connecte depuis cet iPad avec email/mot de passe corrects
Alors une page de vérification 2FA s'affiche
Et l'utilisateur doit saisir le code TOTP
Et un message s'affiche: "Votre appareil de confiance a expiré après 30 jours. Veuillez vous authentifier à nouveau."
Et l'ancien token d'appareil de confiance est révoqué
Et un événement "TRUSTED_DEVICE_EXPIRED" est enregistré
Et la métrique "auth.2fa.trusted_device.expired" est incrémentée
Scénario: Gestion de la liste des appareils de confiance
Étant donné un utilisateur "eve@roadwave.fr" avec 2FA activé
Et 3 appareils de confiance enregistrés
Quand l'utilisateur accède à "Mon compte > Sécurité > Appareils de confiance"
Alors l'utilisateur voit la liste suivante:
| Appareil | Ajouté le | Dernière utilisation | Expire le | Actions |
| iPhone 14 Pro | 2026-01-15 | Il y a 2 heures | 2026-02-14 | [Révoquer] |
| iPad Air | 2026-01-10 | Il y a 5 jours | 2026-02-09 | [Révoquer] |
| MacBook Pro | 2026-01-05 | Il y a 10 jours | 2026-02-04 | [Révoquer] |
Et un bouton "Révoquer tous les appareils de confiance" est disponible
Et un compteur affiche "3 appareils de confiance actifs"
Scénario: Révocation manuelle d'un appareil de confiance
Étant donné un utilisateur "frank@roadwave.fr" avec 2FA activé
Et un MacBook Pro enregistré comme appareil de confiance
Quand l'utilisateur clique sur "Révoquer" pour le MacBook Pro
Alors l'appareil de confiance est immédiatement révoqué
Et le token d'appareil de confiance est invalidé
Et un événement "TRUSTED_DEVICE_REVOKED_MANUAL" est enregistré
Et un email est envoyé: "Vous avez révoqué l'appareil de confiance: MacBook Pro"
Et lors de la prochaine connexion, le code 2FA sera demandé
Et la métrique "auth.2fa.trusted_device.revoked" est incrémentée
Scénario: Utilisation d'un code de récupération en cas de perte de l'application TOTP
Étant donné un utilisateur "grace@roadwave.fr" avec 2FA activé
Et l'utilisateur a perdu l'accès à son application TOTP
Et il possède ses codes de récupération
Quand l'utilisateur se connecte avec email/mot de passe corrects
Et clique sur "Utiliser un code de récupération"
Et saisit l'un des 10 codes de récupération
Alors la connexion est réussie
Et le code de récupération utilisé est marqué comme consommé
Et il reste 9 codes de récupération disponibles
Et un événement "2FA_RECOVERY_CODE_USED" est enregistré
Et un email d'alerte est envoyé: "Un code de récupération a été utilisé. Il vous reste 9 codes."
Et l'utilisateur est invité à reconfigurer son 2FA
Et la métrique "auth.2fa.recovery_code.used" est incrémentée
Scénario: Régénération des codes de récupération
Étant donné un utilisateur "henry@roadwave.fr" avec 2FA activé
Et 3 codes de récupération ont été utilisés
Quand l'utilisateur accède à "Mon compte > Sécurité > Codes de récupération"
Et clique sur "Régénérer les codes de récupération"
Alors un message d'avertissement s'affiche: "Les anciens codes seront invalidés. Êtes-vous sûr ?"
Et après confirmation, 10 nouveaux codes de récupération sont générés
Et les anciens codes sont invalidés immédiatement
Et les nouveaux codes sont affichés une seule fois
Et un événement "2FA_RECOVERY_CODES_REGENERATED" est enregistré
Et un email est envoyé avec les nouveaux codes (chiffrés)
Et la métrique "auth.2fa.recovery_codes.regenerated" est incrémentée
Scénario: Désactivation du 2FA avec vérification renforcée
Étant donné un utilisateur "iris@roadwave.fr" avec 2FA activé
Quand l'utilisateur accède à "Mon compte > Sécurité > Authentification à deux facteurs"
Et clique sur "Désactiver l'authentification à deux facteurs"
Alors un message d'avertissement s'affiche: "Cela réduira la sécurité de votre compte"
Et l'utilisateur doit saisir son mot de passe actuel
Et l'utilisateur doit saisir un code 2FA valide
Et l'utilisateur doit confirmer par email via un lien sécurisé
Alors le 2FA est désactivé
Et tous les appareils de confiance sont révoqués
Et tous les codes de récupération sont invalidés
Et un événement "2FA_DISABLED" est enregistré
Et un email de confirmation est envoyé
Et la métrique "auth.2fa.disabled" est incrémentée
Scénario: Authentification 2FA par SMS en méthode de secours
Étant donné un utilisateur "jack@roadwave.fr" avec 2FA par TOTP activé
Et l'utilisateur a également configuré un numéro de téléphone de secours
Quand l'utilisateur se connecte et clique sur "Recevoir un code par SMS"
Alors un code à 6 chiffres est envoyé au numéro +33612345678
Et l'utilisateur saisit le code reçu par SMS
Alors la connexion est réussie
Et un événement "2FA_SMS_FALLBACK_USED" est enregistré
Et un email d'alerte est envoyé: "Vous avez utilisé la méthode SMS de secours"
Et la métrique "auth.2fa.sms.used" est incrémentée
Scénario: Limitation des tentatives de codes 2FA
Étant donné un utilisateur "kate@roadwave.fr" avec 2FA activé
Quand l'utilisateur se connecte avec email/mot de passe corrects
Et saisit 5 codes 2FA incorrects consécutivement
Alors le compte est temporairement bloqué pour 15 minutes
Et un message s'affiche: "Trop de tentatives échouées. Veuillez réessayer dans 15 minutes."
Et un email d'alerte est envoyé: "Multiples tentatives échouées de codes 2FA détectées"
Et un événement "2FA_TOO_MANY_ATTEMPTS" est enregistré avec niveau "HIGH"
Et la métrique "auth.2fa.blocked.too_many_attempts" est incrémentée
Scénario: Détection de connexion suspecte malgré 2FA valide
Étant donné un utilisateur "luke@roadwave.fr" avec 2FA activé
Et toutes ses connexions habituelles sont depuis la France
Quand l'utilisateur se connecte avec email/mot de passe corrects depuis la Russie
Et saisit un code 2FA valide
Alors la connexion est réussie mais marquée comme suspecte
Et l'utilisateur reçoit immédiatement un email: "Connexion inhabituelle depuis Russie"
Et une notification push est envoyée sur tous les appareils de confiance
Et l'accès aux fonctionnalités sensibles (paiement, changement de mot de passe) est temporairement bloqué
Et l'utilisateur doit confirmer son identité par email avant accès complet
Et un événement "2FA_SUSPICIOUS_LOCATION" est enregistré avec niveau "HIGH"
Et la métrique "auth.2fa.suspicious_login" est incrémentée
Scénario: Révocation de tous les appareils de confiance en cas de compromission
Étant donné un utilisateur "mary@roadwave.fr" avec 2FA activé
Et 5 appareils de confiance enregistrés
Et l'utilisateur suspecte une compromission de son compte
Quand l'utilisateur clique sur "Révoquer tous les appareils de confiance"
Alors tous les appareils de confiance sont immédiatement révoqués
Et tous les tokens d'appareils de confiance sont invalidés
Et toutes les sessions actives sont fermées (sauf la session actuelle)
Et un événement "ALL_TRUSTED_DEVICES_REVOKED" est enregistré avec niveau "HIGH"
Et un email de confirmation est envoyé
Et l'utilisateur devra saisir un code 2FA à chaque nouvelle connexion
Et la métrique "auth.2fa.trusted_device.bulk_revoked" est incrémentée
Scénario: Métriques de sécurité pour le 2FA
Étant donné que le système gère 50 000 utilisateurs avec 2FA activé
Quand les métriques de sécurité sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur cible |
| Pourcentage d'utilisateurs avec 2FA | > 60% |
| Taux de succès de validation 2FA | > 98% |
| Temps moyen de saisie du code 2FA | < 15s |
| Nombre d'appareils de confiance par user | Moyenne: 2.5 |
| Taux d'utilisation des codes de récup. | < 0.5% |
Et les métriques sont exportées vers le système de monitoring
Et des alertes sont déclenchées si le taux de succès < 95%
Scénario: Badge de sécurité pour utilisateurs avec 2FA activé
Étant donné un utilisateur "nathan@roadwave.fr" avec 2FA activé depuis 30 jours
Quand l'utilisateur consulte son profil public
Alors un badge "Compte sécurisé" s'affiche sur son profil
Et le badge indique: "Cet utilisateur a activé l'authentification à deux facteurs"
Et le badge améliore la visibilité et la crédibilité du créateur de contenu
Et la métrique "profile.badge.2fa_secured" est visible

View File

@@ -0,0 +1,120 @@
# language: fr
Fonctionnalité: Classification des contenus par âge
En tant que plateforme responsable
Je veux classifier les contenus par tranche d'âge
Afin de protéger les mineurs et respecter les obligations légales
Contexte:
Étant donné que l'API RoadWave est disponible
Scénario: Créateur doit classifier son contenu à la publication
Étant donné que je suis un créateur connecté
Quand je crée un nouveau contenu audio
Alors je dois obligatoirement choisir une classification d'âge parmi:
| classification | description |
| Tout public | Contenu adapté à tous les âges |
| 13+ | Contenu mature léger |
| 16+ | Contenu mature |
| 18+ | Contenu adulte |
Scénario: Publication impossible sans classification
Étant donné que je crée un contenu audio
Quand j'essaie de publier sans sélectionner de classification
Alors la publication échoue
Et je vois le message "Vous devez sélectionner une classification d'âge"
Scénario: Utilisateur 13-15 ans voit "Tout public" et "13+"
Étant donné que je suis un utilisateur de 14 ans
Et qu'il existe des contenus avec les classifications suivantes:
| classification | nombre |
| Tout public | 20 |
| 13+ | 15 |
| 16+ | 10 |
| 18+ | 5 |
Quand je demande des recommandations
Alors je vois 35 contenus (Tout public + 13+)
Et les contenus 16+ et 18+ ne sont jamais proposés
Scénario: Utilisateur 16-17 ans voit "Tout public", "13+" et "16+"
Étant donné que je suis un utilisateur de 17 ans
Et qu'il existe des contenus avec les classifications suivantes:
| classification | nombre |
| Tout public | 20 |
| 13+ | 15 |
| 16+ | 10 |
| 18+ | 5 |
Quand je demande des recommandations
Alors je vois 45 contenus (Tout public + 13+ + 16+)
Et les contenus 18+ ne sont pas proposés
Scénario: Utilisateur 18+ voit tous les contenus
Étant donné que je suis un utilisateur de 25 ans
Et qu'il existe des contenus avec toutes les classifications
Quand je demande des recommandations
Alors je vois tous les contenus sans restriction
Et aucun filtre d'âge n'est appliqué
Scénario: Inscription réussie à 13 ans pile - accès limité à "Tout public" et "13+"
Étant donné que je m'inscris avec une date de naissance "2013-01-21"
Alors mon compte est créé avec succès
Et je peux voir les contenus "Tout public" et "13+"
Et les contenus 16+ et 18+ ne sont pas accessibles
Scénario: Modérateur reclassifie un contenu mal catégorisé
Étant donné qu'un contenu est publié avec la classification "Tout public"
Et que ce contenu contient du langage inapproprié détecté en modération
Quand le modérateur reclassifie ce contenu en "16+"
Alors la nouvelle classification est appliquée immédiatement
Et le contenu n'est plus visible pour les utilisateurs de moins de 16 ans
Et le créateur reçoit une notification de reclassification
Scénario: Strike si classification volontairement incorrecte
Étant donné qu'un créateur a publié un contenu "18+" classifié comme "Tout public"
Et que ce contenu a été signalé
Quand le modérateur confirme la mauvaise classification volontaire
Alors le créateur reçoit 1 strike
Et le contenu est reclassifié en "18+"
Et le créateur reçoit une notification explicative
Scénario: Créateur peut voir la distribution d'âge de son audience
Étant donné que je suis un créateur
Et que j'ai publié des contenus avec différentes classifications
Quand je consulte mes statistiques
Alors je vois la répartition des âges de mes auditeurs:
| tranche_age | pourcentage |
| 13-15 ans | 15% |
| 16-17 ans | 20% |
| 18+ ans | 65% |
Scénario: Recherche filtrée par classification d'âge
Étant donné que je suis un utilisateur de 16 ans
Quand je recherche des contenus
Alors les résultats incluent uniquement:
| classification |
| Tout public |
| 13+ |
| 16+ |
Et je ne vois pas les contenus 18+ dans les résultats
Scénario: Notification si tentative d'accès à contenu non autorisé
Étant donné que je suis un utilisateur de 15 ans
Et qu'un contenu "16+" est partagé avec moi via un lien direct
Quand j'essaie d'accéder au contenu
Alors l'accès est refusé
Et je vois le message "Ce contenu est réservé aux utilisateurs de 16 ans et plus"
Scénario: Validation obligatoire des 3 premiers contenus inclut la classification
Étant donné que je suis un nouveau créateur
Et que je publie mon premier contenu classifié "18+"
Quand le modérateur valide mon contenu
Alors il vérifie que la classification "18+" est appropriée
Et peut la modifier si nécessaire avant validation
Scénario: Statistiques de classification dans l'interface créateur
Étant donné que je suis un créateur
Quand je consulte mes contenus publiés
Alors je vois pour chaque contenu:
| information | exemple |
| Classification actuelle | 13+ |
| Nombre de signalements | 2 |
| Reclassifications | Aucune / 1× par modérateur |

View File

@@ -0,0 +1,84 @@
# language: fr
Fonctionnalité: Connexion utilisateur
En tant qu'utilisateur existant
Je veux me connecter à mon compte
Afin d'accéder à mes contenus et paramètres
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur existe avec:
| email | mot_de_passe |
| user@test.fr | Password123 |
Scénario: Connexion réussie avec identifiants valides
Quand je me connecte avec:
| email | mot_de_passe |
| user@test.fr | Password123 |
Alors je suis connecté avec succès
Et je reçois un access token valide pour 15 minutes
Et je reçois un refresh token valide pour 30 jours
Scénario: Connexion échouée avec email inexistant
Quand je me connecte avec l'email "inexistant@test.fr"
Alors la connexion échoue
Et je vois le message "Email ou mot de passe incorrect"
Scénario: Connexion échouée avec mot de passe incorrect
Quand je me connecte avec:
| email | mot_de_passe |
| user@test.fr | MauvaisPass1 |
Alors la connexion échoue
Et je vois le message "Email ou mot de passe incorrect"
Scénario: Blocage après 5 tentatives échouées
Étant donné que j'ai échoué 4 tentatives de connexion
Quand j'échoue une 5ème tentative de connexion
Alors mon compte est temporairement bloqué
Et je vois le message "Compte bloqué pour 15 minutes après 5 tentatives échouées"
Et je reçois un email de notification de blocage
Scénario: Tentative de connexion pendant le blocage
Étant donné que mon compte est bloqué suite à 5 tentatives échouées
Et que seulement 5 minutes se sont écoulées
Quand j'essaie de me connecter avec les bons identifiants
Alors la connexion échoue
Et je vois le message "Compte bloqué. Réessayez dans 10 minutes"
Scénario: Déblocage automatique après 15 minutes
Étant donné que mon compte est bloqué suite à 5 tentatives échouées
Et que 15 minutes se sont écoulées
Quand je me connecte avec les bons identifiants
Alors je suis connecté avec succès
Et le compteur de tentatives est réinitialisé
Scénario: Reset du compteur après connexion réussie
Étant donné que j'ai échoué 3 tentatives de connexion
Quand je me connecte avec les bons identifiants
Alors je suis connecté avec succès
Et le compteur de tentatives est remis à 0
Scénario: Reset automatique du compteur après 15 minutes sans blocage
Étant donné que j'ai échoué 3 tentatives de connexion
Et que 15 minutes se sont écoulées sans nouvelle tentative
Quand je consulte mon compteur de tentatives
Alors le compteur est réinitialisé à 0
Scénario: Déblocage via lien "Mot de passe oublié"
Étant donné que mon compte est bloqué suite à 5 tentatives échouées
Quand j'utilise la fonction "Mot de passe oublié"
Et que je réinitialise mon mot de passe
Alors le blocage est levé immédiatement
Et je peux me connecter avec le nouveau mot de passe
Scénario: Email de notification lors d'un blocage
Étant donné que j'ai échoué 5 tentatives de connexion
Alors je reçois un email avec:
| sujet | Tentatives de connexion suspectes détectées |
| contenu_contient | Votre compte a été temporairement bloqué |
| lien_mot_de_passe | présent |
Scénario: Connexion multi-device simultanée autorisée
Étant donné que je suis connecté sur un appareil iOS
Quand je me connecte également sur un appareil Android
Alors les deux sessions sont actives simultanément
Et je peux utiliser l'application sur les deux appareils

View File

@@ -0,0 +1,199 @@
# language: fr
Fonctionnalité: Gestion de compte utilisateur
En tant qu'utilisateur connecté
Je veux gérer les paramètres de mon compte
Afin de maintenir la sécurité et l'exactitude de mes informations
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté avec:
| email | user@test.fr |
| mot_de_passe | Password123 |
# ==========================================
# Déconnexion
# ==========================================
Scénario: Déconnexion volontaire de l'appareil actuel
Quand je clique sur "Se déconnecter"
Alors ma session est invalidée immédiatement
Et mon refresh token est révoqué
Et je suis redirigé vers l'écran de connexion
Et je dois me reconnecter pour accéder à l'application
Scénario: Déconnexion ne révoque pas les autres appareils
Étant donné que je suis connecté sur mon iPhone et mon iPad
Quand je me déconnecte depuis mon iPhone
Alors la session iPhone est invalidée
Et ma session iPad reste active
Et je peux continuer à utiliser l'application sur iPad
# ==========================================
# Changement de mot de passe
# ==========================================
Scénario: Changement de mot de passe avec ancien mot de passe correct
Quand je change mon mot de passe depuis les paramètres avec:
| ancien_mot_de_passe | Password123 |
| nouveau_mot_de_passe | NewPass456 |
| confirmation | NewPass456 |
Alors mon mot de passe est modifié avec succès
Et je reste connecté sur cet appareil
Et tous les autres appareils sont déconnectés
Et je reçois un email de confirmation de changement
Et je vois le message "Mot de passe modifié avec succès"
Scénario: Changement de mot de passe avec ancien mot de passe incorrect
Quand je change mon mot de passe avec un ancien mot de passe incorrect "WrongPass123"
Alors le changement échoue
Et je vois le message "Ancien mot de passe incorrect"
Et mon mot de passe actuel reste inchangé
Scénario: Changement de mot de passe avec nouveau mot de passe invalide
Quand je change mon mot de passe avec un nouveau mot de passe "faible"
Alors le changement échoue
Et je vois le message "Le mot de passe doit contenir au moins 8 caractères, 1 majuscule et 1 chiffre"
Scénario: Changement de mot de passe avec confirmation non correspondante
Quand je change mon mot de passe avec:
| ancien_mot_de_passe | Password123 |
| nouveau_mot_de_passe | NewPass456 |
| confirmation | DiffPass789 |
Alors le changement échoue
Et je vois le message "Les mots de passe ne correspondent pas"
Scénario: Nouveau mot de passe identique à l'ancien
Quand je change mon mot de passe avec:
| ancien_mot_de_passe | Password123 |
| nouveau_mot_de_passe | Password123 |
| confirmation | Password123 |
Alors le changement échoue
Et je vois le message "Le nouveau mot de passe doit être différent de l'ancien"
Scénario: Notification sur tous les appareils après changement de mot de passe
Étant donné que je suis connecté sur 3 appareils différents
Quand je change mon mot de passe depuis mon iPhone
Alors je reçois une notification push sur mes 2 autres appareils
Et je reçois un email de confirmation avec:
| sujet | Votre mot de passe a été modifié |
| appareil | iPhone 13 - Safari |
| localisation | Paris, France |
| action_urgence | Lien pour sécuriser le compte |
# ==========================================
# Changement d'email
# ==========================================
Scénario: Changement d'email avec vérification
Quand je change mon email pour "nouveau@test.fr"
Alors un email de vérification est envoyé à "nouveau@test.fr"
Et mon ancien email "user@test.fr" reste actif pour la connexion
Et je vois le message "Email de vérification envoyé à nouveau@test.fr"
Et le lien de vérification expire dans 7 jours
Scénario: Validation du changement d'email
Étant donné que j'ai demandé un changement d'email pour "nouveau@test.fr"
Et que j'ai reçu le lien de vérification
Quand je clique sur le lien de vérification dans l'email
Alors mon email est changé pour "nouveau@test.fr"
Et je reçois une notification sur l'ancien email "user@test.fr"
Et je vois le message "Email modifié avec succès"
Et je dois utiliser "nouveau@test.fr" pour me connecter désormais
Scénario: Changement d'email vers un email déjà utilisé
Étant donné qu'un utilisateur existe avec l'email "existant@test.fr"
Quand j'essaie de changer mon email pour "existant@test.fr"
Alors le changement échoue
Et je vois le message "Cet email est déjà utilisé par un autre compte"
Scénario: Changement d'email avec format invalide
Quand j'essaie de changer mon email pour "email.invalide"
Alors le changement échoue
Et je vois le message "Format d'email invalide"
Scénario: Expiration du lien de vérification de changement d'email
Étant donné que j'ai demandé un changement d'email il y a 8 jours
Quand j'essaie d'utiliser le lien de vérification
Alors la vérification échoue
Et je vois le message "Ce lien a expiré"
Et mon email reste inchangé à "user@test.fr"
Et je peux demander un nouveau changement d'email
Scénario: Annulation du changement d'email avant vérification
Étant donné que j'ai demandé un changement d'email pour "nouveau@test.fr"
Et que je n'ai pas encore vérifié le nouveau email
Quand je demande à annuler le changement d'email
Alors la demande de changement est annulée
Et le lien de vérification est invalidé
Et mon email reste "user@test.fr"
Scénario: Limite de changements d'email
Étant donné que j'ai déjà changé mon email 2 fois dans les 30 derniers jours
Quand j'essaie de changer mon email une 3ème fois
Alors le changement échoue
Et je vois le message "Maximum 2 changements d'email par mois"
Scénario: Notification de sécurité sur l'ancien email
Étant donné que j'ai changé mon email de "ancien@test.fr" à "nouveau@test.fr"
Alors je reçois un email sur "ancien@test.fr" avec:
| sujet | Votre adresse email a été modifiée |
| contenu | Votre email de connexion est maintenant nouveau@test.fr |
| date_heure | présente |
| appareil | présent |
| action_urgence | Lien pour annuler si ce n'était pas vous |
# ==========================================
# Changement de pseudo
# ==========================================
Scénario: Changement de pseudo valide
Quand je change mon pseudo pour "nouveau_pseudo"
Alors mon pseudo est modifié avec succès
Et je vois le message "Pseudo modifié avec succès"
Et le nouveau pseudo apparaît sur mon profil
Scénario: Changement de pseudo invalide - trop court
Quand j'essaie de changer mon pseudo pour "ab"
Alors le changement échoue
Et je vois le message "Le pseudo doit contenir entre 3 et 30 caractères"
Scénario: Changement de pseudo invalide - caractères spéciaux
Quand j'essaie de changer mon pseudo pour "user@123"
Alors le changement échoue
Et je vois le message "Le pseudo ne peut contenir que des lettres, chiffres et underscores"
Scénario: Changement de pseudo déjà utilisé
Étant donné qu'un utilisateur existe avec le pseudo "pseudo_existant"
Quand j'essaie de changer mon pseudo pour "pseudo_existant"
Alors le changement échoue
Et je vois le message "Ce pseudo est déjà utilisé"
Scénario: Limite de changements de pseudo
Étant donné que j'ai changé mon pseudo il y a 15 jours
Quand j'essaie de changer mon pseudo à nouveau
Alors le changement échoue
Et je vois le message "Vous ne pouvez changer votre pseudo qu'une fois par mois"
# ==========================================
# Consultation des informations de compte
# ==========================================
Scénario: Consulter les informations de mon compte
Quand je consulte les paramètres de mon compte
Alors je vois les informations suivantes:
| champ | valeur |
| Email | user@test.fr |
| Pseudo | user_test |
| Date création | 15/01/2026 |
| Email vérifié | Oui |
| 2FA activée | Non |
| Abonnement | Gratuit |
Scénario: Historique des changements de sécurité
Quand je consulte l'historique de sécurité de mon compte
Alors je vois la liste des événements suivants:
| événement | date | appareil |
| Changement mot de passe | 01/02/2026 | iPhone 13 |
| Activation 2FA | 25/01/2026 | iPad Pro |
| Changement email | 20/01/2026 | PC Windows |
| Création compte | 15/01/2026 | iPhone 13 |

View File

@@ -0,0 +1,112 @@
# language: fr
Fonctionnalité: Inscription utilisateur
En tant que nouvel utilisateur
Je veux créer un compte avec email et mot de passe
Afin d'accéder à l'application RoadWave
Contexte:
Étant donné que l'API RoadWave est disponible
Et que Zitadel est configuré
Scénario: Inscription réussie avec données valides
Étant donné que l'email "nouveau@example.com" n'existe pas
Quand je m'inscris avec les données suivantes:
| champ | valeur |
| email | nouveau@example.com |
| mot_de_passe | Password123 |
| pseudo | nouveau_user |
| date_naissance | 1995-06-15 |
Alors mon compte est créé avec succès
Et je reçois un email de vérification
Et le lien de vérification expire dans 7 jours
Et je suis redirigé vers l'application
Scénario: Inscription avec email déjà existant
Étant donné qu'un utilisateur existe avec l'email "existant@example.com"
Quand je m'inscris avec l'email "existant@example.com"
Alors l'inscription échoue
Et je vois le message "Cet email est déjà utilisé"
Scénario: Inscription avec mot de passe invalide - trop court
Quand je m'inscris avec un mot de passe de moins de 8 caractères "Pass1"
Alors l'inscription échoue
Et je vois le message "Le mot de passe doit contenir au moins 8 caractères"
Scénario: Inscription avec mot de passe invalide - sans majuscule
Quand je m'inscris avec un mot de passe sans majuscule "password123"
Alors l'inscription échoue
Et je vois le message "Le mot de passe doit contenir au moins une majuscule"
Scénario: Inscription avec mot de passe invalide - sans chiffre
Quand je m'inscris avec un mot de passe sans chiffre "Password"
Alors l'inscription échoue
Et je vois le message "Le mot de passe doit contenir au moins un chiffre"
Scénario: Inscription avec pseudo invalide - trop court
Quand je m'inscris avec un pseudo de 2 caractères "ab"
Alors l'inscription échoue
Et je vois le message "Le pseudo doit contenir entre 3 et 30 caractères"
Scénario: Inscription avec pseudo invalide - caractères spéciaux
Quand je m'inscris avec un pseudo contenant des caractères spéciaux "user@123"
Alors l'inscription échoue
Et je vois le message "Le pseudo ne peut contenir que des lettres, chiffres et underscores"
Scénario: Inscription avec email invalide
Quand je m'inscris avec un email invalide "email.invalide"
Alors l'inscription échoue
Et je vois le message "Format d'email invalide"
Plan du Scénario: Inscription avec âge minimum non respecté
Étant donné la date du jour est "2026-01-21"
Quand je m'inscris avec une date de naissance "<date_naissance>"
Alors l'inscription échoue
Et je vois le message "Vous devez avoir au moins 13 ans pour créer un compte"
Exemples:
| date_naissance | age |
| 2013-01-22 | 12 |
| 2015-06-15 | 10 |
| 2020-01-01 | 6 |
Scénario: Inscription avec âge limite acceptable (13 ans)
Étant donné la date du jour est "2026-01-21"
Quand je m'inscris avec une date de naissance "2013-01-21"
Alors mon compte est créé avec succès
Et je peux accéder aux contenus "Tout public" et "13+"
Scénario: Inscription avec âge supérieur à 18 ans
Étant donné la date du jour est "2026-01-21"
Quand je m'inscris avec une date de naissance "1990-06-15"
Alors mon compte est créé avec succès
Et j'ai accès à tous les contenus sans restriction d'âge
Scénario: Données minimales requises à l'inscription
Quand je m'inscris sans fournir de nom complet
Et sans fournir de photo de profil
Et sans fournir de bio
Alors mon compte est créé avec succès
Et un avatar par défaut est généré
Et les champs optionnels sont vides
Scénario: Renvoyer l'email de vérification
Étant donné que je me suis inscrit avec l'email "nouveau@example.com"
Et que je n'ai pas vérifié mon email
Quand je demande à renvoyer l'email de vérification
Alors un nouvel email de vérification est envoyé
Et le précédent lien est invalidé
Scénario: Limite de renvoi d'email de vérification
Étant donné que je me suis inscrit avec l'email "nouveau@example.com"
Et que j'ai déjà renvoyé l'email de vérification 3 fois aujourd'hui
Quand je demande à renvoyer l'email de vérification une 4ème fois
Alors la demande échoue
Et je vois le message "Vous avez atteint la limite de 3 renvois par jour"
Scénario: Expiration du lien de vérification
Étant donné que je me suis inscrit il y a 8 jours
Et que je n'ai pas vérifié mon email
Quand j'essaie d'utiliser le lien de vérification
Alors la vérification échoue
Et je vois le message "Ce lien a expiré"
Et je peux demander un nouveau lien

View File

@@ -0,0 +1,171 @@
# language: fr
@api @authentication @security @mvp
Fonctionnalité: Limitation des tentatives de connexion
En tant que système de sécurité
Je veux limiter les tentatives de connexion échouées
Afin de protéger les comptes utilisateurs contre les attaques par force brute
Contexte:
Étant donné que le système est configuré avec les limites suivantes:
| Paramètre | Valeur |
| Tentatives max avant blocage | 5 |
| Durée de blocage temporaire | 15 min |
| Tentatives max avant blocage 24h | 10 |
| Durée de blocage prolongé | 24h |
| Fenêtre de temps pour reset | 30 min |
Scénario: Connexion réussie réinitialise le compteur de tentatives
Étant donné un utilisateur "alice@roadwave.fr" avec 3 tentatives échouées
Quand l'utilisateur se connecte avec les bons identifiants
Alors la connexion est réussie
Et le compteur de tentatives échouées est réinitialisé à 0
Et un événement de sécurité "LOGIN_SUCCESS_AFTER_FAILURES" est enregistré
Scénario: Blocage temporaire après 5 tentatives échouées
Étant donné un utilisateur "bob@roadwave.fr" avec 4 tentatives échouées
Quand l'utilisateur tente de se connecter avec un mauvais mot de passe
Alors la connexion échoue avec le code d'erreur "ACCOUNT_TEMPORARILY_LOCKED"
Et le message est "Votre compte est temporairement verrouillé pour 15 minutes suite à de multiples tentatives échouées"
Et un email de notification de sécurité est envoyé à "bob@roadwave.fr"
Et un événement de sécurité "ACCOUNT_LOCKED_TEMP" est enregistré
Et la métrique "security.account_locks.temporary" est incrémentée
Scénario: Tentative de connexion pendant le blocage temporaire
Étant donné un utilisateur "charlie@roadwave.fr" bloqué temporairement
Et il reste 10 minutes avant la fin du blocage
Quand l'utilisateur tente de se connecter avec les bons identifiants
Alors la connexion échoue avec le code d'erreur "ACCOUNT_TEMPORARILY_LOCKED"
Et le message contient "Votre compte reste verrouillé pour 10 minutes"
Et le temps de blocage restant est indiqué en minutes
Et la tentative ne rallonge pas la durée du blocage
Scénario: Connexion autorisée après expiration du blocage temporaire
Étant donné un utilisateur "david@roadwave.fr" bloqué temporairement il y a 16 minutes
Quand l'utilisateur tente de se connecter avec les bons identifiants
Alors la connexion est réussie
Et le compteur de tentatives échouées est réinitialisé à 0
Et le statut de blocage est levé
Et un événement de sécurité "ACCOUNT_UNLOCKED_AUTO" est enregistré
Scénario: Blocage prolongé après 10 tentatives échouées sur 24h
Étant donné un utilisateur "eve@roadwave.fr" avec historique:
| Tentatives échouées | Quand |
| 5 | Il y a 2 heures |
| Blocage 15min levé | Il y a 1h30 |
| 4 | Il y a 30 minutes |
Quand l'utilisateur tente une nouvelle connexion échouée
Alors la connexion échoue avec le code d'erreur "ACCOUNT_LOCKED_24H"
Et le message est "Votre compte est verrouillé pour 24 heures suite à de multiples tentatives suspectes"
Et un email urgent de sécurité est envoyé avec un lien de déblocage sécurisé
Et une notification SMS est envoyée (si configuré)
Et un événement de sécurité "ACCOUNT_LOCKED_24H" est enregistré avec niveau "HIGH"
Et la métrique "security.account_locks.prolonged" est incrémentée
Scénario: Blocage différencié par adresse IP
Étant donné un utilisateur "frank@roadwave.fr" avec 3 tentatives échouées depuis IP "1.2.3.4"
Quand l'utilisateur se connecte avec succès depuis IP "5.6.7.8"
Alors la connexion est réussie
Et le compteur de tentatives échouées pour IP "1.2.3.4" reste à 3
Et le compteur de tentatives échouées pour IP "5.6.7.8" est à 0
Et un événement de sécurité "LOGIN_FROM_NEW_IP" est enregistré
Scénario: Alerte de sécurité sur pattern suspect multi-IP
Étant donné un utilisateur "grace@roadwave.fr"
Quand 5 tentatives échouées sont détectées depuis 5 IP différentes en 10 minutes:
| IP | Tentatives | Timestamp |
| 1.2.3.4 | 2 | Il y a 10 min |
| 5.6.7.8 | 1 | Il y a 8 min |
| 9.10.11.12 | 1 | Il y a 5 min |
| 13.14.15.16| 1 | Il y a 2 min |
Alors le compte est immédiatement bloqué pour 24h
Et un email d'alerte critique "POSSIBLE_CREDENTIAL_STUFFING_ATTACK" est envoyé
Et l'équipe de sécurité est notifiée via webhook
Et toutes les sessions actives sont révoquées
Et la métrique "security.attacks.credential_stuffing.detected" est incrémentée
Scénario: Déblocage manuel par l'utilisateur via email sécurisé
Étant donné un utilisateur "henry@roadwave.fr" bloqué pour 24h
Et il a reçu un email avec un lien de déblocage sécurisé à usage unique
Quand l'utilisateur clique sur le lien dans les 2 heures suivant l'email
Et confirme son identité via un code envoyé par SMS
Alors le compte est débloqué immédiatement
Et l'utilisateur est invité à changer son mot de passe
Et un événement de sécurité "ACCOUNT_UNLOCKED_MANUAL" est enregistré
Et la métrique "security.account_unlocks.user_initiated" est incrémentée
Scénario: Réinitialisation automatique du compteur après période d'inactivité
Étant donné un utilisateur "iris@roadwave.fr" avec 3 tentatives échouées
Et aucune nouvelle tentative depuis 35 minutes
Quand l'utilisateur tente de se connecter avec un mauvais mot de passe
Alors le compteur de tentatives est réinitialisé à 1
Et le message d'erreur est standard sans mention de blocage imminent
Et un événement de sécurité "ATTEMPT_COUNTER_RESET" est enregistré
Scénario: Protection contre les attaques par timing
Étant donné un utilisateur "jack@roadwave.fr"
Quand l'utilisateur effectue 10 tentatives de connexion échouées
Alors chaque réponse HTTP prend entre 800ms et 1200ms (temps constant)
Et les messages d'erreur ne révèlent pas si l'email existe
Et la métrique "security.timing_protection.applied" est incrémentée
Et les logs n'exposent pas de patterns de timing exploitables
Scénario: Escalade des notifications avec tentatives répétées
Étant donné un utilisateur "kate@roadwave.fr" Premium
Quand les événements suivants se produisent:
| Événement | Notification |
| 3 tentatives échouées | Aucune notification |
| 5 tentatives (blocage) | Email standard |
| 10 tentatives (24h) | Email + SMS + notification app|
| Tentative pendant 24h | Email urgent + alerte support |
Alors chaque niveau de notification est proportionnel à la gravité
Et l'utilisateur peut configurer ses préférences de notification
Et la métrique "security.notifications.escalated" est incrémentée
Scénario: Whitelist d'IP pour utilisateurs de confiance
Étant donné un utilisateur "luke@roadwave.fr" avec IP de confiance "1.2.3.4"
Et la whitelist est configurée pour autoriser 10 tentatives au lieu de 5
Quand l'utilisateur effectue 7 tentatives échouées depuis "1.2.3.4"
Alors le compte n'est pas bloqué
Et un avertissement est affiché "3 tentatives restantes avant blocage"
Et un événement de sécurité "TRUSTED_IP_EXTENDED_ATTEMPTS" est enregistré
Scénario: Logs de sécurité détaillés pour audit
Étant donné un utilisateur "mary@roadwave.fr" avec tentatives échouées
Quand un audit de sécurité est effectué
Alors les logs contiennent pour chaque tentative:
| Champ | Exemple |
| Timestamp | 2026-02-03T14:32:18.123Z |
| User ID | uuid-123-456 |
| Email | mary@roadwave.fr |
| IP Address | 1.2.3.4 |
| User Agent | Mozilla/5.0 (iPhone...) |
| Failure Reason | INVALID_PASSWORD |
| Attempts Count | 3 |
| Geolocation | Paris, France |
| Device Fingerprint| hash-abc-def |
Et les logs sont conservés pendant 90 jours minimum
Et les logs sont conformes RGPD (pas de mots de passe en clair)
Scénario: Métriques de performance du système de limitation
Étant donné que le système traite 1000 tentatives de connexion par minute
Quand les métriques de performance sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur cible |
| Temps de vérification du compteur | < 50ms |
| Latence ajoutée par le rate limiting | < 100ms |
| Pourcentage de tentatives bloquées | < 2% |
| Faux positifs (utilisateurs légitimes) | < 0.1% |
| Temps de déblocage automatique | < 1s |
Et les métriques sont exportées vers le système de monitoring
Et des alertes sont déclenchées si les seuils sont dépassés
Scénario: Compatibilité avec authentification multi-facteurs
Étant donné un utilisateur "nathan@roadwave.fr" avec 2FA activé
Et il a 4 tentatives échouées (mot de passe correct mais code 2FA incorrect)
Quand l'utilisateur tente une 5ème connexion avec mot de passe correct et mauvais code 2FA
Alors le compte est bloqué temporairement
Et le message précise "Blocage suite à de multiples erreurs de code 2FA"
Et le compteur 2FA est distinct du compteur de mot de passe
Et un événement de sécurité "2FA_LOCK_TRIGGERED" est enregistré

View File

@@ -0,0 +1,191 @@
# language: fr
@api @authentication @sessions @mvp
Fonctionnalité: Gestion des sessions multi-appareils
En tant qu'utilisateur
Je veux gérer mes sessions actives sur plusieurs appareils
Afin de contrôler l'accès à mon compte et améliorer la sécurité
Contexte:
Étant donné que le système supporte les sessions suivantes:
| Paramètre | Valeur |
| Nombre max de sessions simultanées | 5 |
| Durée de vie d'une session | 30 jours |
| Durée d'inactivité avant expiration | 7 jours |
| Durée du token de refresh | 90 jours |
| Taille max du stockage de session | 10 KB |
Scénario: Création d'une nouvelle session avec empreinte d'appareil
Étant donné un utilisateur "alice@roadwave.fr" non connecté
Quand l'utilisateur se connecte depuis un iPhone 14 Pro avec iOS 17.2
Alors une nouvelle session est créée avec les métadonnées:
| Champ | Valeur |
| Device Type | mobile |
| OS | iOS 17.2 |
| App Version | 1.2.3 |
| Device Model | iPhone 14 Pro |
| Browser | N/A |
| IP Address | 1.2.3.4 |
| Geolocation | Paris, France |
| Created At | 2026-02-03T14:32:18Z |
| Last Activity | 2026-02-03T14:32:18Z |
Et un token JWT avec durée de vie de 30 jours est généré
Et un refresh token avec durée de vie de 90 jours est généré
Et un événement "SESSION_CREATED" est enregistré
Et la métrique "sessions.created" est incrémentée
Scénario: Connexion simultanée sur plusieurs appareils
Étant donné un utilisateur "bob@roadwave.fr" connecté sur:
| Appareil | OS | Dernière activité |
| iPhone 13 | iOS 16.5 | Il y a 5 min |
| iPad Pro | iPadOS 17.1 | Il y a 2 heures |
| MacBook Pro | macOS 14.2 | Il y a 1 jour |
Quand l'utilisateur se connecte depuis un Samsung Galaxy S23
Alors une nouvelle session est créée
Et l'utilisateur a maintenant 4 sessions actives
Et toutes les sessions précédentes restent valides
Et un événement "NEW_DEVICE_LOGIN" est enregistré
Et une notification push est envoyée sur tous les appareils: "Nouvelle connexion depuis Samsung Galaxy S23"
Scénario: Limitation du nombre de sessions simultanées
Étant donné un utilisateur "charlie@roadwave.fr" avec 5 sessions actives
Quand l'utilisateur se connecte depuis un 6ème appareil
Alors la session la plus ancienne est automatiquement révoquée
Et une nouvelle session est créée pour le nouvel appareil
Et l'utilisateur reçoit une notification: "Votre session sur [Ancien Appareil] a été fermée automatiquement"
Et un événement "SESSION_EVICTED_MAX_LIMIT" est enregistré
Et la métrique "sessions.evicted.max_limit" est incrémentée
Scénario: Liste des sessions actives dans les paramètres du compte
Étant donné un utilisateur "david@roadwave.fr" avec 3 sessions actives
Quand l'utilisateur accède à "Mon compte > Sécurité > Appareils connectés"
Alors l'utilisateur voit la liste suivante:
| Appareil | Localisation | Dernière activité | IP | Actions |
| iPhone 14 Pro | Paris, France | Actif maintenant | 1.2.3.4 | [Cet appareil]|
| iPad Air | Lyon, France | Il y a 2 heures | 5.6.7.8 | [Déconnecter] |
| MacBook Pro | Marseille, FR | Il y a 3 jours | 9.10.11.12| [Déconnecter] |
Et la session actuelle est clairement identifiée
Et un bouton "Déconnecter tous les autres appareils" est disponible
Scénario: Révocation manuelle d'une session spécifique
Étant donné un utilisateur "eve@roadwave.fr" avec 4 sessions actives
Et il consulte la liste de ses appareils depuis son iPhone
Quand l'utilisateur clique sur "Déconnecter" pour la session "MacBook Pro"
Alors la session "MacBook Pro" est immédiatement révoquée
Et le token JWT associé est invalidé dans Redis
Et le refresh token est révoqué
Et l'utilisateur sur le MacBook Pro est déconnecté lors de sa prochaine requête
Et un événement "SESSION_REVOKED_MANUAL" est enregistré
Et une notification est envoyée: "Vous avez été déconnecté de votre MacBook Pro"
Scénario: Déconnexion de tous les autres appareils
Étant donné un utilisateur "frank@roadwave.fr" avec 5 sessions actives
Et il suspecte un accès non autorisé
Quand l'utilisateur clique sur "Déconnecter tous les autres appareils" depuis son iPhone
Alors toutes les sessions sauf la session actuelle (iPhone) sont révoquées
Et 4 tokens JWT sont invalidés
Et 4 refresh tokens sont révoqués
Et un événement "SESSIONS_REVOKED_ALL_OTHER" est enregistré
Et une notification est envoyée sur tous les appareils déconnectés
Et un email de confirmation est envoyé: "Vous avez déconnecté tous vos autres appareils"
Et la métrique "sessions.revoked.bulk" est incrémentée
Scénario: Détection de connexion suspecte depuis un nouveau pays
Étant donné un utilisateur "grace@roadwave.fr" avec sessions habituelles en France
Quand l'utilisateur se connecte depuis une IP en Russie
Alors une alerte de sécurité est déclenchée
Et un email est envoyé: "Connexion détectée depuis Russie - Est-ce bien vous ?"
Et une notification push est envoyée sur tous les appareils de confiance
Et la session est créée mais marquée comme "suspecte"
Et un événement "SUSPICIOUS_LOCATION_LOGIN" est enregistré avec niveau "HIGH"
Et l'utilisateur doit confirmer son identité par code SMS avant d'accéder aux fonctionnalités sensibles
Scénario: Expiration automatique d'une session inactive
Étant donné un utilisateur "henry@roadwave.fr" avec une session sur iPad
Et la session n'a pas été utilisée depuis 8 jours
Quand le job de nettoyage des sessions s'exécute
Alors la session iPad est automatiquement révoquée
Et le token JWT est invalidé
Et le refresh token est révoqué
Et un événement "SESSION_EXPIRED_INACTIVITY" est enregistré
Et un email est envoyé: "Votre session sur iPad a expiré suite à 8 jours d'inactivité"
Et la métrique "sessions.expired.inactivity" est incrémentée
Scénario: Rafraîchissement automatique du token avant expiration
Étant donné un utilisateur "iris@roadwave.fr" avec une session active
Et le token JWT expire dans 2 minutes
Quand l'application mobile effectue une requête API
Alors l'API détecte que le token expire bientôt
Et un nouveau token JWT est généré automatiquement
Et le nouveau token est retourné dans le header "X-Refreshed-Token"
Et l'application mobile stocke le nouveau token
Et un événement "TOKEN_REFRESHED" est enregistré
Et la métrique "tokens.refreshed" est incrémentée
Scénario: Révocation de toutes les sessions lors d'un changement de mot de passe
Étant donné un utilisateur "jack@roadwave.fr" avec 4 sessions actives
Quand l'utilisateur change son mot de passe depuis son iPhone
Alors toutes les sessions sauf la session actuelle (iPhone) sont révoquées
Et tous les tokens JWT sont invalidés
Et tous les refresh tokens sont révoqués
Et un événement "SESSIONS_REVOKED_PASSWORD_CHANGE" est enregistré
Et un email est envoyé: "Votre mot de passe a été modifié. Toutes vos autres sessions ont été déconnectées."
Et des notifications push sont envoyées sur tous les appareils déconnectés
Scénario: Persistance de la session avec "Se souvenir de moi"
Étant donné un utilisateur "kate@roadwave.fr" qui se connecte
Quand l'utilisateur coche l'option "Se souvenir de moi"
Alors la durée de vie du token JWT est étendue à 90 jours
Et la durée de vie du refresh token est étendue à 180 jours
Et la session persiste même après fermeture de l'application
Et un cookie sécurisé "remember_token" est stocké (pour web)
Et un événement "LONG_SESSION_CREATED" est enregistré
Et la métrique "sessions.remember_me.enabled" est incrémentée
Scénario: Détection de vol de token et révocation automatique
Étant donné un utilisateur "luke@roadwave.fr" avec une session active
Et le token JWT a été volé et utilisé depuis une IP différente
Quand le système détecte une utilisation simultanée du même token depuis 2 IP différentes
Alors toutes les sessions de l'utilisateur sont immédiatement révoquées
Et tous les tokens sont invalidés
Et un email d'alerte critique est envoyé: "Activité suspecte détectée - Toutes vos sessions ont été fermées"
Et une notification push urgente est envoyée sur tous les appareils
Et l'utilisateur doit réinitialiser son mot de passe avant de se reconnecter
Et un événement "TOKEN_THEFT_DETECTED" est enregistré avec niveau "CRITICAL"
Et l'équipe de sécurité est alertée via webhook
Scénario: Synchronisation des informations de session en temps réel
Étant donné un utilisateur "mary@roadwave.fr" connecté sur 3 appareils
Quand l'utilisateur révoque une session depuis son iPhone
Alors la liste des sessions est mise à jour en temps réel sur tous les appareils via WebSocket
Et l'appareil déconnecté reçoit immédiatement une notification de déconnexion
Et l'UI est rafraîchie automatiquement sur tous les appareils connectés
Et la métrique "sessions.realtime_sync" est incrémentée
Scénario: Métriques de performance de gestion des sessions
Étant donné que le système gère 100 000 sessions actives
Quand les métriques de performance sont collectées
Alors les indicateurs suivants sont respectés:
| Métrique | Valeur cible |
| Temps de création de session | < 50ms |
| Temps de validation de token | < 20ms |
| Temps de révocation de session | < 100ms |
| Latence de synchronisation temps réel | < 500ms |
| Taux de succès du refresh automatique | > 99.9% |
Et les métriques sont exportées vers le système de monitoring
Et des alertes sont déclenchées si les seuils sont dépassés
Scénario: Stockage sécurisé des sessions dans Redis
Étant donné un utilisateur "nathan@roadwave.fr" avec une session active
Quand la session est stockée dans Redis
Alors les données suivantes sont chiffrées:
| Champ | Chiffrement |
| User ID | Hash |
| Refresh Token | AES-256 |
| Device Info | Non |
| IP Address | Hash |
Et la clé Redis a un TTL correspondant à la durée de vie de la session
Et les données sensibles ne sont jamais loggées en clair
Et les accès à Redis sont audités
Et la métrique "sessions.storage.encrypted" est incrémentée

View File

@@ -0,0 +1,107 @@
# language: fr
Fonctionnalité: Récupération de compte
En tant qu'utilisateur ayant oublié son mot de passe
Je veux pouvoir réinitialiser mon mot de passe via email
Afin de récupérer l'accès à mon compte
Contexte:
Étant donné que l'API RoadWave est disponible
Et qu'un utilisateur existe avec l'email "user@test.fr"
Scénario: Demander la réinitialisation du mot de passe
Quand je clique sur "Mot de passe oublié"
Et que je saisis mon email "user@test.fr"
Alors je reçois un email avec un lien de réinitialisation
Et le lien expire dans 1 heure
Et je vois le message "Email de réinitialisation envoyé"
Scénario: Email inexistant lors de la demande de réinitialisation
Quand je demande une réinitialisation pour l'email "inexistant@test.fr"
Alors je vois le même message "Email de réinitialisation envoyé"
Mais aucun email n'est envoyé (sécurité - pas d'énumération d'emails)
Scénario: Réinitialiser le mot de passe avec un lien valide
Étant donné que j'ai demandé une réinitialisation de mot de passe
Et que j'ai reçu le lien de réinitialisation
Quand je clique sur le lien
Et que je saisis un nouveau mot de passe "NouveauPass123"
Et que je confirme le nouveau mot de passe "NouveauPass123"
Alors mon mot de passe est modifié avec succès
Et je suis déconnecté de tous mes appareils sauf celui en cours
Et je reçois un email de confirmation de changement
Scénario: Lien de réinitialisation expiré
Étant donné que j'ai demandé une réinitialisation il y a 2 heures
Quand j'essaie d'utiliser le lien
Alors je vois le message "Ce lien a expiré"
Et je peux demander un nouveau lien
Scénario: Nouveau mot de passe ne respecte pas les règles
Étant donné que j'ai un lien de réinitialisation valide
Quand je saisis un nouveau mot de passe "faible"
Alors la réinitialisation échoue
Et je vois le message "Le mot de passe doit contenir au moins 8 caractères, 1 majuscule et 1 chiffre"
Scénario: Confirmation du mot de passe ne correspond pas
Étant donné que j'ai un lien de réinitialisation valide
Quand je saisis un nouveau mot de passe "NouveauPass123"
Et que je confirme avec un mot de passe différent "AutrePass123"
Alors la réinitialisation échoue
Et je vois le message "Les mots de passe ne correspondent pas"
Scénario: Limite de demandes de réinitialisation
Étant donné que j'ai déjà demandé 3 réinitialisations dans la dernière heure
Quand je demande une 4ème réinitialisation
Alors la demande échoue
Et je vois le message "Maximum 3 demandes par heure. Réessayez plus tard."
Scénario: Compteur de demandes se réinitialise après 1 heure
Étant donné que j'ai demandé 3 réinitialisations
Et que 1 heure s'est écoulée
Quand je demande une nouvelle réinitialisation
Alors la demande réussit
Et je reçois un email avec un nouveau lien
Scénario: Email de notification de changement de mot de passe
Étant donné que je viens de réinitialiser mon mot de passe
Alors je reçois un email de confirmation avec:
| sujet | Votre mot de passe a été modifié |
| contenu_contient | Votre mot de passe a été modifié |
| date_heure | présente |
| appareil | présent |
| localisation | présente |
| action_urgence | Lien si ce n'était pas vous |
Scénario: Notification push si changement depuis appareil non reconnu
Étant donné que je me suis toujours connecté depuis mon iPhone
Et que je réinitialise mon mot de passe depuis un PC Windows
Alors je reçois une notification push sur mon iPhone avec:
| titre | Mot de passe modifié |
| message | Depuis Windows - Paris, France |
| action | Sécuriser le compte si ce n'est pas vous |
Scénario: Déconnexion de tous les appareils après réinitialisation
Étant donné que je suis connecté sur 4 appareils différents
Et que je réinitialise mon mot de passe depuis un navigateur web
Alors les 3 autres appareils sont déconnectés immédiatement
Et seule la session du navigateur web reste active
Et je vois le message "Vous avez été déconnecté des autres appareils par sécurité"
Scénario: Lien de réinitialisation invalide si déjà utilisé
Étant donné que j'ai réinitialisé mon mot de passe avec un lien
Quand j'essaie de réutiliser le même lien
Alors je vois le message "Ce lien a déjà été utilisé"
Et je peux demander un nouveau lien si nécessaire
Scénario: Nouveau lien invalide l'ancien
Étant donné que j'ai demandé une réinitialisation et reçu un lien
Quand je demande une nouvelle réinitialisation
Alors l'ancien lien est invalidé
Et seul le nouveau lien fonctionne
Scénario: Réinitialisation débloque un compte bloqué
Étant donné que mon compte est bloqué après 5 tentatives de connexion
Quand je réinitialise mon mot de passe via email
Alors le blocage est levé immédiatement
Et je peux me connecter avec le nouveau mot de passe
Et le compteur de tentatives est remis à 0

View File

@@ -0,0 +1,187 @@
# language: fr
@api @authentication @security @mvp
Fonctionnalité: Récupération et réinitialisation avancée du mot de passe
En tant qu'utilisateur ayant oublié son mot de passe
Je veux pouvoir récupérer l'accès à mon compte de manière sécurisée
Afin de reprendre l'utilisation de l'application
Contexte:
Étant donné que le système de récupération est configuré avec:
| Paramètre | Valeur |
| Durée de validité du lien de reset | 1 heure |
| Nombre max de demandes par heure | 3 |
| Nombre max de demandes par jour | 10 |
| Longueur du token de reset | 64 chars |
| Délai de cooldown entre demandes | 5 minutes |
Scénario: Demande de réinitialisation de mot de passe
Étant donné un utilisateur "alice@roadwave.fr" qui a oublié son mot de passe
Quand l'utilisateur clique sur "Mot de passe oublié ?" sur l'écran de connexion
Et saisit son adresse email "alice@roadwave.fr"
Alors un email de réinitialisation est envoyé avec:
| Élément | Contenu |
| Sujet | Réinitialisation de votre mot de passe RoadWave |
| Lien sécurisé | https://roadwave.fr/reset?token=abc123... |
| Durée de validité | Ce lien expire dans 1 heure |
| Warning sécurité | Si vous n'êtes pas à l'origine de cette demande... |
Et un événement "PASSWORD_RESET_REQUESTED" est enregistré
Et la métrique "auth.password_reset.requested" est incrémentée
Et un message s'affiche: "Si cette adresse est enregistrée, vous recevrez un email de réinitialisation"
Scénario: Protection contre l'énumération d'adresses email
Étant donné une adresse email "inexistant@roadwave.fr" non enregistrée
Quand un utilisateur demande la réinitialisation pour cette adresse
Alors le même message de confirmation s'affiche: "Si cette adresse est enregistrée, vous recevrez un email"
Et aucun email n'est envoyé
Et le temps de réponse est identique à une demande valide (800-1200ms)
Et un événement "PASSWORD_RESET_UNKNOWN_EMAIL" est enregistré
Et la métrique "auth.password_reset.unknown_email" est incrémentée
Et les logs n'exposent pas l'information de l'existence ou non de l'email
Scénario: Limitation du nombre de demandes de réinitialisation
Étant donné un utilisateur "bob@roadwave.fr"
Et il a déjà effectué 3 demandes de réinitialisation dans la dernière heure
Quand l'utilisateur effectue une 4ème demande
Alors la demande est refusée avec le message: "Trop de demandes de réinitialisation. Veuillez attendre 1 heure."
Et aucun email n'est envoyé
Et un événement "PASSWORD_RESET_RATE_LIMITED" est enregistré
Et la métrique "auth.password_reset.rate_limited" est incrémentée
Scénario: Utilisation du lien de réinitialisation valide
Étant donné un utilisateur "charlie@roadwave.fr" ayant demandé la réinitialisation
Et il a reçu un email avec un token valide il y a 30 minutes
Quand l'utilisateur clique sur le lien dans l'email
Alors il est redirigé vers la page de réinitialisation
Et le formulaire de nouveau mot de passe s'affiche
Et le token est validé côté serveur
Et un événement "PASSWORD_RESET_TOKEN_ACCESSED" est enregistré
Et la session est sécurisée avec CSRF protection
Scénario: Définition du nouveau mot de passe avec validation
Étant donné un utilisateur "david@roadwave.fr" sur la page de réinitialisation
Et il a un token valide
Quand l'utilisateur saisit un nouveau mot de passe "SecurePass2026!"
Et confirme le mot de passe
Alors le mot de passe est validé selon les règles de sécurité
Et le mot de passe est hashé avec bcrypt (cost: 12)
Et le mot de passe est enregistré dans la base de données
Et toutes les sessions actives sont révoquées
Et tous les tokens d'accès sont invalidés
Et un événement "PASSWORD_RESET_COMPLETED" est enregistré
Et un email de confirmation est envoyé: "Votre mot de passe a été modifié avec succès"
Et la métrique "auth.password_reset.completed" est incrémentée
Et l'utilisateur est redirigé vers la page de connexion
Scénario: Tentative d'utilisation d'un token expiré
Étant donné un utilisateur "eve@roadwave.fr" ayant demandé la réinitialisation
Et il a reçu un email avec un token valide il y a 2 heures
Quand l'utilisateur clique sur le lien expiré
Alors un message d'erreur s'affiche: "Ce lien de réinitialisation a expiré. Veuillez faire une nouvelle demande."
Et un bouton "Demander un nouveau lien" est affiché
Et un événement "PASSWORD_RESET_TOKEN_EXPIRED" est enregistré
Et la métrique "auth.password_reset.token_expired" est incrémentée
Scénario: Tentative d'utilisation d'un token déjà utilisé
Étant donné un utilisateur "frank@roadwave.fr" ayant réinitialisé son mot de passe
Et le token a déjà été utilisé il y a 10 minutes
Quand l'utilisateur tente de réutiliser le même lien
Alors un message d'erreur s'affiche: "Ce lien a déjà été utilisé. Si vous avez besoin de réinitialiser à nouveau, faites une nouvelle demande."
Et un événement "PASSWORD_RESET_TOKEN_REUSED" est enregistré avec niveau "MEDIUM"
Et un email d'alerte est envoyé: "Tentative de réutilisation d'un ancien lien de réinitialisation"
Et la métrique "auth.password_reset.token_reused" est incrémentée
Scénario: Détection de tentative d'attaque par force brute sur les tokens
Étant donné un attaquant qui tente de deviner des tokens de réinitialisation
Quand 10 tokens invalides sont testés depuis la même IP en 5 minutes
Alors l'IP est bloquée temporairement pour 1 heure
Et tous les tokens valides pour cette IP sont invalidés
Et un événement "PASSWORD_RESET_BRUTE_FORCE_DETECTED" est enregistré avec niveau "CRITICAL"
Et l'équipe de sécurité est alertée via webhook
Et la métrique "security.password_reset.brute_force" est incrémentée
Scénario: Réinitialisation avec validation 2FA pour comptes sensibles
Étant donné un utilisateur "grace@roadwave.fr" avec 2FA activé
Et il a demandé la réinitialisation de son mot de passe
Quand l'utilisateur clique sur le lien de réinitialisation
Alors une étape supplémentaire de vérification 2FA s'affiche
Et l'utilisateur doit saisir un code TOTP ou un code de récupération
Et après validation 2FA, le formulaire de nouveau mot de passe s'affiche
Et un événement "PASSWORD_RESET_2FA_VALIDATED" est enregistré
Et la métrique "auth.password_reset.with_2fa" est incrémentée
Scénario: Notification de sécurité sur tous les appareils
Étant donné un utilisateur "henry@roadwave.fr" connecté sur 3 appareils
Quand l'utilisateur réinitialise son mot de passe
Alors une notification push est envoyée sur tous les appareils:
| Message |
| Votre mot de passe a été modifié |
| Si ce n'est pas vous, contactez immédiatement le support |
Et un email est envoyé avec détails:
| Détail | Valeur |
| Date et heure | 2026-02-03 14:32:18 |
| Adresse IP | 1.2.3.4 |
| Localisation | Paris, France |
| Appareil | iPhone 14 Pro |
| Navigateur | Safari 17.2 |
Et un lien "Ce n'était pas moi" permet de bloquer le compte immédiatement
Scénario: Historique des modifications de mot de passe
Étant donné un utilisateur "iris@roadwave.fr"
Quand l'utilisateur accède à "Mon compte > Sécurité > Historique"
Alors l'utilisateur voit l'historique des modifications:
| Date | Action | IP | Appareil | Localisation |
| 2026-02-03 14:32 | Réinitialisation mot de passe | 1.2.3.4 | iPhone 14 | Paris, FR |
| 2026-01-15 10:20 | Changement mot de passe | 5.6.7.8 | MacBook Pro | Lyon, FR |
| 2025-12-01 08:15 | Création du compte | 9.10.11.12| iPad Air | Marseille, FR |
Et les événements sont conservés pendant 90 jours minimum
Et les logs sont conformes RGPD
Scénario: Réinitialisation impossible pour compte bloqué ou suspendu
Étant donné un utilisateur "jack@roadwave.fr" dont le compte est suspendu
Quand l'utilisateur demande la réinitialisation de son mot de passe
Alors un message s'affiche: "Votre compte est actuellement suspendu. Veuillez contacter le support."
Et aucun email de réinitialisation n'est envoyé
Et un événement "PASSWORD_RESET_ACCOUNT_SUSPENDED" est enregistré
Et un lien vers le support est fourni
Et la métrique "auth.password_reset.blocked_account" est incrémentée
Scénario: Vérification de l'unicité du nouveau mot de passe
Étant donné un utilisateur "kate@roadwave.fr" sur la page de réinitialisation
Quand l'utilisateur tente de définir le même mot de passe que l'ancien
Alors une erreur s'affiche: "Veuillez choisir un mot de passe différent de l'ancien"
Et le mot de passe n'est pas enregistré
Et un événement "PASSWORD_RESET_SAME_PASSWORD" est enregistré
Et la métrique "auth.password_reset.same_password" est incrémentée
Scénario: Vérification contre les mots de passe compromis
Étant donné un utilisateur "luke@roadwave.fr" sur la page de réinitialisation
Quand l'utilisateur tente de définir un mot de passe "Password123!"
Et ce mot de passe figure dans la base de données Have I Been Pwned
Alors une erreur s'affiche: "Ce mot de passe est connu et a été compromis. Veuillez en choisir un autre."
Et le mot de passe n'est pas enregistré
Et un événement "PASSWORD_RESET_COMPROMISED_PASSWORD" est enregistré
Et la métrique "auth.password_reset.compromised_blocked" est incrémentée
Scénario: Cooldown entre demandes successives de réinitialisation
Étant donné un utilisateur "mary@roadwave.fr"
Et il a fait une demande de réinitialisation il y a 2 minutes
Quand l'utilisateur fait une nouvelle demande de réinitialisation
Alors la demande est refusée avec le message: "Veuillez attendre 5 minutes entre chaque demande"
Et un compteur affiche "Vous pourrez faire une nouvelle demande dans 3 minutes"
Et un événement "PASSWORD_RESET_COOLDOWN" est enregistré
Et la métrique "auth.password_reset.cooldown_hit" est incrémentée
Scénario: Métriques de sécurité pour la réinitialisation de mot de passe
Étant donné que le système traite 1000 demandes de réinitialisation par jour
Quand les métriques de sécurité sont collectées
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur cible |
| Taux de complétion des réinitialisations | > 75% |
| Taux de tokens expirés avant utilisation | < 20% |
| Temps moyen de complétion | < 5 min |
| Taux de détection de mots de passe compromis | > 5% |
| Nombre de tentatives de brute force bloquées | Visible |
Et les métriques sont exportées vers le système de monitoring
Et des alertes sont déclenchées si anomalies détectées

View File

@@ -0,0 +1,114 @@
# language: fr
Fonctionnalité: Gestion des sessions et tokens
En tant qu'utilisateur connecté
Je veux que mes sessions soient sécurisées et gérées automatiquement
Afin de maintenir l'accès à l'application sans friction
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté avec succès
Scénario: Access token expire après 15 minutes
Étant donné que j'ai reçu un access token
Et que 15 minutes se sont écoulées
Quand je fais une requête API avec cet access token
Alors la requête échoue avec le code 401
Et je vois le message "Token expiré"
Scénario: Refresh automatique du token avec refresh token
Étant donné que mon access token a expiré
Et que mon refresh token est valide
Quand l'application demande un nouveau access token
Alors je reçois un nouvel access token valide pour 15 minutes
Et je reçois un nouveau refresh token (rotation)
Et l'ancien refresh token est invalidé
Scénario: Refresh token expire après 30 jours d'inactivité
Étant donné que je me suis connecté il y a 30 jours
Et que je n'ai pas utilisé l'application depuis
Quand j'essaie d'utiliser mon refresh token
Alors la requête échoue
Et je dois me reconnecter avec email/password
Scénario: Prolongation automatique de la session si l'app est utilisée
Étant donné que je me suis connecté il y a 25 jours
Et que j'utilise l'application régulièrement
Quand je fais une requête API
Alors ma session est automatiquement prolongée
Et mon refresh token reste valide
Scénario: Détection de token replay attack
Étant donné que j'ai rafraîchi mon token
Et que j'ai reçu un nouveau refresh token
Quand j'essaie de réutiliser l'ancien refresh token
Alors la requête échoue
Et je vois le message "Token invalide ou révoqué"
Et toutes mes sessions sont révoquées par sécurité
Scénario: Voir la liste des appareils connectés
Étant donné que je suis connecté sur 3 appareils différents
Quand je consulte la liste de mes appareils connectés
Alors je vois 3 appareils avec les informations suivantes:
| information | exemple |
| OS | iOS 17.1 |
| Navigateur | Safari |
| Dernière connexion | Il y a 2 heures |
| Localisation | Paris, France (IP visible) |
Scénario: Révoquer un appareil spécifique
Étant donné que je suis connecté sur mon iPhone et mon iPad
Quand je révoque la session de mon iPad depuis les paramètres
Alors la session iPad est immédiatement déconnectée
Et ma session iPhone reste active
Scénario: Déconnecter tous les appareils sauf celui en cours
Étant donné que je suis connecté sur 4 appareils
Quand je clique sur "Déconnecter tous les appareils"
Alors les 3 autres appareils sont déconnectés
Et seul l'appareil actuel reste connecté
Scénario: Alerte de connexion depuis nouveau device
Étant donné que je me suis toujours connecté depuis Paris
Quand je me connecte depuis un nouvel appareil à Lyon
Alors je reçois une notification push sur mes autres appareils
Et je reçois un email avec:
| sujet | Nouvelle connexion détectée |
| localisation | Lyon, France |
| appareil | Android 14 - Chrome |
| action | Lien pour révoquer la session |
Scénario: Alerte de connexion suspecte depuis pays différent
Étant donné que je me suis toujours connecté depuis la France
Quand je me connecte depuis un appareil aux États-Unis
Alors je reçois une notification push immédiate
Et je reçois un email d'alerte de sécurité
Et la nouvelle session nécessite une validation 2FA même si désactivée
Scénario: Déconnexion après 30 jours d'inactivité totale
Étant donné que je ne me suis pas connecté depuis 30 jours
Quand j'ouvre l'application
Alors je suis automatiquement déconnecté
Et je dois me reconnecter avec email/password
Et je vois le message "Session expirée après 30 jours d'inactivité"
Scénario: Sessions multiples simultanées autorisées
Étant donné que je suis connecté sur:
| appareil |
| iPhone |
| iPad |
| PC Windows (Web) |
Quand je fais des actions sur les 3 appareils simultanément
Alors toutes les sessions fonctionnent sans conflit
Et chaque appareil maintient sa propre session
Scénario: Validation de JWT via Zitadel
Étant donné que j'ai reçu un access token JWT
Quand l'API RoadWave valide le token
Alors la validation est faite localement avec la clé publique Zitadel
Et aucune requête externe n'est effectuée (performance)
Et le token contient les claims suivants:
| claim | valeur_exemple |
| sub | user-id-123 |
| email | user@test.fr |
| exp | timestamp + 15 minutes |
| iss | zitadel.roadwave.com |

View File

@@ -0,0 +1,132 @@
# language: fr
Fonctionnalité: Authentification à deux facteurs (2FA)
En tant qu'utilisateur soucieux de sécurité
Je veux activer la 2FA sur mon compte
Afin de protéger mon accès même si mon mot de passe est compromis
Contexte:
Étant donné que l'API RoadWave est disponible
Et que je suis connecté à mon compte
Scénario: Activer la 2FA TOTP (Time-based One-Time Password)
Étant donné que la 2FA n'est pas activée sur mon compte
Quand je choisis d'activer la 2FA TOTP
Alors je vois un QR code à scanner
Et je vois le secret partagé en texte clair (backup)
Et je dois entrer un code de vérification depuis mon app authenticator
Quand je saisis un code TOTP valide
Alors la 2FA TOTP est activée avec succès
Et je reçois des codes de backup (10 codes)
Scénario: Connexion avec 2FA TOTP activée
Étant donné que la 2FA TOTP est activée sur mon compte
Quand je me connecte avec email/password
Alors je suis redirigé vers la page de saisie du code 2FA
Quand je saisis un code TOTP valide de mon authenticator
Alors je suis connecté avec succès
Scénario: Connexion échouée avec code TOTP invalide
Étant donné que la 2FA TOTP est activée
Quand je me connecte avec email/password
Et que je saisis un code TOTP invalide "000000"
Alors la connexion échoue
Et je vois le message "Code d'authentification invalide"
Et je peux réessayer
Scénario: Utiliser un code de backup pour 2FA
Étant donné que la 2FA TOTP est activée
Et que j'ai perdu l'accès à mon authenticator
Quand je me connecte avec email/password
Et que je clique sur "Utiliser un code de backup"
Et que je saisis un code de backup valide
Alors je suis connecté avec succès
Et le code de backup utilisé est invalidé
Et il me reste 9 codes de backup
Scénario: Activer la 2FA par email
Étant donné que la 2FA n'est pas activée
Quand je choisis d'activer la 2FA par email
Alors la 2FA email est activée immédiatement
Et je vois le message "2FA email activée. Vous recevrez un code à chaque connexion"
Scénario: Connexion avec 2FA email
Étant donné que la 2FA email est activée
Quand je me connecte avec email/password
Alors je reçois un email avec un code à 6 chiffres
Et le code expire dans 10 minutes
Et je dois saisir ce code pour terminer la connexion
Scénario: Code 2FA email expiré
Étant donné que la 2FA email est activée
Et que je me suis connecté avec email/password
Et que j'ai reçu un code 2FA par email il y a 11 minutes
Quand je saisis ce code
Alors la connexion échoue
Et je vois le message "Code expiré. Demandez un nouveau code."
Scénario: Renvoyer le code 2FA email
Étant donné que la 2FA email est activée
Et que je suis sur la page de saisie du code 2FA
Quand je clique sur "Renvoyer le code"
Alors je reçois un nouveau code par email
Et l'ancien code est invalidé
Scénario: Ajouter un appareil de confiance (skip 2FA pendant 30 jours)
Étant donné que la 2FA TOTP est activée
Quand je me connecte avec email/password et code TOTP
Et que je coche "Ne plus demander sur cet appareil"
Alors je suis connecté avec succès
Et cet appareil est enregistré comme "appareil de confiance"
Quand je me reconnecte dans les 30 jours suivants sur ce même appareil
Alors je ne dois pas saisir de code 2FA
Scénario: Appareil de confiance expire après 30 jours
Étant donné que j'ai enregistré un appareil de confiance il y a 31 jours
Quand je me connecte depuis cet appareil
Alors je dois saisir un code 2FA
Et je vois le message "Appareil de confiance expiré. Veuillez vous authentifier"
Scénario: Voir la liste des appareils de confiance
Étant donné que j'ai enregistré 3 appareils de confiance
Quand je consulte mes paramètres de sécurité
Alors je vois la liste de mes 3 appareils de confiance avec:
| information | exemple |
| Nom | iPhone 13 - Safari |
| Date ajout | 15 janvier 2026 |
| Dernière vue | Il y a 2 heures |
| Expire le | 14 février 2026 |
Scénario: Révoquer un appareil de confiance
Étant donné que j'ai un iPhone enregistré comme appareil de confiance
Quand je révoque cet appareil depuis les paramètres
Alors l'appareil est supprimé de la liste
Quand je me reconnecte depuis cet iPhone
Alors je dois saisir un code 2FA
Scénario: Révoquer tous les appareils de confiance
Étant donné que j'ai 5 appareils de confiance enregistrés
Quand je clique sur "Révoquer tous les appareils de confiance"
Alors tous les appareils sont révoqués
Et je vois le message "Tous les appareils de confiance ont été révoqués"
Scénario: 2FA forcée pour connexion suspecte malgré appareil de confiance
Étant donné que j'ai un appareil de confiance enregistré en France
Et que je me connecte depuis ce même appareil mais avec une IP américaine
Quand je tente de me connecter
Alors la 2FA est requise malgré l'appareil de confiance
Et je vois le message "Connexion suspecte détectée. Authentification requise."
Scénario: Désactiver la 2FA
Étant donné que la 2FA TOTP est activée
Quand je désactive la 2FA depuis mes paramètres
Et que je confirme avec mon mot de passe
Alors la 2FA est désactivée
Et tous les codes de backup sont invalidés
Et tous les appareils de confiance sont révoqués
Scénario: Régénérer les codes de backup
Étant donné que la 2FA est activée
Et que j'ai utilisé 8 codes de backup sur 10
Quand je demande à régénérer les codes de backup
Alors je reçois 10 nouveaux codes
Et tous les anciens codes (utilisés ou non) sont invalidés

View File

@@ -0,0 +1,250 @@
# language: fr
@api @authentication @security @mvp
Fonctionnalité: Validation des règles de mot de passe
En tant que système d'authentification
Je veux valider la complexité des mots de passe
Afin de garantir la sécurité des comptes utilisateurs
Contexte:
Étant donné un utilisateur souhaite créer un compte ou modifier son mot de passe
# ============================================================================
# VALIDATION LONGUEUR MINIMALE (8 CARACTÈRES)
# ============================================================================
Scénario: Mot de passe valide avec 8 caractères minimum
Étant donné l'utilisateur saisit le mot de passe "Azerty123"
Quand le système valide le mot de passe
Alors la validation doit réussir
Et aucune erreur ne doit être affichée
Scénario: Mot de passe trop court (7 caractères)
Étant donné l'utilisateur saisit le mot de passe "Azert12"
Quand le système valide le mot de passe
Alors la validation doit échouer
Et le message d'erreur doit être "Le mot de passe doit contenir au moins 8 caractères"
Et le champ doit être marqué en rouge
Scénario: Mot de passe très court (3 caractères)
Étant donné l'utilisateur saisit le mot de passe "Ab1"
Quand le système valide le mot de passe
Alors la validation doit échouer
Et le message d'erreur doit être "Le mot de passe doit contenir au moins 8 caractères"
# ============================================================================
# VALIDATION MAJUSCULE REQUISE
# ============================================================================
Scénario: Mot de passe valide avec au moins 1 majuscule
Étant donné l'utilisateur saisit le mot de passe "Monpass123"
Quand le système valide le mot de passe
Alors la validation doit réussir
Et le critère "majuscule" doit être validé avec une coche verte
Scénario: Mot de passe sans majuscule
Étant donné l'utilisateur saisit le mot de passe "monpass123"
Quand le système valide le mot de passe
Alors la validation doit échouer
Et le message d'erreur doit être "Le mot de passe doit contenir au moins 1 majuscule"
Scénario: Mot de passe avec plusieurs majuscules
Étant donné l'utilisateur saisit le mot de passe "MonPASSword123"
Quand le système valide le mot de passe
Alors la validation doit réussir
Car au moins 1 majuscule est présente
# ============================================================================
# VALIDATION CHIFFRE REQUIS
# ============================================================================
Scénario: Mot de passe valide avec au moins 1 chiffre
Étant donné l'utilisateur saisit le mot de passe "Monpass1"
Quand le système valide le mot de passe
Alors la validation doit réussir
Et le critère "chiffre" doit être validé avec une coche verte
Scénario: Mot de passe sans chiffre
Étant donné l'utilisateur saisit le mot de passe "Monpassword"
Quand le système valide le mot de passe
Alors la validation doit échouer
Et le message d'erreur doit être "Le mot de passe doit contenir au moins 1 chiffre"
Scénario: Mot de passe avec plusieurs chiffres
Étant donné l'utilisateur saisit le mot de passe "Monpass123456"
Quand le système valide le mot de passe
Alors la validation doit réussir
Car au moins 1 chiffre est présent
# ============================================================================
# VALIDATION COMBINÉE DES 3 CRITÈRES
# ============================================================================
Scénario: Mot de passe valide respectant tous les critères
Étant donné l'utilisateur saisit le mot de passe "SecurePass2024!"
Quand le système valide le mot de passe
Alors la validation doit réussir
Et tous les critères doivent être validés :
| critère | statut |
| longueur | |
| majuscule | |
| chiffre | |
Scénario: Mot de passe échouant sur plusieurs critères
Étant donné l'utilisateur saisit le mot de passe "pass"
Quand le système valide le mot de passe
Alors la validation doit échouer
Et les messages d'erreur suivants doivent être affichés :
| Le mot de passe doit contenir au moins 8 caractères |
| Le mot de passe doit contenir au moins 1 majuscule |
| Le mot de passe doit contenir au moins 1 chiffre |
Scénario: Mot de passe long mais sans majuscule ni chiffre
Étant donné l'utilisateur saisit le mot de passe "monmotdepasse"
Quand le système valide le mot de passe
Alors la validation doit échouer
Et les messages d'erreur suivants doivent être affichés :
| Le mot de passe doit contenir au moins 1 majuscule |
| Le mot de passe doit contenir au moins 1 chiffre |
# ============================================================================
# VALIDATION TEMPS RÉEL (FRONTEND)
# ============================================================================
Scénario: Affichage progressif des critères pendant la saisie
Étant donné l'utilisateur commence à saisir son mot de passe
Quand l'utilisateur tape "m"
Alors les critères suivants doivent être affichés :
| critère | statut |
| longueur | |
| majuscule | |
| chiffre | |
Quand l'utilisateur tape "Mon"
Alors les critères doivent être mis à jour :
| critère | statut |
| longueur | |
| majuscule | |
| chiffre | |
Quand l'utilisateur tape "Monpass1"
Alors les critères doivent être mis à jour :
| critère | statut |
| longueur | |
| majuscule | |
| chiffre | |
Scénario: Feedback visuel temps réel
Étant donné l'utilisateur saisit progressivement son mot de passe
Quand un critère est validé
Alors une coche verte doit apparaître à côté du critère
Et le texte du critère doit passer en vert
Quand un critère n'est pas validé
Alors une croix rouge doit apparaître
Et le texte du critère doit rester en gris ou rouge
# ============================================================================
# VALIDATION BACKEND (SÉCURITÉ)
# ============================================================================
Scénario: Validation backend en plus du frontend
Étant donné l'utilisateur contourne la validation frontend
Et envoie directement le mot de passe "weak" via API
Quand le backend reçoit la requête
Alors la validation backend doit rejeter le mot de passe
Et retourner une erreur HTTP 400 Bad Request
Et le message doit être :
"""
{
"error": "invalid_password",
"details": [
"Le mot de passe doit contenir au moins 8 caractères",
"Le mot de passe doit contenir au moins 1 majuscule",
"Le mot de passe doit contenir au moins 1 chiffre"
]
}
"""
Scénario: Validation backend avec mot de passe valide
Étant donné l'utilisateur envoie le mot de passe "SecurePass123"
Quand le backend valide le mot de passe
Alors la validation backend doit réussir
Et le mot de passe doit être hashé avec bcrypt (coût 12)
Et le hash doit être stocké dans la base de données
# ============================================================================
# CAS LIMITES ET CARACTÈRES SPÉCIAUX
# ============================================================================
Scénario: Mot de passe avec caractères spéciaux (acceptés)
Étant donné l'utilisateur saisit le mot de passe "MonP@ss123!"
Quand le système valide le mot de passe
Alors la validation doit réussir
Car les caractères spéciaux sont autorisés (mais non obligatoires)
Scénario: Mot de passe avec espaces (acceptés)
Étant donné l'utilisateur saisit le mot de passe "Mon Pass 123"
Quand le système valide le mot de passe
Alors la validation doit réussir
Car les espaces sont autorisés
Scénario: Mot de passe avec accents (acceptés)
Étant donné l'utilisateur saisit le mot de passe "MônPàss123"
Quand le système valide le mot de passe
Alors la validation doit réussir
Car les caractères accentués comptent comme des lettres
Scénario: Mot de passe avec émojis (acceptés)
Étant donné l'utilisateur saisit le mot de passe "MonPass123🔒"
Quand le système valide le mot de passe
Alors la validation doit réussir
Car les émojis sont autorisés
Scénario: Mot de passe vide
Étant donné l'utilisateur laisse le champ mot de passe vide
Quand le système valide le mot de passe
Alors la validation doit échouer
Et le message d'erreur doit être "Le mot de passe est requis"
# ============================================================================
# MODIFICATION MOT DE PASSE
# ============================================================================
Scénario: Changement de mot de passe avec validation
Étant donné un utilisateur authentifié veut changer son mot de passe
Et l'utilisateur saisit son ancien mot de passe "OldPass123"
Et l'utilisateur saisit le nouveau mot de passe "NewSecure456"
Quand le système valide le nouveau mot de passe
Alors la validation doit réussir
Et le nouveau mot de passe doit respecter les mêmes règles
Et l'ancien mot de passe doit être vérifié avant le changement
Scénario: Nouveau mot de passe identique à l'ancien (autorisé)
Étant donné un utilisateur veut changer son mot de passe
Et l'utilisateur saisit le nouveau mot de passe identique à l'ancien
Quand le système valide le mot de passe
Alors la validation doit réussir
Car il n'y a pas de règle interdisant la réutilisation
# ============================================================================
# MESSAGES D'AIDE ET UX
# ============================================================================
Scénario: Affichage des règles avant saisie
Étant donné l'utilisateur accède au formulaire d'inscription
Quand le champ mot de passe reçoit le focus
Alors une info-bulle doit s'afficher avec les règles :
"""
Votre mot de passe doit contenir :
Au moins 8 caractères
Au moins 1 majuscule
Au moins 1 chiffre
"""
Scénario: Indicateur de force du mot de passe
Étant donné l'utilisateur saisit progressivement son mot de passe
Quand l'utilisateur tape "Weak1"
Alors l'indicateur de force doit afficher "Faible" en orange
Quand l'utilisateur tape "Medium12"
Alors l'indicateur de force doit afficher "Moyen" en jaune
Quand l'utilisateur tape "VeryStrong123!"
Alors l'indicateur de force doit afficher "Fort" en vert

View File

@@ -0,0 +1,79 @@
# language: fr
Fonctionnalité: Vérification d'email
En tant qu'utilisateur inscrit
Je veux vérifier mon adresse email
Afin d'accéder à toutes les fonctionnalités selon mon rôle
Contexte:
Étant donné que l'API RoadWave est disponible
Scénario: Auditeur avec email non vérifié - lecture illimitée
Étant donné que je suis un auditeur avec email non vérifié
Quand j'essaie d'écouter du contenu
Alors je peux écouter tous les contenus sans limite
Scénario: Auditeur avec email non vérifié - création limitée à 5 contenus
Étant donné que je suis un auditeur avec email non vérifié
Et que j'ai créé 4 contenus
Quand je crée un 5ème contenu
Alors le contenu est créé avec succès
Mais quand j'essaie de créer un 6ème contenu
Alors la création échoue
Et je vois le message "Vérifiez votre email pour créer plus de contenus"
Scénario: Rappel de vérification après le 3ème contenu créé
Étant donné que je suis un auditeur avec email non vérifié
Et que j'ai créé 2 contenus
Quand je crée mon 3ème contenu
Alors le contenu est créé avec succès
Et je vois une notification in-app "Vérifiez votre email pour débloquer la création illimitée"
Scénario: Auditeur vérifie son email
Étant donné que je suis un auditeur avec email non vérifié
Et que j'ai reçu un lien de vérification
Quand je clique sur le lien de vérification dans l'email
Alors mon email est marqué comme vérifié
Et je vois le message "Email vérifié avec succès"
Et toutes les fonctionnalités sont débloquées
Scénario: Créateur doit vérifier son email sous 7 jours pour monétisation
Étant donné que je suis inscrit comme créateur
Et que mon email n'est pas vérifié
Et que je remplis les conditions de monétisation
Quand j'essaie d'accéder au programme de monétisation
Alors l'accès est refusé
Et je vois le message "Vérifiez votre email pour accéder à la monétisation"
Scénario: Créateur ne peut pas publier de contenus illimités sans vérification
Étant donné que je suis un créateur avec email non vérifié
Et que j'ai créé 5 contenus
Quand j'essaie de créer un 6ème contenu
Alors la création échoue
Et je vois le message "Vérifiez votre email pour publier des contenus illimités"
Scénario: Créateur vérifie son email et déboque tout
Étant donné que je suis un créateur avec email non vérifié
Et que j'ai reçu un lien de vérification
Quand je clique sur le lien de vérification
Alors mon email est marqué comme vérifié
Et je peux publier des contenus illimités
Et je peux accéder au programme de monétisation si j'en remplis les conditions
Scénario: KYC impossible sans email vérifié
Étant donné que je suis un créateur avec email non vérifié
Quand j'essaie de compléter le KYC via Mangopay
Alors l'accès au KYC est refusé
Et je vois le message "Vérifiez votre email avant de procéder au KYC"
Scénario: Tentative de vérification avec un lien déjà utilisé
Étant donné que j'ai déjà vérifié mon email avec un lien
Quand j'essaie de réutiliser le même lien de vérification
Alors la vérification échoue
Et je vois le message "Ce lien a déjà été utilisé"
Scénario: Auditeur vérifié peut créer plus de 5 contenus
Étant donné que je suis un auditeur avec email vérifié
Et que j'ai créé 10 contenus
Quand je crée un 11ème contenu
Alors le contenu est créé avec succès
Et il n'y a pas de limite de création

View File

@@ -0,0 +1,115 @@
# language: fr
@error-handling @no-content
Fonctionnalité: Élargissement automatique de zone quand aucun contenu n'est disponible
Contexte:
Étant donné que je suis un utilisateur connecté
Et que la géolocalisation est activée
Et que je suis en mode écoute
# 12.1 - Élargissement progressif
Scénario: Aucun contenu dans rayon 50km - élargissement à 100km
Étant donné que je suis situé à la position GPS 48.8566, 2.3522
Et qu'aucun contenu n'existe dans un rayon de 50 km autour de ma position
Mais qu'au moins 1 contenu existe dans un rayon de 100 km
Quand le système recherche du contenu à me proposer
Alors le système élargit automatiquement la zone de recherche à 100 km
Et je reçois un message "Aucun contenu dans votre zone immédiate. Voici du contenu à proximité (100 km)"
Et un contenu dans le rayon de 100 km m'est proposé
Scénario: Aucun contenu dans rayon 100km - élargissement au département
Étant donné que je suis situé dans le département "75" (Paris)
Et qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position
Mais qu'au moins 1 contenu existe avec la zone "département" pour "75"
Quand le système recherche du contenu à me proposer
Alors le système élargit automatiquement la zone de recherche au département
Et je reçois un message "Aucun contenu local disponible. Voici du contenu dans votre département"
Et un contenu départemental m'est proposé
Scénario: Aucun contenu départemental - élargissement à la région
Étant donné que je suis situé dans la région "Île-de-France"
Et qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position
Et qu'aucun contenu départemental n'existe pour mon département
Mais qu'au moins 1 contenu existe avec la zone "région" pour "Île-de-France"
Quand le système recherche du contenu à me proposer
Alors le système élargit automatiquement la zone de recherche à la région
Et je reçois un message "Aucun contenu local disponible. Voici du contenu dans votre région"
Et un contenu régional m'est proposé
Scénario: Aucun contenu régional - basculement sur contenu national
Étant donné que je suis situé en France
Et qu'aucun contenu n'existe dans un rayon de 100 km autour de ma position
Et qu'aucun contenu départemental n'existe pour mon département
Et qu'aucun contenu régional n'existe pour ma région
Quand le système recherche du contenu à me proposer
Alors le système bascule automatiquement sur du contenu national
Et je reçois un message "Aucun contenu local disponible. Voici du contenu national qui pourrait vous intéresser"
Et un contenu national m'est proposé
Et je ne reste jamais sans contenu disponible
Scénario: Élargissement progressif avec plusieurs étapes
Étant donné que je suis situé dans une zone rurale isolée
Et qu'aucun contenu n'existe dans un rayon de 50 km
Et qu'aucun contenu n'existe dans un rayon de 100 km
Et qu'aucun contenu départemental n'existe
Et qu'aucun contenu régional n'existe
Quand le système recherche du contenu à me proposer
Alors le système essaie d'abord 50 km
Puis essaie 100 km
Puis essaie le département
Puis essaie la région
Puis bascule sur le contenu national
Et tout ce processus se fait de manière transparente et automatique
Et je reçois le message correspondant au dernier niveau trouvé
# Messages adaptatifs selon le niveau d'élargissement
Scénario: Message personnalisé selon la distance trouvée
Étant donné que je suis situé à la position GPS 43.6047, 1.4442
Et que <niveau_geo> contenu(s) est/sont trouvé(s)
Quand le système me propose du contenu
Alors je reçois le message "<message_attendu>"
Exemples:
| niveau_geo | message_attendu |
| 100 km | Aucun contenu dans votre zone immédiate. Voici du contenu à proximité (100 km) |
| département | Aucun contenu local disponible. Voici du contenu dans votre département |
| région | Aucun contenu local disponible. Voici du contenu dans votre région |
| national | Aucun contenu local disponible. Voici du contenu national qui pourrait vous intéresser|
# Filet de sécurité - toujours du contenu disponible
Scénario: Le contenu national sert de filet de sécurité
Étant donné que le système a épuisé toutes les zones géographiques locales
Quand le système bascule sur du contenu national
Alors je dois toujours avoir au moins 1 contenu disponible
Et ce contenu peut être:
| type_contenu |
| Actualités Le Monde |
| Podcasts génériques |
| Contenu éducatif national |
| Contenu culturel national |
# Pas de message d'erreur bloquant
Scénario: Pas d'écran d'erreur "Aucun contenu"
Étant donné que je lance l'application
Et qu'aucun contenu local n'est disponible dans ma zone
Quand le système recherche du contenu
Alors je ne dois jamais voir un message d'erreur "Aucun contenu disponible"
Et je ne dois jamais voir un écran vide
Et un contenu doit toujours m'être proposé, même si c'est du contenu national
# Comportement avec historique d'écoute
Scénario: Élargissement avec prise en compte des centres d'intérêt
Étant donné que je suis situé dans une zone rurale
Et qu'aucun contenu n'existe dans un rayon de 50 km
Et que mes centres d'intérêt incluent "Automobile" à 80% et "Voyage" à 70%
Et qu'un contenu national existe avec le tag "Automobile"
Et qu'un contenu national existe avec le tag "Politique"
Quand le système bascule sur du contenu national
Alors le contenu national proposé prend en compte mes centres d'intérêt
Et le contenu "Automobile" a un score supérieur au contenu "Politique"

View File

@@ -0,0 +1,116 @@
# language: fr
@error-handling @content-removal
Fonctionnalité: Gestion d'un contenu supprimé pendant l'écoute
Contexte:
Étant donné que je suis un utilisateur connecté
Et que je suis en mode écoute
Et qu'un contenu "C123" est en cours de lecture
# 12.2 - Pas d'interruption brutale
Scénario: Contenu supprimé pendant lecture - fin de lecture sans interruption
Étant donné que j'écoute le contenu "C123" depuis 30 secondes
Et que la durée totale du contenu est de 120 secondes
Quand le contenu est supprimé par la modération côté backend
Alors la lecture du contenu continue sans interruption
Et je peux écouter le contenu jusqu'à la fin
Et aucune interruption brutale ne se produit
Scénario: Passage automatique après fin du contenu supprimé
Étant donné que le contenu "C123" a été supprimé pendant ma lecture
Et que j'ai écouté le contenu jusqu'à la fin
Quand le contenu se termine
Alors le système attend 2 secondes
Et passe automatiquement au contenu suivant
Et je reçois une notification toast discrète "Contenu précédent retiré (violation règles)"
Scénario: Bouton Précédent désactivé après suppression
Étant donné que le contenu "C123" a été supprimé pendant ma lecture
Et que je suis passé au contenu suivant "C456"
Quand j'essaie d'appuyer sur le bouton "Précédent"
Alors le bouton "Précédent" ne me ramène pas au contenu supprimé
Et je reçois un message "Ce contenu n'est plus disponible"
Et la lecture du contenu actuel "C456" continue
Scénario: Tentative de retour manuel au contenu supprimé
Étant donné que je suis sur le contenu "C456"
Et que le contenu précédent "C123" a été supprimé
Quand j'appuie sur le bouton "Précédent" pour revenir au contenu supprimé
Alors je reçois un message "Ce contenu n'est plus disponible"
Et la lecture reste sur le contenu actuel "C456"
Et aucune action n'est effectuée
# Sécurité routière - pas d'alerte intrusive
Scénario: Notification discrète pendant la conduite
Étant donné que je conduis à une vitesse de 60 km/h
Et que le contenu "C123" est supprimé pendant ma lecture
Quand le contenu se termine
Alors la notification "Contenu précédent retiré (violation règles)" s'affiche en toast discret
Et la notification disparaît automatiquement après 5 secondes
Et aucune popup modale n'interrompt ma conduite
Et le contenu suivant démarre automatiquement après 2 secondes
Scénario: Message informatif mais non alarmiste
Étant donné que le contenu "C123" a été supprimé
Et que je passe au contenu suivant
Quand la notification s'affiche
Alors le message doit être informatif: "Contenu précédent retiré (violation règles)"
Et le ton ne doit pas être alarmiste
Et le message doit être bref et compréhensible
Et aucun détail technique n'est affiché pendant la conduite
# Empêche la réécoute du contenu modéré
Scénario: Contenu supprimé retiré de l'historique
Étant donné que le contenu "C123" a été supprimé
Quand je consulte mon historique d'écoute
Alors le contenu "C123" n'apparaît plus dans mon historique
Et je ne peux pas relancer la lecture de ce contenu
Et l'historique affiche "[Contenu retiré]" à la place du titre
Scénario: Contenu supprimé non accessible via lien direct
Étant donné que le contenu "C123" a été supprimé
Et que j'ai un lien de partage "roadwave.fr/share/c/C123"
Quand je clique sur le lien de partage
Alors je reçois un message "Ce contenu a été retiré pour violation des règles de la communauté"
Et je suis redirigé vers l'accueil de l'application
Et aucune lecture n'est possible
# Gestion de plusieurs contenus supprimés consécutivement
Scénario: Plusieurs contenus supprimés dans l'historique récent
Étant donné que j'ai écouté les contenus suivants:
| id | statut |
| C123 | supprimé |
| C456 | actif |
| C789 | supprimé |
Et que je suis actuellement sur le contenu "C456"
Quand j'appuie plusieurs fois sur "Précédent"
Alors je ne peux pas revenir aux contenus "C123" ou "C789"
Et le système saute automatiquement les contenus supprimés
Et je reviens au dernier contenu actif disponible avant "C456"
# Comportement à l'arrêt du véhicule
Scénario: Consultation détaillée du contenu supprimé à l'arrêt
Étant donné que je suis à l'arrêt
Et que le contenu "C123" a été supprimé pendant ma session
Quand j'ouvre les détails de la notification de suppression
Alors je peux voir les informations suivantes:
| information |
| Titre du contenu |
| Créateur |
| Raison de suppression |
| Date de suppression |
Et je peux signaler une erreur de modération si je pense qu'elle est injustifiée
Scénario: Pas d'impact sur les jauges d'intérêt lors de la suppression
Étant donné que j'ai écouté le contenu "C123" pendant 80 secondes (66%)
Et que mes jauges d'intérêt ont été mises à jour pendant l'écoute
Quand le contenu est supprimé après mon écoute
Alors les modifications de mes jauges d'intérêt sont conservées
Et l'écoute déjà effectuée reste comptabilisée
Et seules les futures écoutes de ce contenu sont bloquées

View File

@@ -0,0 +1,210 @@
# language: fr
@error-handling @gps-disabled
Fonctionnalité: Mode dégradé sans géolocalisation
Contexte:
Étant donné que je suis un utilisateur connecté
Et que j'ai refusé ou désactivé l'accès à la géolocalisation
# 12.4 - Mode dégradé automatique
Scénario: Types de contenu disponibles sans géolocalisation
Étant donné que la géolocalisation est désactivée
Quand j'ouvre l'application
Alors les types de contenu suivants sont disponibles:
| type_contenu | disponible |
| Contenu national | oui |
| Contenu téléchargé (offline) | oui |
| Contenus "Neutre" géographiquement | oui |
| Contenu géolocalisé Ancré | non |
| Contenu géolocalisé Contextuel | non |
| Audio-guides | non |
| Notifications push géo-déclenchées | non |
# Popup au premier lancement
Scénario: Popup d'information au premier lancement sans GPS
Étant donné que c'est mon premier lancement de l'application
Et que j'ai refusé l'accès à la géolocalisation
Quand l'application détecte que le GPS est désactivé
Alors une popup s'affiche avec le message:
"""
RoadWave fonctionne mieux avec la géolocalisation activée. Sans elle, seul le contenu national sera disponible.
"""
Et la popup contient les boutons suivants:
| bouton | action |
| Activer | Redirection vers paramètres OS |
| Continuer sans | Ferme popup et lance en mode dégradé |
Et une checkbox "Ne plus me demander" est disponible
Scénario: Popup non affichée si case "Ne plus me demander" cochée
Étant donné que j'ai déjà vu la popup de géolocalisation
Et que j'ai coché "Ne plus me demander"
Quand je lance l'application avec le GPS désactivé
Alors la popup de géolocalisation ne s'affiche pas
Et l'application démarre directement en mode dégradé
Et le banner permanent de rappel s'affiche
Scénario: Redirection vers paramètres OS lors du clic sur "Activer"
Étant donné que la popup de géolocalisation est affichée
Quand je clique sur "Activer"
Alors je suis redirigé vers les paramètres de géolocalisation de mon OS
Et sur iOS, j'arrive dans "Réglages > RoadWave > Localisation"
Et sur Android, j'arrive dans "Paramètres > Applications > RoadWave > Autorisations > Position"
# Banner permanent en mode dégradé
Scénario: Banner de rappel permanent sans GPS
Étant donné que j'ai cliqué sur "Continuer sans" géolocalisation
Quand l'application s'affiche
Alors un bandeau s'affiche en haut de l'écran
Et le bandeau contient le texte: "Mode limité : géolocalisation désactivée. [Activer]"
Et le bandeau a un fond de couleur avertissement (jaune/orange)
Et le bandeau n'est pas intrusif mais reste visible
Et le bandeau reste affiché sur toutes les pages de l'application
Scénario: Clic sur le bouton "Activer" du banner
Étant donné que le banner "Mode limité" est affiché
Quand je clique sur le lien "[Activer]" dans le banner
Alors je suis redirigé vers les paramètres de géolocalisation de mon OS
Scénario: Disparition du banner après activation GPS
Étant donné que le banner "Mode limité" est affiché
Et que je reviens dans l'application après avoir activé le GPS dans les paramètres
Quand l'application détecte que la géolocalisation est maintenant active
Alors le banner disparaît automatiquement
Et l'application bascule en mode normal avec contenu géolocalisé
Et un toast de confirmation s'affiche: "Géolocalisation activée"
# Contenu disponible en mode dégradé
Scénario: Lecture de contenu national sans GPS
Étant donné que la géolocalisation est désactivée
Et que du contenu national existe (actualités Le Monde, podcasts génériques)
Quand je lance la lecture
Alors je peux écouter le contenu national sans restriction
Et l'algorithme de recommandation se base uniquement sur:
| critère |
| Mes centres d'intérêt |
| Mon historique d'écoute |
| Popularité générale |
Et la proximité géographique n'est pas prise en compte
Scénario: Lecture de contenu téléchargé sans GPS
Étant donné que la géolocalisation est désactivée
Et que j'ai téléchargé 30 contenus quand j'avais le GPS activé
Quand j'accède à mes contenus téléchargés
Alors je peux lire tous mes contenus téléchargés normalement
Et les contenus géolocalisés téléchargés restent accessibles
Et le filtre géographique n'est pas appliqué pour les contenus offline
Scénario: Contenu "Neutre" géographiquement disponible
Étant donné que la géolocalisation est désactivée
Et qu'un créateur a publié du contenu avec la classification géographique "Neutre"
Quand je recherche du contenu
Alors les contenus "Neutre" sont inclus dans les résultats
Et ils sont mélangés avec le contenu national
Et l'algorithme les priorise selon mes centres d'intérêt
# Restrictions de contenu sans GPS
Scénario: Audio-guides inaccessibles sans GPS
Étant donné que la géolocalisation est désactivée
Quand je recherche un audio-guide spécifique
Alors les audio-guides apparaissent dans les résultats de recherche
Mais un badge "GPS requis" est affiché sur chaque audio-guide
Et quand je clique sur un audio-guide, un message s'affiche:
"""
Les audio-guides nécessitent la géolocalisation pour fonctionner. Voulez-vous activer le GPS ?
"""
Et je peux choisir "Activer" ou "Annuler"
Scénario: Notifications push géo-déclenchées désactivées
Étant donné que la géolocalisation est désactivée
Et que je suis abonné à un créateur qui diffuse du contenu géolocalisé
Quand le créateur publie un nouveau contenu géolocalisé
Alors je ne reçois pas de notification push géo-déclenchée
Mais je reçois une notification push standard (non géo-déclenchée) si le créateur publie du contenu national
Et la notification précise: "Nouveau contenu national de [Créateur]"
Scénario: Contenu géolocalisé non proposé dans le feed
Étant donné que la géolocalisation est désactivée
Quand le système génère mon feed de contenu
Alors aucun contenu "Ancré" ou "Contextuel" n'est inclus
Et seuls les contenus "Neutre" et "National" sont proposés
Et mon feed contient au minimum 20 contenus disponibles
# RGPD: Respect du consentement libre
Scénario: Application fonctionnelle sans GPS (pas de blocage)
Étant donné que la géolocalisation est désactivée
Quand j'utilise l'application
Alors je ne suis jamais bloqué par un écran "GPS requis"
Et toutes les fonctionnalités non-géolocalisées restent accessibles:
| fonctionnalité |
| Écoute contenu national |
| Gestion profil |
| Abonnements créateurs |
| Recherche textuelle |
| Historique d'écoute |
| Paramètres |
| Mode offline |
Et je peux créer et publier du contenu national
Scénario: Respect du choix utilisateur de ne pas activer GPS
Étant donné que j'ai coché "Ne plus me demander" pour la géolocalisation
Quand j'utilise l'application pendant plusieurs semaines
Alors la popup de demande GPS ne s'affiche plus jamais automatiquement
Et seul le banner permanent reste affiché
Et l'application ne force jamais l'activation du GPS
# Réactivation GPS et bascule en mode normal
Scénario: Bascule automatique en mode normal après activation GPS
Étant donné que j'utilise l'application en mode dégradé depuis 1 semaine
Et que je décide d'activer la géolocalisation
Quand l'application détecte que le GPS est maintenant actif
Alors le mode dégradé est désactivé automatiquement
Et le banner "Mode limité" disparaît
Et le contenu géolocalisé devient disponible immédiatement
Et mon feed se rafraîchit avec du contenu local pertinent
Et un toast de confirmation s'affiche: "Géolocalisation activée - Contenu local disponible"
Scénario: Demande de permission GPS lors de l'utilisation d'une fonctionnalité géo
Étant donné que la géolocalisation est désactivée
Quand j'essaie d'accéder à une fonctionnalité nécessitant le GPS (ex: audio-guide)
Alors une popup contextuelle s'affiche:
"""
Cette fonctionnalité nécessite la géolocalisation. Voulez-vous l'activer ?
"""
Et je peux accepter ou refuser
Et si j'accepte, je suis redirigé vers les paramètres OS
Et si je refuse, je reste en mode dégradé sans message d'erreur répétitif
# Incitation à activer GPS sans être intrusif
Scénario: Statistiques de contenu local disponible non affiché
Étant donné que la géolocalisation est désactivée
Quand je navigue dans l'application
Alors le banner peut afficher occasionnellement:
"""
Mode limité : géolocalisation désactivée. Plus de 500 contenus locaux disponibles près de vous. [Activer]
"""
Et ce message incitatif change tous les 3 jours
Et il reste non intrusif (pas de popup, juste le banner)
Scénario: Onboarding différent pour utilisateurs sans GPS
Étant donné que c'est ma première utilisation de RoadWave
Et que j'ai refusé la géolocalisation
Quand l'onboarding se termine
Alors un écran explicatif s'affiche:
"""
Vous utilisez RoadWave sans géolocalisation. Voici ce qui est disponible :
- Contenu national (actualités, podcasts)
- Contenus téléchargés
- Tous les créateurs et abonnements
Pour profiter pleinement de RoadWave (contenu local, audio-guides), activez la géolocalisation à tout moment.
"""
Et je peux continuer avec un bouton "Compris"

View File

@@ -0,0 +1,179 @@
# language: fr
@error-handling @network-loss
Fonctionnalité: Gestion de la perte de réseau et buffering adaptatif
Contexte:
Étant donné que je suis un utilisateur connecté
Et que je suis en mode écoute
Et qu'un contenu est en cours de lecture
# 12.3 - Buffer adaptatif selon type de réseau (cf. TECHNICAL.md et ADR-002)
Plan du Scénario: Paramètres de buffer selon le type de réseau
Étant donné que je suis connecté en "<type_reseau>"
Quand le système initialise le buffer audio
Alors le buffer minimum est de <buffer_min> secondes
Et le buffer cible est de <buffer_cible> secondes
Et le buffer maximum est de <buffer_max> secondes
Exemples:
| type_reseau | buffer_min | buffer_cible | buffer_max |
| WiFi | 5 | 30 | 120 |
| 4G | 10 | 45 | 120 |
| 5G | 10 | 45 | 120 |
| 3G | 30 | 90 | 300 |
# Phase 1: Connexion instable
Scénario: Connexion instable avec latence élevée - aucun message immédiat
Étant donné que je suis connecté en 4G
Et que le buffer contient 45 secondes de contenu
Quand la latence réseau dépasse 500ms
Alors aucun message n'est affiché immédiatement
Et la lecture continue normalement sur le buffer
Et le système tente de continuer le téléchargement en arrière-plan
Scénario: Connexion instable pendant plus de 10 secondes - toast discret
Étant donné que je suis connecté en 4G
Et que la latence réseau dépasse 500ms depuis 10 secondes
Quand le système détecte la latence prolongée
Alors un toast discret s'affiche: "Connexion instable"
Et le toast disparaît automatiquement après 3 secondes
Et la lecture continue normalement
# Phase 2: Perte totale réseau
Scénario: Perte totale de réseau - lecture sur buffer
Étant donné que je suis connecté en WiFi
Et que le buffer contient 30 secondes de contenu
Quand je perds totalement la connexion réseau
Alors la lecture continue sur le buffer disponible
Et un toast s'affiche: "Hors ligne, lecture sur buffer (30s restantes)"
Et un compte à rebours du temps de buffer restant est visible
Scénario: Buffer qui s'épuise pendant la perte réseau
Étant donné que je suis hors ligne
Et que le buffer contient 30 secondes de contenu
Quand le contenu continue de jouer
Alors le compte à rebours diminue en temps réel
Et le toast affiche "Hors ligne, lecture sur buffer (15s restantes)" après 15 secondes
Et le toast affiche "Hors ligne, lecture sur buffer (5s restantes)" après 25 secondes
# Phase 3: Buffer épuisé sans reconnexion
Scénario: Pause automatique après épuisement du buffer
Étant donné que je suis hors ligne depuis 30 secondes
Et que le buffer est complètement épuisé
Quand il n'y a plus de contenu audio à lire
Alors la lecture se met en pause automatiquement
Et un overlay s'affiche: "Connexion perdue. Reconnexion en cours..."
Et le système tente de se reconnecter automatiquement
Scénario: Tentatives de reconnexion automatique
Étant donné que la lecture est en pause suite à l'épuisement du buffer
Quand le système tente de se reconnecter
Alors une tentative de reconnexion est effectuée toutes les 5 secondes
Et un maximum de 6 tentatives sont effectuées (30 secondes au total)
Et l'overlay affiche "Tentative de reconnexion... (X/6)"
# Phase 4: Basculement mode offline après échec reconnexion
Scénario: Proposition du mode offline après 30 secondes d'échec
Étant donné que 6 tentatives de reconnexion ont échoué
Et que cela fait 30 secondes que je suis déconnecté
Quand la 6ème tentative échoue
Alors une popup s'affiche: "Voulez-vous continuer avec vos contenus téléchargés ?"
Et la popup contient deux boutons:
| bouton | action |
| Réessayer | Nouvelle série de 6 tentatives |
| Mode offline | Bascule sur contenus téléchargés |
Scénario: Basculement réussi vers le mode offline
Étant donné que la popup de mode offline est affichée
Et que j'ai téléchargé 20 contenus dans ma zone géographique
Quand je clique sur "Mode offline"
Alors le système bascule sur les contenus téléchargés
Et un nouveau contenu téléchargé démarre automatiquement
Et un bandeau permanent indique "Mode hors ligne - Contenus téléchargés"
Scénario: Aucun contenu téléchargé disponible
Étant donné que la popup de mode offline est affichée
Et que je n'ai aucun contenu téléchargé
Quand je clique sur "Mode offline"
Alors un message s'affiche: "Aucun contenu téléchargé disponible"
Et je suis invité à me connecter en WiFi pour télécharger du contenu
Et le bouton "Réessayer" reste la seule option
# Reconnexion réussie
Scénario: Reprise automatique après reconnexion
Étant donné que la lecture est en pause depuis 15 secondes
Et que j'étais à 02:35 du contenu en cours
Quand la connexion réseau est rétablie
Alors la lecture reprend automatiquement au point d'arrêt exact (02:35)
Et un toast s'affiche: "Connexion rétablie"
Et le toast disparaît après 3 secondes
Et le buffer se remplit progressivement selon le type de réseau
Scénario: Reconnexion avec changement de type de réseau
Étant donné que j'étais connecté en WiFi
Et que j'ai perdu la connexion
Quand je me reconnecte en 4G
Alors le système ajuste automatiquement les paramètres de buffer
Et le buffer minimum passe de 5s à 10s
Et le buffer cible passe de 30s à 45s
Et la lecture reprend normalement
# Cas spécifique: tunnel routier
Scénario: Passage dans un tunnel avec perte de signal
Étant donné que je conduis à 90 km/h sur autoroute
Et que je suis connecté en 4G avec un buffer de 45 secondes
Quand j'entre dans un tunnel et perds le signal
Alors la lecture continue sur le buffer pendant 45 secondes maximum
Et aucune notification n'est affichée pendant les 10 premières secondes
Et un toast discret s'affiche après 10 secondes: "Connexion instable"
Scénario: Sortie du tunnel avant épuisement du buffer
Étant donné que je suis dans un tunnel depuis 30 secondes
Et qu'il reste 15 secondes de buffer
Quand je sors du tunnel et récupère le signal 4G
Alors la lecture continue sans interruption
Et le buffer se remplit à nouveau
Et un toast s'affiche: "Connexion rétablie"
# Handoff réseau (changement de cellule mobile)
Scénario: Changement de cellule 4G pendant la lecture
Étant donné que je conduis et change de cellule mobile toutes les 5-10 minutes
Et que le buffer contient 45 secondes de contenu
Quand un handoff de cellule se produit
Alors la lecture continue sans interruption grâce au buffer
Et la connexion à la nouvelle cellule se fait de manière transparente
Et aucune notification n'est affichée si le handoff réussit en moins de 5 secondes
# Mode offline préventif avant perte réseau
Scénario: Téléchargement préventif en WiFi avant trajet
Étant donné que je suis connecté en WiFi
Et que j'ai activé le téléchargement automatique
Quand le système détecte que je suis à l'arrêt en WiFi
Alors le système me propose de télécharger du contenu pour mon trajet
Et je peux sélectionner une zone géographique à télécharger
Et le téléchargement se fait en arrière-plan
# Métriques et monitoring
Scénario: Tracking des événements de perte réseau pour amélioration
Étant donné que je perds la connexion réseau
Quand l'événement de perte est détecté
Alors le système enregistre les métriques suivantes:
| métrique |
| Type de réseau avant perte |
| Durée de la coupure |
| Buffer disponible |
| Position GPS approximative |
| Heure de la journée |
Et ces métriques sont anonymisées et envoyées en batch lors de la prochaine connexion WiFi
Et les données servent à améliorer les paramètres de buffer

View File

@@ -0,0 +1,67 @@
# language: fr
@ui @sharing @premium @viral @mvp
Fonctionnalité: Partage de contenu Premium pour viralité
En tant qu'utilisateur Premium
Je veux partager mes découvertes
Afin de recommander la plateforme à mes amis
Scénario: Partage d'un audio-guide avec preview
Étant donné un utilisateur "alice@roadwave.fr" Premium
Quand elle partage l'audio-guide "Visite du Louvre"
Alors un lien unique est généré: roadwave.fr/share/abc123
Et le lien affiche une preview attractive:
| Élément | Contenu |
| Image cover | Photo du Louvre |
| Titre | Visite du Louvre |
| Description | Découvrez 3000 ans d'art... |
| Durée | 2h 30min - 12 séquences |
| Note | 4.8/5 (1,234 avis) |
| Créateur | @MuseeDuLouvre |
| CTA | [Écouter gratuitement] |
Et un événement "CONTENT_SHARED" est enregistré
Scénario: Essai gratuit de 3 jours pour contenu partagé
Étant donné un utilisateur Free qui clique sur un lien partagé
Quand il consulte un contenu Premium
Alors une offre s'affiche: "Essai gratuit 3 jours offerts par votre ami"
Et il peut écouter le contenu sans payer
Et un événement "FREE_TRIAL_FROM_SHARE" est enregistré
Scénario: Programme de parrainage avec récompenses
Étant donné un utilisateur Premium qui partage
Quand 3 amis s'abonnent via son lien
Alors il reçoit 1 mois gratuit par ami converti
Et un badge "Ambassadeur" s'affiche sur son profil
Et un événement "REFERRAL_REWARDS_GRANTED" est enregistré
Scénario: Statistiques de partage
Étant donné un utilisateur "bob@roadwave.fr"
Quand il consulte ses statistiques de partage
Alors il voit:
| Métrique | Valeur |
| Contenus partagés | 12 |
| Clics sur liens | 45 |
| Amis convertis | 3 |
| Mois gratuits gagnés | 3 |
Et un événement "SHARE_STATS_VIEWED" est enregistré
Scénario: Partage optimisé pour réseaux sociaux
Étant donné un lien partagé sur Facebook
Alors les Open Graph tags sont optimisés:
| Tag | Valeur |
| og:title | Visite du Louvre - RoadWave |
| og:image | Image haute résolution |
| og:description| Description accrocheuse |
Et génère un maximum d'engagement
Et un événement "SOCIAL_SHARE_OPTIMIZED" est enregistré
Scénario: Métriques de viralité
Étant donné 1000 partages effectués
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur |
| Taux de clic sur partage | 18% |
| Taux de conversion | 12% |
| K-factor (viralité) | 1.3 |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,206 @@
# language: fr
Fonctionnalité: Partage de contenu
En tant qu'utilisateur de RoadWave
Je veux pouvoir partager du contenu audio
Afin de faire découvrir l'application à d'autres personnes
Contexte:
Étant donné que l'application RoadWave est démarrée
Et que l'utilisateur "jean@example.com" est connecté
# 15.1.1 - Bouton "Partager"
Scénario: Bouton partager disponible dans le player en lecture
Étant donné que le contenu "Balade à Paris" est en cours de lecture
Quand l'utilisateur consulte les contrôles du player
Alors le bouton "Partager" est visible
Scénario: Bouton partager disponible sur la page profil créateur
Étant donné que l'utilisateur consulte le profil de "@paris_stories"
Quand l'utilisateur consulte un contenu dans la liste
Alors le bouton "Partager" est disponible pour chaque contenu
Scénario: Bouton partager dans la liste de recherche
Étant donné que l'utilisateur effectue une recherche "voyage paris"
Quand l'utilisateur ouvre le menu contextuel d'un résultat
Alors l'option "Partager" est disponible
Scénario: Bouton partager dans l'historique personnel
Étant donné que l'utilisateur consulte son historique d'écoute
Quand l'utilisateur sélectionne un contenu de l'historique
Alors le bouton "Partager" est accessible
Plan du Scénario: Menu de partage avec options multiples
Étant donné que le contenu "<contenu>" est disponible
Quand l'utilisateur clique sur le bouton "Partager"
Alors le menu natif OS s'ouvre
Et les options suivantes sont disponibles:
| option |
| Copier le lien |
| WhatsApp |
| Email |
| SMS |
| Plus... |
Exemples:
| contenu |
| Balade à Paris |
| Secrets de Montmartre |
# 15.1.2 - Comportement du lien partagé
Scénario: Génération du lien de partage
Étant donné un contenu avec l'ID "content_12345"
Quand l'utilisateur copie le lien de partage
Alors le lien généré est "https://roadwave.fr/share/c/content_12345"
Scénario: Ouverture du lien partagé avec l'application installée (Deep link)
Étant donné que l'application RoadWave est installée sur l'appareil
Et qu'un lien "https://roadwave.fr/share/c/content_12345" est partagé
Quand l'utilisateur clique sur le lien
Alors l'application RoadWave s'ouvre automatiquement
Et le contenu "content_12345" commence à jouer
Scénario: Ouverture du lien partagé sans l'application installée (Web player)
Étant donné que l'application RoadWave n'est pas installée
Et qu'un lien "https://roadwave.fr/share/c/content_12345" est partagé
Quand l'utilisateur clique sur le lien
Alors une page web responsive s'affiche
Et le web player HTML5 est visible
Et les boutons de téléchargement App Store et Google Play sont affichés
Scénario: Contenu de la page web de partage
Étant donné un contenu public avec les métadonnées suivantes:
| champ | valeur |
| titre | Balade à Paris |
| créateur | @paris_stories |
| durée | 12 min |
| écoutes | 2300 |
| localisation | Paris 5e |
| type_geo | Ancré |
| tags | Voyage, Histoire |
Quand la page de partage est affichée
Alors la page contient:
| élément |
| Cover image 16:9 |
| Titre "Balade à Paris" |
| "@paris_stories" |
| "12 min · 🎧 2.3K" |
| "📍 Paris 5e · Ancré" |
| "🏷 #Voyage #Histoire" |
| Description |
| Player HTML5 |
| Bouton App Store |
| Bouton Google Play |
Scénario: Métadonnées Open Graph pour partage social
Étant donné un contenu "Balade à Paris" par "@paris_stories"
Quand la page de partage est générée
Alors les métadonnées Open Graph incluent:
| propriété | valeur |
| og:title | Balade à Paris - RoadWave |
| og:description | Écoutez ce contenu par @paris_stories |
| og:type | music.song |
| og:site_name | RoadWave |
| twitter:card | player |
Et l'aperçu s'affiche correctement sur WhatsApp
Et l'aperçu s'affiche correctement sur Facebook
Et l'aperçu s'affiche correctement sur Twitter
Plan du Scénario: Deep linking par plateforme
Étant donné que l'application RoadWave est installée sur <plateforme>
Et qu'un lien de partage est ouvert
Quand le système détecte l'application
Alors l'application s'ouvre via <mécanisme>
Exemples:
| plateforme | mécanisme |
| iOS | Universal Links |
| Android | App Links |
Scénario: Fallback URL scheme pour deep linking
Étant donné que les App Links ne fonctionnent pas
Quand le système tente d'ouvrir le contenu
Alors l'URL scheme "roadwave://content/content_12345" est utilisé
# 15.1.3 - Contenus Premium partagés
Scénario: Badge Premium visible sur le lien partagé
Étant donné un contenu Premium "Visite VIP Louvre"
Quand l'utilisateur non-premium clique sur le lien partagé
Alors la page web affiche le badge "👑 Contenu Premium"
Scénario: Preview 30 secondes d'un contenu Premium partagé
Étant donné un contenu Premium "Visite VIP Louvre" de 15 minutes
Et qu'un utilisateur non-premium ouvre le lien partagé
Quand le player démarre automatiquement
Alors l'audio joue pendant 30 secondes exactement
Et un fade out de 2 secondes est appliqué
Et un overlay "Contenu réservé Premium" s'affiche après 32 secondes
Scénario: Contenu de l'overlay paywall Premium
Étant donné qu'un contenu Premium a atteint la limite de 30 secondes
Quand l'overlay paywall s'affiche
Alors le texte suivant est visible:
"""
👑 Contenu réservé Premium
Profitez de ce contenu complet
et de milliers d'autres
sans publicité
[Passer Premium - 4.99/mois]
[Télécharger l'app]
"""
Scénario: Actions disponibles sur l'overlay Premium
Étant donné que l'overlay paywall Premium est affiché
Quand l'utilisateur consulte les options
Alors les actions suivantes sont disponibles:
| action | comportement |
| Passer Premium | Redirection vers paiement Mangopay web |
| Télécharger l'app | Redirection vers App Store/Google Play |
| Rejouer les 30 premières sec | Relecture illimitée du preview |
Scénario: Relecture illimitée du preview Premium
Étant donné un contenu Premium partagé
Et que l'utilisateur a écouté les 30 premières secondes
Quand l'utilisateur clique sur "Rejouer"
Alors les 30 premières secondes sont rejouées
Et cette action est possible de manière illimitée
Scénario: Tracking des partages Premium
Étant donné un créateur "@guide_louvre" avec un contenu Premium
Quand son contenu est partagé
Alors les métriques suivantes sont enregistrées:
| métrique | valeur |
| Partages Premium | +1 |
| Ouvertures lien | compteur |
| Conversions Premium | si souscription |
Scénario: Rémunération créateur sur conversion Premium via partage
Étant donné un contenu Premium partagé par "@guide_louvre"
Quand un utilisateur s'abonne via le lien partagé
Alors le créateur reçoit 70% des revenus de cet abonnement
Et la conversion est trackée dans son dashboard
# Cas d'erreur
Scénario: Partage d'un contenu supprimé
Étant donné qu'un lien de partage "https://roadwave.fr/share/c/deleted_content" est ouvert
Et que le contenu n'existe plus
Quand la page web se charge
Alors un message "Ce contenu n'est plus disponible" s'affiche
Et les boutons de téléchargement de l'app sont affichés
Scénario: Partage d'un contenu en attente de modération
Étant donné un contenu en cours de validation modération
Quand un lien de partage est ouvert
Alors le message "Ce contenu est en cours de validation" s'affiche
Scénario: Génération du lien hors connexion
Étant donné que l'utilisateur n'a pas de connexion réseau
Quand l'utilisateur tente de partager un contenu
Alors le lien est copié dans le presse-papiers
Et un message "Lien copié (nécessite connexion pour ouvrir)" s'affiche

View File

@@ -0,0 +1,90 @@
# language: fr
@api @profile @verification @mvp
Fonctionnalité: Badge compte vérifié pour créateurs authentiques
En tant que créateur officiel
Je veux obtenir un badge vérifié
Afin de prouver mon authenticité et gagner la confiance
Scénario: Demande de vérification par un créateur
Étant donné un créateur "MuseeDuLouvre" avec 1000+ abonnés
Quand il demande la vérification via "Paramètres > Demander la vérification"
Alors un formulaire de demande s'affiche:
| Champ requis | Exemple |
| Nom officiel | Musée du Louvre |
| Type d'organisation | Institution culturelle |
| Document officiel | KBIS / Statuts |
| Preuve d'identité | Carte d'identité |
| Site web officiel | louvre.fr |
| Compte social officiel | @MuseeLouvre (Twitter) |
Et la demande est soumise pour review
Et un événement "VERIFICATION_REQUEST_SUBMITTED" est enregistré
Scénario: Vérification par l'équipe RoadWave
Étant donné une demande de vérification reçue
Quand un modérateur examine le dossier
Alors il vérifie:
| Critère | Validation |
| Documents officiels | Authentiques |
| Correspondance identité | Confirmée |
| Site web officiel | Vérifié (DNS) |
| Réseaux sociaux | Cross-vérifiés |
| Activité sur RoadWave | Régulière (3+ mois) |
Et prend une décision dans les 7 jours
Et un événement "VERIFICATION_REVIEWED" est enregistré
Scénario: Attribution du badge vérifié
Étant donné une demande acceptée
Quand le badge est attribué
Alors un badge bleu " Vérifié" s'affiche:
| Emplacement | Affichage |
| À côté du nom de profil | Musée du Louvre |
| Dans les résultats | Badge visible |
| Dans les commentaires | Badge visible |
Et une notification est envoyée: "Félicitations ! Votre compte est maintenant vérifié"
Et un événement "VERIFICATION_BADGE_GRANTED" est enregistré
Scénario: Avantages du compte vérifié
Étant donné un créateur vérifié
Alors il bénéficie de:
| Avantage | Détail |
| Badge bleu visible | Crédibilité accrue |
| Priorité dans les recherches | Meilleur ranking SEO |
| Statistiques avancées | Analytics détaillées |
| Support prioritaire | Réponse < 24h |
| Contenu mis en avant | Page "Créateurs vérifiés" |
Et un événement "VERIFIED_BENEFITS_DISPLAYED" est enregistré
Scénario: Révocation du badge pour violation
Étant donné un créateur vérifié "InstitutionX"
Quand il viole les CGU (contenu inapproprié)
Alors le badge est révoqué immédiatement
Et un email explique la raison
Et il peut faire appel de la décision
Et un événement "VERIFICATION_BADGE_REVOKED" est enregistré
Scénario: Renouvellement annuel de la vérification
Étant donné un créateur vérifié depuis 12 mois
Quand l'anniversaire de la vérification arrive
Alors une review automatique est lancée
Et des documents à jour peuvent être demandés
Et le badge reste actif pendant la review
Et un événement "VERIFICATION_RENEWAL_STARTED" est enregistré
Scénario: Badge spécial pour partenaires officiels
Étant donné un partenaire stratégique (Offices du Tourisme, Musées nationaux)
Alors un badge or " Partenaire Officiel" est attribué
Et des privilèges supplémentaires sont accordés
Et un événement "OFFICIAL_PARTNER_BADGE_GRANTED" est enregistré
Scénario: Statistiques des comptes vérifiés
Étant donné que 150 comptes sont vérifiés
Alors les indicateurs suivants sont disponibles:
| Métrique | Valeur |
| Comptes vérifiés | 150 |
| % de la base créateurs | 1.5% |
| Demandes en attente | 45 |
| Taux d'acceptation | 65% |
| Temps moyen de vérification | 5 jours |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,293 @@
# language: fr
Fonctionnalité: Profil créateur
En tant qu'utilisateur de RoadWave
Je veux consulter les profils des créateurs
Afin de découvrir leur contenu et décider de m'abonner
Contexte:
Étant donné que l'application RoadWave est démarrée
# 15.2.1 - Structure de la page profil
Scénario: URL du profil créateur
Étant donné un créateur avec le pseudo "paris_stories"
Quand l'utilisateur accède au profil
Alors l'URL est "https://roadwave.fr/@paris_stories"
Scénario: Informations principales du profil
Étant donné un créateur "@paris_stories" avec les informations suivantes:
| champ | valeur |
| photo | avatar_120x120.jpg |
| pseudo | paris_stories |
| badge_vérifié | true |
| bio | Histoires et anecdotes de Paris |
| abonnés | 1200 |
| contenus | 42 |
| durée_totale | 18h |
| écoutes_totales | 54000 |
Quand le profil est affiché
Alors les éléments suivants sont visibles:
| élément | valeur affichée |
| Photo profil | 120×120 px |
| @pseudo | @paris_stories |
| Badge vérifié | |
| Bio | Histoires et... |
| Nombre abonnés | 1.2K abonnés |
| Nombre contenus | 42 contenus |
| Durée totale | 18h de contenu créé |
| Écoutes totales | 54K écoutes totales |
Plan du Scénario: Arrondi des statistiques publiques
Étant donné un créateur avec <valeur_exacte> <métrique>
Quand le profil est affiché
Alors la valeur affichée est "<valeur_affichée>"
Exemples:
| métrique | valeur_exacte | valeur_affichée |
| abonnés | 342 | 342 |
| abonnés | 1200 | 1.2K |
| abonnés | 54000 | 54K |
| abonnés | 1200000 | 1.2M |
| écoutes | 842 | 842 |
| écoutes | 5400 | 5.4K |
| écoutes | 142000 | 142K |
| écoutes | 2100000 | 2.1M |
| durée (heures) | 18 | 18h |
| durée (heures) | 142 | 142h |
Scénario: Bio avec markdown basique
Étant donné un créateur avec la bio suivante en markdown:
"""
**Histoires de Paris** - Découvrez la capitale
*Nouveau contenu chaque semaine*
https://paris-stories.fr
"""
Quand le profil est affiché
Alors le texte en gras "Histoires de Paris" est formaté
Et le texte en italique "Nouveau contenu chaque semaine" est formaté
Et le lien "https://paris-stories.fr" est cliquable
Scénario: Limitation de la bio à 300 caractères
Étant donné un créateur qui entre une bio de 350 caractères
Quand la bio est sauvegardée
Alors seuls les 300 premiers caractères sont conservés
Et un message "Maximum 300 caractères" s'affiche
Scénario: Boutons d'action principaux
Étant donné que l'utilisateur consulte un profil créateur
Quand la page est chargée
Alors les boutons suivants sont visibles:
| bouton | action |
| S'abonner | Abonnement au créateur |
| Partager profil | Menu de partage |
| | Menu contextuel |
Scénario: Menu contextuel du profil [•••]
Étant donné que l'utilisateur clique sur le bouton []
Quand le menu s'ouvre
Alors les options suivantes sont disponibles:
| option | description |
| Partager profil | Partager le lien du profil |
| Signaler profil | Signaler spam ou usurpation d'identité |
| Bloquer créateur | Masquer tous les contenus du créateur |
Scénario: Liste des contenus du créateur
Étant donné un créateur avec 3 contenus publiés
Quand le profil est affiché
Alors chaque contenu affiche:
| élément | exemple |
| Cover image | Image 16:9 |
| Titre | Balade à Paris |
| Durée et écoutes | 12 min · 🎧 2.3K |
| Localisation | 📍 Paris |
| Bouton lecture | |
Plan du Scénario: Options de tri des contenus
Étant donné un créateur avec 10 contenus publiés
Quand l'utilisateur sélectionne le tri "<option_tri>"
Alors les contenus sont triés par <critère>
Exemples:
| option_tri | critère |
| Plus récents | Date publication DESC (défaut) |
| Plus populaires | Écoutes × facteur temporel (90 jours) |
| Plus anciens | Date publication ASC |
Scénario: Filtrage des contenus par tag
Étant donné un créateur avec des contenus taggés "Voyage", "Histoire", "Gastronomie"
Quand l'utilisateur filtre par tags "Voyage, Histoire"
Alors seuls les contenus avec ces tags sont affichés
Et le nombre de résultats est indiqué "12 contenus"
Scénario: Recherche locale dans le profil
Étant donné que l'utilisateur consulte le profil de "@paris_stories"
Et que le créateur a publié 50 contenus
Quand l'utilisateur entre "Montmartre" dans la barre de recherche
Alors la recherche s'effectue sur les titres et descriptions
Et seuls les contenus correspondants sont affichés
Et le placeholder indique "Rechercher dans les contenus de @paris_stories"
Scénario: Chargement paginé des contenus
Étant donné un créateur avec 100 contenus publiés
Quand le profil est affiché
Alors 20 contenus sont chargés initialement
Et un bouton "Charger plus" est visible en bas de page
Quand l'utilisateur clique sur "Charger plus"
Alors 20 contenus supplémentaires sont chargés
# 15.2.2 - Statistiques publiques
Scénario: Informations publiques visibles par tous
Étant donné que l'utilisateur consulte un profil créateur
Alors les informations suivantes sont publiques:
| information | visible |
| Photo et pseudo | |
| Badge vérifié | |
| Bio | |
| Nombre abonnés | |
| Nombre contenus | |
| Durée totale créée | |
| Écoutes totales | |
Scénario: Informations privées non visibles
Étant donné que l'utilisateur consulte un profil créateur
Alors les informations suivantes sont privées:
| information | visible |
| Liste des abonnés | |
| Revenus | |
| Localisation précise | |
| Email | |
Scénario: Dashboard créateur avec métriques privées
Étant donné que le créateur "@paris_stories" consulte son propre dashboard
Quand la page statistiques est affichée
Alors les métriques suivantes sont accessibles:
| métrique | type |
| Taux complétion moyen | 78% |
| Évolution abonnés | Graphique |
| Écoutes par contenu | Tableau |
| Revenus | Dashboard |
| Taux conversion Premium | Pourcentage |
| Démographie (âge/zone) | Agrégée |
Scénario: Graphique d'évolution des abonnés
Étant donné que le créateur consulte son dashboard
Quand il sélectionne la période "30 jours"
Alors un graphique d'évolution des abonnés est affiché
Et les périodes disponibles sont:
| période |
| 30j |
| 90j |
| 1 an |
Scénario: Tableau détaillé des écoutes par contenu
Étant donné un créateur avec 10 contenus publiés
Quand il consulte le tableau des performances
Alors chaque contenu affiche:
| métrique | exemple |
| Titre | Balade |
| Écoutes totales | 2300 |
| Écoutes complètes >80% | 1840 |
| Taux complétion | 80% |
| Likes | 420 |
| Partages | 56 |
# 15.2.3 - Badge vérifié
Scénario: Affichage du badge vérifié
Étant donné un créateur vérifié "@paris_stories"
Quand son profil est affiché
Alors le badge bleu "" est accolé au pseudo
Et un tooltip "Compte vérifié" s'affiche au survol
Scénario: Badge vérifié visible partout
Étant donné un créateur vérifié "@paris_stories"
Alors le badge "" est affiché dans:
| emplacement |
| Page profil |
| Player en lecture |
| Résultats de recherche|
| Notifications |
Plan du Scénario: Attribution automatique du badge selon critères
Étant donné un créateur avec <critère>
Quand les conditions sont validées
Alors le badge vérifié est attribué <automatique>
Exemples:
| critère | automatique |
| KYC Mangopay validé | Oui |
| 10K abonnés + compte >6 mois | Oui |
| Célébrité / Média officiel | Manuel |
Scénario: Attribution automatique via KYC
Étant donné un créateur qui complète son KYC Mangopay
Quand les documents sont validés
Alors le badge vérifié est attribué automatiquement
Et une notification "Votre compte est maintenant vérifié " est envoyée
Scénario: Attribution automatique à 10K abonnés
Étant donné un créateur avec 9999 abonnés et un compte de 7 mois
Quand il atteint 10000 abonnés
Alors le badge vérifié est attribué automatiquement
Et une notification de félicitations est envoyée
Scénario: Demande manuelle de vérification (célébrité)
Étant donné un créateur reconnu publiquement
Quand il soumet le formulaire de demande de vérification
Alors une requête est créée pour l'équipe RoadWave
Et l'équipe vérifie l'identité sous 48-72h
Et le badge est attribué si validation réussie
Scénario: Retrait du badge en cas de suspension
Étant donné un créateur vérifié avec le badge ""
Quand sa monétisation est suspendue
Alors le badge vérifié est retiré temporairement
Et le badge est restauré après levée de la suspension
Scénario: Retrait définitif du badge pour strikes multiples
Étant donné un créateur vérifié avec 3 strikes actifs
Quand un 4ème strike est appliqué (ban)
Alors le badge vérifié est retiré définitivement
Et le compte est banni
Scénario: Retrait du badge pour usurpation d'identité
Étant donné un créateur vérifié qui usurpe l'identité d'une célébrité
Quand la fraude est détectée
Alors le badge est retiré immédiatement
Et le compte est banni
Et une enquête est ouverte
# Cas d'erreur et limites
Scénario: Profil créateur supprimé
Étant donné qu'un utilisateur tente d'accéder à "@deleted_user"
Quand la page est chargée
Alors un message "Ce profil n'existe pas ou a été supprimé" s'affiche
Scénario: Blocage d'un créateur
Étant donné que l'utilisateur bloque le créateur "@spam_account"
Quand l'utilisateur consulte son flux de recommandations
Alors aucun contenu de "@spam_account" n'est affiché
Et le créateur n'apparaît plus dans les recherches
Scénario: Déblocage d'un créateur
Étant donné que l'utilisateur a bloqué "@paris_stories"
Quand il accède à ses paramètres "Comptes bloqués"
Et qu'il débloque "@paris_stories"
Alors les contenus du créateur réapparaissent dans les recommandations
Scénario: Signalement d'un profil pour spam
Étant donné que l'utilisateur signale le profil "@spam_account"
Quand il sélectionne la raison "Spam"
Alors le signalement est envoyé à la modération
Et un message de confirmation s'affiche
Et le profil reste visible jusqu'à décision de modération
Scénario: Signalement pour usurpation d'identité
Étant donné que l'utilisateur signale le profil "@fake_celebrity"
Quand il sélectionne "Usurpation d'identité"
Et qu'il fournit une preuve
Alors le signalement est priorisé (priorité HAUTE)
Et l'équipe modération traite sous 24h

View File

@@ -0,0 +1,70 @@
# language: fr
@ui @profile @privacy @mvp
Fonctionnalité: Statistiques arrondies pour protection de la vie privée
En tant qu'utilisateur
Je veux que mes statistiques publiques soient arrondies
Afin de protéger ma vie privée et éviter le tracking précis
Scénario: Arrondi du nombre d'écoutes publiques
Étant donné un créateur avec 1,234 écoutes exactes
Quand son profil public est affiché
Alors le nombre affiché est: "1.2k écoutes"
Et non pas "1,234"
Et un événement "STATS_ROUNDED_DISPLAYED" est enregistré
Scénario: Règles d'arrondi selon les volumes
Étant donné différents volumes d'écoutes
Alors l'arrondi appliqué est:
| Écoutes exactes | Affiché publiquement |
| 42 | 40 |
| 157 | 150+ |
| 1,234 | 1.2k |
| 15,678 | 15k |
| 123,456 | 120k |
| 1,234,567 | 1.2M |
Et un événement "ROUNDING_RULES_APPLIED" est enregistré
Scénario: Statistiques précises pour le créateur seulement
Étant donné un créateur "alice@roadwave.fr"
Quand elle consulte son propre dashboard
Alors elle voit les chiffres exacts: 1,234
Mais les visiteurs externes voient: 1.2k
Et un événement "PRECISE_STATS_CREATOR_VIEW" est enregistré
Scénario: Arrondi des revenus publics
Étant donné un créateur avec 1,567 de revenus
Quand ses stats publiques sont affichées
Alors le montant est arrondi: "1.5k"
Et les décimales exactes sont masquées
Et un événement "REVENUE_ROUNDED_PUBLIC" est enregistré
Scénario: Arrondi du nombre d'abonnés
Étant donné un créateur avec 8,743 abonnés
Alors le profil public affiche: "8.7k abonnés"
Et évite le tracking précis de croissance
Et un événement "FOLLOWERS_ROUNDED_DISPLAYED" est enregistré
Scénario: Protection contre le scraping de données
Étant donné un bot qui scrape les profils
Quand il collecte les statistiques arrondies
Alors il ne peut pas obtenir de données précises
Et le tracking temporel est rendu imprécis
Et un événement "SCRAPING_PROTECTION_ACTIVE" est enregistré
Scénario: Option de désactivation de l'arrondi pour créateurs vérifiés
Étant donné un créateur vérifié "MuseeDuLouvre"
Quand il active "Afficher statistiques exactes"
Alors les chiffres précis sont publics
Et cela renforce la transparence
Et un événement "PRECISE_STATS_PUBLIC_ENABLED" est enregistré
Scénario: Métriques d'impact de l'arrondi sur la vie privée
Étant donné que 10 000 profils affichent des stats arrondies
Alors l'impact est mesuré:
| Métrique | Valeur |
| Tentatives de tracking bloquées | 1,234 |
| Précision moyenne du scraping | -70% |
| Satisfaction utilisateurs | 4.5/5 |
Et les métriques sont exportées vers le monitoring

View File

@@ -0,0 +1,176 @@
# language: fr
@rgpd @anonymization @gps
Fonctionnalité: Anonymisation des données GPS après 24h
# 13.2 - Geohash après 24h (conformité RGPD + CNIL)
Contexte:
Étant donné que je suis un utilisateur avec le GPS activé
Et que j'utilise l'application depuis plusieurs jours
Scénario: Conservation des données GPS précises pendant 24h
Étant donné que j'écoute un contenu à la position GPS 48.8566, 2.3522 (Paris, Tour Eiffel)
Et qu'il est 10:00 le 2025-01-20
Quand l'événement d'écoute est enregistré en base de données
Alors les coordonnées précises 48.8566, 2.3522 sont stockées
Et le champ `anonymized` est à `false`
Et le champ `created_at` contient "2025-01-20 10:00:00"
Et ces données précises servent à la recommandation personnalisée
Scénario: Conversion en geohash après 24h
Étant donné que j'ai écouté un contenu le 2025-01-20 à 10:00 à la position 48.8566, 2.3522
Quand le job quotidien d'anonymisation s'exécute le 2025-01-21 à 02:00
Alors les coordonnées précises sont converties en geohash précision 5
Et le geohash correspond à une zone d'environ 5km²
Et les coordonnées originales 48.8566, 2.3522 sont supprimées définitivement
Et le champ `anonymized` passe à `true`
Et il est impossible de retrouver la position précise d'origine
Scénario: Requête SQL d'anonymisation (PostGIS)
Étant donné que le job quotidien d'anonymisation s'exécute
Quand la requête SQL suivante est exécutée:
"""sql
UPDATE location_history
SET location = ST_SetSRID(ST_GeomFromGeoHash(ST_GeoHash(location::geography, 5)), 4326)::geography,
anonymized = true
WHERE created_at < NOW() - INTERVAL '24 hours' AND anonymized = false;
"""
Alors toutes les positions vieilles de plus de 24h sont anonymisées
Et le processus est automatique et irréversible
Et les données sont conformes RGPD
Scénario: Précision du geohash niveau 5
Étant donné qu'une position GPS est convertie en geohash précision 5
Quand on analyse la zone couverte
Alors la zone fait environ 5km² (4.9km × 4.9km)
Et cette précision est suffisante pour des analytics agrégées
Et cette précision ne permet pas d'identifier un individu (conformité CNIL)
Scénario: Exemple de conversion Paris
Étant donné que ma position précise est 48.8566, 2.3522 (Tour Eiffel)
Quand la conversion en geohash précision 5 est appliquée
Alors le geohash généré est "u09wh"
Et ce geohash couvre une zone de ~5km² autour de la Tour Eiffel
Et toutes les positions dans cette zone partagent le même geohash
Et il est impossible de distinguer deux utilisateurs dans cette zone
# Exception: Historique personnel
Scénario: Conservation de l'historique personnel utilisateur
Étant donné que j'ai écouté des contenus aux positions suivantes:
| date | heure | latitude | longitude | lieu |
| 2025-01-15 | 08:30 | 48.8566 | 2.3522 | Paris |
| 2025-01-16 | 14:00 | 43.6047 | 1.4442 | Toulouse |
| 2025-01-17 | 19:00 | 45.7640 | 4.8357 | Lyon |
Quand j'ouvre mon historique personnel dans "Profil > Mes trajets"
Alors je vois mes trajets avec les positions précises intégrales
Et ces données ne sont pas anonymisées tant que mon compte est actif
Et seul moi peut accéder à ces données
Et elles ne sont pas utilisées pour des analytics globales
Scénario: Anonymisation pour analytics globales uniquement
Étant donné que RoadWave génère des analytics agrégées
Quand l'équipe analyse les zones géographiques populaires
Alors seules les données anonymisées (geohash) sont utilisées
Et les positions précises de l'historique personnel ne sont jamais agrégées
Et les heatmaps de trafic utilisent uniquement les geohash ~5km²
# Job quotidien automatique
Scénario: Planification du job d'anonymisation
Étant donné que le système est en production
Quand on consulte les jobs planifiés (cron)
Alors un job "anonymize_gps_data" est configuré
Et le job s'exécute tous les jours à 02:00 (heure creuse)
Et le job traite toutes les positions vieilles de plus de 24h
Et un log est généré pour traçabilité
Scénario: Exécution du job avec métriques
Étant donné que le job d'anonymisation s'exécute le 2025-01-21 à 02:00
Quand le job se termine
Alors un rapport est généré avec:
| métrique | valeur |
| Nombre de positions traitées | 15420 |
| Nombre de positions anonymisées| 15420 |
| Durée d'exécution | 3.5s |
| Erreurs | 0 |
Et le rapport est loggé dans Sentry/Grafana
Et une alerte est envoyée si le job échoue
Scénario: Performances du job d'anonymisation
Étant donné que 100 000 positions doivent être anonymisées
Quand le job s'exécute
Alors le traitement se fait en moins de 30 secondes
Et la requête PostGIS est optimisée avec index
Et aucun impact sur les performances de l'application en production
# Vraie anonymisation RGPD
Scénario: Impossibilité de réidentification
Étant donné qu'une position a été anonymisée en geohash "u09wh"
Quand un attaquant tente de retrouver la position précise d'origine
Alors il est impossible de déterminer la position exacte
Et des milliers de positions précises correspondent au même geohash
Et il n'y a aucune traçabilité vers la position originale
Et cette anonymisation est irréversible
Scénario: Conformité CNIL - données véritablement anonymisées
Étant donné que les positions sont converties en geohash précision 5
Quand un auditeur CNIL vérifie la conformité
Alors les données sont considérées comme véritablement anonymisées
Et elles ne sont plus considérées comme des données personnelles
Et aucun consentement n'est requis pour leur traitement analytique
Et elles peuvent être conservées indéfiniment
# Analytics agrégées autorisées
Scénario: Heatmap de trafic avec données anonymisées
Étant donné que RoadWave génère une heatmap des zones populaires
Quand on analyse les données utilisées
Alors seules les positions anonymisées (geohash) sont agrégées
Et la heatmap montre des zones de ~5km²
Et aucune position précise n'est révélée
Et cette analyse ne nécessite pas de consentement utilisateur (données anonymes)
Scénario: Statistiques géographiques par département
Étant donné que RoadWave analyse l'utilisation par département
Quand les statistiques sont générées
Alors les données anonymisées sont agrégées par département
Et les résultats montrent: "Paris (75): 12 500 écoutes, Lyon (69): 8 300 écoutes"
Et aucune donnée personnelle n'est révélée
Et les statistiques sont RGPD-compliant
# PostGIS natif - 0
Scénario: Coût de la solution d'anonymisation
Étant donné que PostGIS est utilisé pour l'anonymisation GPS
Quand on calcule le coût de la solution
Alors le coût est de 0 (PostGIS inclus dans PostgreSQL)
Et aucune librairie tierce n'est nécessaire
Et la solution est entièrement maîtrisée (self-hosted)
# Cas limites
Scénario: Anonymisation respecte les positions en cours de session
Étant donné que je suis en train d'écouter du contenu actuellement
Et que certaines de mes positions ont plus de 24h
Quand le job d'anonymisation s'exécute
Alors mes positions de plus de 24h sont anonymisées
Mais ma position actuelle (session en cours) reste précise
Et la recommandation continue de fonctionner normalement
Scénario: Suppression de compte et anonymisation GPS
Étant donné que je demande la suppression de mon compte
Quand le compte est supprimé (après grace period de 30j)
Alors toutes mes positions GPS (précises et anonymisées) sont supprimées
Et mon historique personnel de trajets est supprimé
Et aucune donnée GPS ne subsiste, même anonymisée
Scénario: Export de données avant anonymisation
Étant donné que je demande un export de mes données
Et que certaines de mes positions ont été anonymisées
Quand l'export est généré
Alors les positions précises de mon historique personnel sont incluses
Mais les positions déjà anonymisées (>24h, analytics) apparaissent en geohash
Et l'export précise quelles données ont été anonymisées et pourquoi

View File

@@ -0,0 +1,256 @@
# language: fr
@rgpd @administrative @dpo
Fonctionnalité: Conformité administrative RGPD (Registre, Breach, DPO)
# 13.8 - Registre des traitements (Article 30 RGPD)
@registre-traitements
Scénario: Registre des traitements en Markdown versionné Git
Étant donné que RoadWave doit tenir un registre des traitements
Quand on consulte la documentation
Alors un fichier `docs/rgpd/registre-traitements.md` existe
Et le fichier est versionné dans Git
Et l'historique des modifications est traçable via Git
Et chaque traitement est documenté dans une section dédiée
Scénario: Contenu obligatoire pour chaque traitement
Étant donné que le registre des traitements contient le traitement "Géolocalisation utilisateurs"
Quand on lit la section correspondante
Alors les informations suivantes sont présentes:
| information obligatoire | exemple |
| Nom du traitement | Géolocalisation utilisateurs |
| Finalité | Recommandation de contenu géolocalisé |
| Catégories de données | Coordonnées GPS, historique de position |
| Base légale | Consentement (Article 6.1.a RGPD) |
| Durée de conservation | 24h (précis), puis geohash anonymisé |
| Destinataires | Aucun tiers |
| Transferts hors UE | Aucun |
| Mesures de sécurité | TLS 1.3, anonymisation après 24h |
Plan du Scénario: Traitements documentés dans le registre
Étant donné que le registre des traitements est complet
Quand on liste tous les traitements
Alors le traitement "<traitement>" est documenté avec la base légale "<base_legale>"
Exemples:
| traitement | base_legale |
| Géolocalisation utilisateurs | Consentement |
| Historique d'écoute | Intérêt légitime |
| Création de contenu | Exécution du contrat |
| Analytics (Matomo) | Consentement |
| Paiements (Mangopay) | Exécution du contrat |
| Modération contenus | Intérêt légitime |
| Notifications push | Consentement |
Scénario: Review trimestrielle du registre
Étant donné que le registre des traitements existe
Quand on consulte l'historique Git
Alors une mise à jour est effectuée au moins tous les 3 mois
Et chaque mise à jour a un commit avec message explicite
Et un tag Git marque chaque review trimestrielle
Et les modifications sont traçables (auteur, date, changements)
Scénario: Mise à jour immédiate si nouveau traitement
Étant donné qu'une nouvelle fonctionnalité nécessite un traitement de données
Quand la fonctionnalité est développée
Alors le registre est mis à jour AVANT le déploiement en production
Et le nouveau traitement est documenté complètement
Et un commit Git enregistre l'ajout
Et le DPO valide la conformité RGPD du nouveau traitement
Scénario: Migration future vers interface admin PostgreSQL
Étant donné que RoadWave dépasse 100 000 utilisateurs
Quand la complexité du registre augmente
Alors une interface admin PostgreSQL est développée
Et le registre Markdown est migré vers la base de données
Et l'historique Git est conservé pour audit
Et l'interface permet une gestion plus efficace des traitements
# 13.9 - Notification violations de données (Breach)
@breach-notification
Scénario: Détection automatique d'événements critiques
Étant donné que le système de monitoring est actif
Quand un événement critique se produit
Alors une alerte est envoyée selon le type d'événement:
| événement | outil | alerte |
| Erreur backend critique | Sentry | Discord/Slack immédiat |
| Pic requêtes anormal | Grafana | Email équipe |
| Accès non autorisé DB | PostgreSQL logs | SMS fondateur |
| Authentification suspecte | Zitadel alerts | Email équipe |
Et les alertes permettent une réaction rapide
Scénario: Runbook de procédure breach disponible
Étant donné qu'une violation de données potentielle est détectée
Quand l'équipe consulte la documentation
Alors un runbook `docs/rgpd/procedure-breach.md` existe
Et le runbook contient une checklist 72h CNIL
Et chaque étape est clairement documentée
Et les contacts d'urgence sont listés
Scénario: Checklist 72h en cas de breach
Étant donné qu'une violation de données est confirmée
Quand l'équipe suit la procédure breach
Alors les étapes suivantes sont exécutées dans les délais:
| délai | étape |
| H+0 | Détection et confinement immédiat |
| H+24 | Évaluation gravité (données concernées, users impactés) |
| H+48 | Notification CNIL si risque pour utilisateurs |
| H+72 | Notification utilisateurs si risque élevé |
Et chaque étape est documentée pour audit
Scénario: Évaluation de la gravité du breach
Étant donné qu'une violation de données est détectée
Quand l'équipe évalue la gravité
Alors les critères suivants sont analysés:
| critère | exemple |
| Type de données concernées | Emails, mots de passe, GPS, etc. |
| Nombre d'utilisateurs impactés | 10, 100, 10000, etc. |
| Mesures de sécurité existantes | Chiffrement, hachage, anonymisation |
| Risque pour les droits et libertés | Faible, modéré, élevé |
Et si le risque est élevé, la CNIL est notifiée sous 72h
Scénario: Notification CNIL dans les 72h
Étant donné qu'un breach avec risque élevé est confirmé à 10:00 le 2025-01-20
Quand la gravité est évaluée comme nécessitant une notification
Alors la CNIL est notifiée avant 10:00 le 2025-01-23 (72h)
Et la notification contient:
| information |
| Nature de la violation |
| Données concernées |
| Nombre d'utilisateurs impactés |
| Conséquences probables |
| Mesures prises |
| Mesures de remédiation |
Et un email pré-rédigé (template) est utilisé pour gagner du temps
Scénario: Notification des utilisateurs si risque élevé
Étant donné qu'un breach impacte 5000 utilisateurs
Et que le risque est élevé (mots de passe non chiffrés exposés)
Quand la CNIL est notifiée
Alors les utilisateurs impactés sont notifiés dans les 72h
Et l'email contient:
"""
Objet: Alerte sécurité - Action requise
Bonjour,
Nous vous informons qu'une violation de données a affecté votre compte RoadWave.
Données concernées: [liste]
Date de l'incident: [date]
Actions prises: [mesures]
Action requise: Changez immédiatement votre mot de passe.
Pour toute question: security@roadwave.fr
"""
Et un lien de réinitialisation de mot de passe est inclus
Scénario: Aucune notification si risque faible
Étant donné qu'un breach mineur est détecté (logs techniques exposés, aucune donnée personnelle)
Quand l'équipe évalue la gravité
Alors le risque est jugé faible
Et aucune notification CNIL n'est requise (Article 33.1 RGPD)
Et aucune notification utilisateur n'est envoyée
Et un log interne est créé pour traçabilité
Scénario: Monitoring proactif pour éviter découverte tardive
Étant donné que Sentry et Grafana sont configurés
Quand un comportement anormal est détecté
Alors une alerte est envoyée en temps réel
Et l'équipe peut réagir avant qu'un breach majeur ne se produise
Et les logs sont analysés quotidiennement pour détecter des anomalies
Et cette approche proactive limite les risques de découverte tardive
# 13.10 - DPO (Délégué à la Protection des Données)
@dpo
Scénario: Fondateur = DPO temporaire (MVP)
Étant donné que RoadWave est en phase MVP
Et que l'entreprise a moins de 250 employés
Quand on vérifie l'obligation légale d'avoir un DPO
Alors le DPO n'est pas obligatoire selon le RGPD Article 37
Et le fondateur assume temporairement le rôle de DPO
Et le fondateur suit la formation CNIL gratuite (4h)
Scénario: Formation CNIL du DPO temporaire
Étant donné que le fondateur est DPO temporaire
Quand on vérifie sa formation
Alors le fondateur a suivi la formation CNIL en ligne (4h)
Et le fondateur a obtenu la certification "Atelier RGPD" (gratuit)
Et le certificat est conservé pour audit
Et la formation couvre:
| sujet |
| Principes fondamentaux du RGPD |
| Droits des personnes |
| Sécurité des données |
| Violations de données (breach) |
| Registre des traitements |
Scénario: Contact DPO publié et accessible
Étant donné que je consulte les mentions légales de RoadWave
Quand je cherche le contact du DPO
Alors l'email "dpo@roadwave.fr" est clairement affiché
Et cet email est également dans les CGU
Et le délai de réponse garanti est de 1 mois maximum (RGPD Article 12.3)
Et une adresse postale est également fournie
Scénario: Demande d'exercice de droits RGPD au DPO
Étant donné que je veux exercer mon droit d'accès à mes données
Quand j'envoie un email à dpo@roadwave.fr
Alors je reçois un accusé de réception dans les 48h
Et ma demande est traitée dans un délai maximum de 1 mois
Et si le délai dépasse 1 mois, je suis informé de la prolongation (max 2 mois supplémentaires)
Et la réponse est complète et conforme au RGPD
Scénario: Types de demandes gérées par le DPO
Étant donné que je contacte le DPO
Quand j'envoie une demande
Alors le DPO peut traiter les demandes suivantes:
| type de demande |
| Droit d'accès (Article 15) |
| Droit de rectification (Article 16) |
| Droit à l'effacement (Article 17) |
| Droit à la portabilité (Article 20) |
| Droit d'opposition (Article 21) |
| Plainte RGPD |
| Question sur le traitement des données |
Et chaque demande reçoit une réponse personnalisée
Scénario: Migration vers DPO externe si croissance
Étant donné que RoadWave dépasse 100 000 utilisateurs
Quand la charge de travail DPO augmente
Alors un DPO externe mutualisé est engagé
Et le coût est d'environ 200/mois
Et le DPO externe a les certifications CNIL requises
Et un contrat de sous-traitance RGPD est signé
Scénario: Recrutement DPO interne si >10 employés
Étant donné que RoadWave a plus de 10 employés
Quand l'entreprise se structure
Alors un DPO interne peut être recruté
Et le DPO interne a une certification CNIL (AFCDP ou équivalent)
Et le DPO est indépendant et ne peut être licencié pour ses fonctions
Et le DPO a un accès direct à la direction
# Coût total conformité RGPD
Scénario: Récapitulatif des coûts RGPD
Étant donné que toutes les mesures RGPD sont en place
Quand on calcule le coût total mensuel
Alors le récapitulatif est le suivant:
| mesure | implémentation | coût |
| Consentement | Tarteaucitron.js + PostgreSQL | 0 |
| Anonymisation GPS | Geohash PostGIS (24h) | 0 |
| Export données | JSON+HTML+ZIP asynchrone | 0 |
| Suppression compte | Grace period 30j + anonymisation | 0 |
| Mode dégradé | GeoIP IP2Location + GPS optionnel | 0 |
| Conservation | Purge auto 5 ans inactivité | 0 |
| Analytics | Matomo self-hosted | ~5/mois |
| Registre traitements | Markdown Git | 0 |
| Breach detection | Sentry + Grafana + runbook | 0 (< 5K events) |
| DPO | Fondateur formé CNIL | 0 |
Et le coût total est d'environ 5/mois
Et cette conformité est 100% opensource et maîtrisée

View File

@@ -0,0 +1,186 @@
# language: fr
@rgpd @consent
Fonctionnalité: Gestion du consentement RGPD
Contexte:
Étant donné que je suis un nouvel utilisateur
Et que j'accède à l'application pour la première fois
# 13.1 - Gestion du consentement avec Tarteaucitron.js + PostgreSQL
Scénario: Affichage du banner de consentement au premier lancement web
Étant donné que j'accède à l'application web pour la première fois
Quand la page se charge
Alors un banner RGPD Tarteaucitron.js s'affiche
Et le banner est en français
Et le banner propose les options suivantes:
| option | description |
| Tout accepter | Active tous les consentements |
| Tout refuser | Refuse tous les consentements optionnels |
| Personnaliser | Ouvre le panneau de personnalisation |
Et le banner est customisé aux couleurs de RoadWave
Scénario: Granularité des consentements
Étant donné que le banner RGPD est affiché
Quand je clique sur "Personnaliser"
Alors je vois les catégories de consentements suivantes:
| catégorie | type | requis |
| Fonctionnel | Nécessaire | oui |
| Analytique | Optionnel | non |
| Marketing | Optionnel | non |
Et chaque catégorie a une description claire de son usage
Et je peux accepter ou refuser chaque catégorie individuellement
Scénario: Consentement géolocalisation précise - obligatoire
Étant donné que je suis sur l'application mobile
Et que l'onboarding est terminé
Quand l'application a besoin d'accéder à ma position précise
Alors un écran de demande de consentement s'affiche
Et le message explique clairement l'usage:
"""
RoadWave utilise votre position GPS pour vous proposer du contenu audio géolocalisé pertinent.
Vos données sont anonymisées après 24h.
"""
Et je peux accepter ou refuser
Et si je refuse, l'application bascule en mode dégradé (GeoIP uniquement)
Scénario: Double consentement GPS - banner app + permission OS
Étant donné que je veux activer la géolocalisation précise
Quand j'accepte le consentement dans l'application
Alors l'application demande également la permission au système d'exploitation
Et sur iOS, la popup système s'affiche: "Autoriser RoadWave à accéder à votre position ?"
Et sur Android, la popup système s'affiche avec les options "Toujours autoriser / Autoriser seulement pendant l'utilisation / Refuser"
Et les deux consentements (app + OS) doivent être acceptés pour activer le GPS précis
# Historique des consentements - PostgreSQL backend
Scénario: Enregistrement du consentement en base de données
Étant donné que j'ai accepté les consentements suivants:
| type | accepté |
| Fonctionnel | oui |
| Analytique | oui |
| Marketing | non |
| GPS précis | oui |
Quand je valide mes choix
Alors un enregistrement est créé dans la table `user_consents`
Et l'enregistrement contient les champs suivants:
| champ | valeur |
| user_id | [mon ID utilisateur] |
| consent_type | fonctionnel / analytique / gps |
| version | 1 |
| accepted | true / false |
| timestamp | [date et heure exacte] |
Et chaque type de consentement a un enregistrement séparé
Scénario: Versioning des consentements
Étant donné que j'ai accepté le consentement "Analytique" version 1 le 2025-01-01
Et que les CGU sont mises à jour le 2025-06-01
Quand je me connecte après la mise à jour
Alors un nouveau consentement version 2 m'est demandé
Et mon ancien consentement version 1 reste dans l'historique
Et je dois accepter la nouvelle version pour continuer à utiliser les analytics
Scénario: Historique complet conservé pour preuve légale
Étant donné que j'ai modifié mes consentements plusieurs fois:
| date | consent_type | accepted | version |
| 2025-01-01 | Analytique | oui | 1 |
| 2025-03-15 | Analytique | non | 1 |
| 2025-06-01 | Analytique | oui | 2 |
Quand un auditeur CNIL consulte mon historique de consentements
Alors tous les enregistrements sont conservés
Et l'historique prouve que chaque consentement a été donné librement
Et les timestamps permettent de prouver la conformité à tout moment
# Consentements requis vs optionnels
Scénario: Consentement analytique - optionnel
Étant donné que je refuse le consentement "Analytique"
Quand j'utilise l'application
Alors aucun cookie Matomo `_pk_id` n'est déposé
Et aucune donnée d'usage n'est envoyée à Matomo
Et l'application fonctionne normalement sans analytics
Scénario: Consentement notifications push - optionnel
Étant donné que je refuse le consentement "Notifications push"
Quand un créateur que je suis publie un nouveau contenu
Alors je ne reçois pas de notification push
Mais je peux voir le nouveau contenu dans l'application
Et l'application fonctionne normalement
Scénario: Consentement GPS précis - requis pour fonctionnalités géo
Étant donné que je refuse le consentement "GPS précis"
Quand j'utilise l'application
Alors je peux accéder aux contenus nationaux
Mais les contenus géolocalisés précis (Ancré, Contextuel) ne sont pas disponibles
Et les audio-guides nécessitent l'activation du GPS
Et un banner permanent me rappelle que l'activation du GPS améliore l'expérience
# Modification des consentements
Scénario: Révocation d'un consentement depuis les paramètres
Étant donné que j'ai accepté le consentement "Analytique"
Et que j'utilise l'application depuis 3 mois
Quand j'ouvre "Paramètres > Confidentialité > Gérer mes consentements"
Alors je vois la liste de tous mes consentements actuels
Et je peux révoquer le consentement "Analytique"
Quand je révoque le consentement
Alors un nouvel enregistrement est créé avec `accepted = false`
Et le cookie Matomo est supprimé immédiatement
Et les analytics sont désactivées à partir de ce moment
Scénario: Acceptation d'un consentement précédemment refusé
Étant donné que j'avais refusé le consentement "GPS précis"
Quand j'ouvre "Paramètres > Confidentialité > Gérer mes consentements"
Et que je clique sur "Activer la géolocalisation précise"
Alors un nouvel enregistrement est créé avec `accepted = true`
Et la permission OS est demandée si ce n'est pas déjà fait
Et l'application bascule en mode géolocalisation précise
Et les contenus géolocalisés deviennent disponibles immédiatement
# Preuves légales pour contrôle CNIL
Scénario: Export de l'historique des consentements pour audit
Étant donné qu'un contrôle CNIL est en cours
Quand l'équipe RoadWave exporte l'historique des consentements
Alors l'export contient pour chaque utilisateur:
| champ | description |
| user_id | ID anonymisé |
| consent_type | Type de consentement |
| version | Version des CGU/consentement |
| accepted | Accepté ou refusé |
| timestamp | Date et heure exacte |
| ip_address | IP (anonymisée) au moment du consentement |
| user_agent | Navigateur/app utilisé |
Et l'export est au format CSV pour analyse
Et les données prouvent la conformité RGPD
Scénario: Conformité recommandations CNIL
Étant donné que le système de consentement est implémenté
Quand un auditeur CNIL vérifie la conformité
Alors le système respecte les critères suivants:
| critère CNIL | respecté |
| Consentement libre | oui |
| Consentement spécifique (granulaire) | oui |
| Consentement éclairé (information claire) | oui |
| Consentement univoque (action positive) | oui |
| Révocable à tout moment | oui |
| Preuve du consentement conservée | oui |
# Opensource et self-hosted
Scénario: Tarteaucitron.js self-hosted
Étant donné que l'application web utilise Tarteaucitron.js
Quand je consulte les sources JavaScript chargées
Alors le script Tarteaucitron.js est hébergé sur les serveurs RoadWave
Et aucun script tiers (CDN externe) n'est chargé
Et le code source de Tarteaucitron.js est vérifiable
Et aucune donnée n'est envoyée à un tiers lors de l'affichage du banner
Scénario: Coût de la solution - 0€
Étant donné que Tarteaucitron.js est opensource
Et que PostgreSQL est utilisé pour le backend
Quand on calcule le coût de la solution de consentement
Alors le coût est de 0
Et la solution est entièrement maîtrisée (self-hosted)
Et aucune dépendance à un service SaaS tiers

View File

@@ -0,0 +1,245 @@
# language: fr
@rgpd @data-retention
Fonctionnalité: Durée de conservation des données et purge automatique
# 13.6 - 5 ans inactivité purge automatique (principe de minimisation RGPD)
Contexte:
Étant donné que le système de purge automatique est actif
# Règles de conservation
Scénario: Auditeur inactif depuis 5 ans - suppression automatique
Étant donné que je suis un auditeur (sans contenu créé)
Et que je ne me suis pas connecté depuis le 2020-01-01
Et que la date actuelle est 2025-01-02 (>5 ans)
Quand le job de purge automatique s'exécute
Alors mon compte est automatiquement supprimé
Et toutes mes données personnelles sont effacées
Et aucune trace ne subsiste dans la base de données
Scénario: Créateur avec contenus actifs - conservation indéfinie
Étant donné que je suis un créateur
Et que j'ai créé 10 contenus qui reçoivent encore des écoutes
Et que je ne me suis pas connecté depuis 6 ans
Quand le job de purge automatique s'exécute
Alors mon compte n'est pas supprimé
Et mes données personnelles sont conservées tant que mes contenus sont écoutés
Et mes contenus continuent d'être diffusés normalement
Scénario: Créateur inactif sans écoutes - suppression automatique
Étant donné que je suis un créateur
Et que j'ai créé 5 contenus
Et que je ne me suis pas connecté depuis 5 ans (depuis 2020-01-01)
Et que mes contenus n'ont reçu aucune écoute depuis 2 ans (depuis 2023-01-01)
Et que la date actuelle est 2025-01-02
Quand le job de purge automatique s'exécute
Alors mon compte est automatiquement supprimé
Et mes contenus sont anonymisés (créateur = "Utilisateur supprimé")
Et les fichiers audio restent disponibles mais anonymisés
# Notifications avant suppression
Scénario: Notifications par email avant purge
Étant donné que je suis inactif depuis 4 ans et 9 mois
Quand le système détecte que je suis éligible à la purge dans 90 jours
Alors je reçois un email avec le sujet "Votre compte RoadWave sera supprimé dans 90 jours"
Et l'email contient:
"""
Bonjour,
Votre compte RoadWave n'a pas été utilisé depuis plus de 4 ans.
Conformément à notre politique de conservation des données, votre compte sera automatiquement supprimé dans 90 jours si vous ne vous connectez pas.
Pour conserver votre compte, il suffit de vous connecter avant le [date limite].
Date de dernière connexion: [date]
Date de suppression prévue: [date + 90j]
Si vous ne souhaitez pas conserver ce compte, aucune action n'est requise.
"""
Et un lien de connexion est inclus dans l'email
Scénario: Rappels à 90j, 30j et 7j avant suppression
Étant donné que je suis éligible à la purge automatique
Quand les délais s'écoulent
Alors je reçois les emails suivants:
| délai | sujet email |
| 90 jours | Votre compte sera supprimé dans 90 jours |
| 30 jours | Rappel: Votre compte sera supprimé dans 30 jours |
| 7 jours | Dernière alerte: suppression dans 7 jours |
Et chaque email contient un lien de connexion pour réactiver le compte
Et les notifications push sont également envoyées si activées
Scénario: Connexion annule la suppression programmée
Étant donné que je suis éligible à la purge dans 15 jours
Et que j'ai reçu plusieurs emails d'avertissement
Quand je me connecte à mon compte
Alors la suppression programmée est annulée immédiatement
Et le compteur d'inactivité est remis à zéro
Et je reçois un email de confirmation: "Votre compte a été réactivé"
Et je peux continuer à utiliser l'application normalement
# Job de purge automatique
Scénario: Exécution quotidienne du job de purge
Étant donné que le système est en production
Quand on consulte les jobs planifiés
Alors un job "purge_inactive_accounts" est configuré
Et le job s'exécute tous les jours à 03:00 (heure creuse)
Et le job identifie les comptes éligibles à la purge
Et le job traite les suppressions automatiques
Scénario: Critères d'éligibilité à la purge
Étant donné que le job de purge s'exécute
Quand le système identifie les comptes éligibles
Alors les critères suivants sont appliqués:
| type_compte | critères |
| Auditeur uniquement | 5 ans sans connexion |
| Créateur avec contenus actifs| Jamais (tant qu'écoutes) |
| Créateur inactif | 5 ans sans connexion + 2 ans sans écoute |
Et seuls les comptes remplissant tous les critères sont supprimés
Scénario: Métriques du job de purge
Étant donné que le job de purge s'exécute le 2025-01-15
Quand le job se termine
Alors un rapport est généré avec:
| métrique | exemple |
| Comptes analysés | 150 000 |
| Comptes éligibles à la purge | 350 |
| Auditeurs supprimés | 300 |
| Créateurs inactifs supprimés | 50 |
| Créateurs conservés (actifs) | 0 |
| Erreurs | 0 |
| Durée d'exécution | 45s |
Et le rapport est loggé pour audit
# Contenus conservés après purge
Scénario: Contenus de comptes purgés conservés anonymement
Étant donné que mon compte créateur est purgé automatiquement
Quand la suppression est effective
Alors mes contenus créés sont conservés indéfiniment
Et les contenus sont anonymisés (créateur = "Utilisateur supprimé")
Et les fichiers audio restent sur le CDN
Et les statistiques d'écoute sont préservées
Et les utilisateurs peuvent toujours écouter mes contenus
# Exception: créateurs avec écoutes régulières
Scénario: Créateur inactif mais contenus populaires - pas de purge
Étant donné que je suis un créateur inactif depuis 6 ans
Mais que mes contenus reçoivent 500+ écoutes par mois
Quand le job de purge s'exécute
Alors mon compte n'est pas supprimé
Et je continue de recevoir les emails d'avertissement tous les 6 mois
Et mes contenus continuent d'être diffusés
Et je peux me reconnecter à tout moment
# Définition de "écoute" pour les créateurs
Scénario: Qu'est-ce qu'une "écoute" pour le calcul d'inactivité
Étant donné que je suis un créateur
Quand le système calcule si mes contenus sont "actifs"
Alors une "écoute" est comptabilisée si:
| condition | comptabilisée |
| Écoute complète (>80%) | oui |
| Écoute partielle (>30%) | oui |
| Skip rapide (<30%) | non |
| Écoute par un bot (détecté) | non |
Et au moins 1 écoute valide dans les 2 dernières années maintient le compte actif
# Principe de minimisation RGPD
Scénario: Conformité principe de minimisation
Étant donné que le système de purge automatique est en place
Quand un auditeur RGPD vérifie la conformité
Alors le système respecte le principe de minimisation:
| principe | respecté |
| Conservation limitée dans le temps | oui |
| Suppression automatique après inactivité | oui |
| Délai raisonnable (5 ans) | oui |
| Notifications préalables | oui |
| Exception justifiée (contenus actifs) | oui |
Et le délai de 5 ans est conforme aux standards de l'industrie
# Reset du compteur d'inactivité
Scénario: Actions qui réinitialisent le compteur d'inactivité
Étant donné que je suis inactif depuis 4 ans
Quand j'effectue l'une des actions suivantes:
| action |
| Connexion à l'application |
| Publication d'un nouveau contenu |
| Like d'un contenu |
| Abonnement à un créateur |
| Modification de mon profil |
Alors le compteur d'inactivité est remis à zéro
Et la suppression programmée est annulée
Et je ne suis plus éligible à la purge pour 5 ans
# Logs d'audit
Scénario: Traçabilité des suppressions automatiques
Étant donné qu'un compte est supprimé automatiquement
Quand la suppression est effective
Alors un log d'audit est créé avec:
| champ | valeur |
| user_id | [ID anonymisé] |
| account_type | auditeur / créateur |
| last_login | 2020-01-15T10:00:00Z |
| last_content_listen | 2023-06-01T14:30:00Z |
| purge_date | 2025-01-15T03:00:00Z |
| notifications_sent | 3 (90j, 30j, 7j) |
| reason | 5_years_inactivity |
Et le log est conservé 5 ans pour audit RGPD
Et l'user_id est pseudonymisé pour anonymat
# Cas particuliers
Scénario: Compte Premium inactif - pas de privilège spécial
Étant donné que je suis un utilisateur Premium
Et que je suis inactif depuis 5 ans
Quand le job de purge s'exécute
Alors mon compte est supprimé comme un compte gratuit
Et l'abonnement Premium ne prolonge pas la durée de conservation
Et aucun remboursement n'est effectué (compte inactif depuis 5 ans)
Scénario: Compte avec signalements de modération - purge différée
Étant donné que je suis éligible à la purge
Mais que j'ai des signalements de modération en cours
Quand le job de purge s'exécute
Alors ma purge est différée de 90 jours
Et les signalements sont traités en priorité
Et si les signalements aboutissent à un ban, le compte est supprimé immédiatement
Et si les signalements sont infondés, la purge automatique reprend son cours
# Durée de 5 ans - justification
Scénario: Pourquoi 5 ans d'inactivité
Étant donné que le délai de purge est fixé à 5 ans
Quand on justifie ce choix
Alors les raisons suivantes sont avancées:
| justification |
| Standard de l'industrie (Google, Facebook: 2-3 ans) |
| Équilibre raisonnable entre minimisation et utilité |
| Conforme aux recommandations CNIL |
| Laisse une marge de réactivation pour utilisateurs |
| Exception pour créateurs = intérêt légitime communauté |
# Communication transparente
Scénario: Politique de conservation visible dans les CGU
Étant donné que je consulte les CGU de RoadWave
Quand je lis la section "Conservation des données"
Alors la politique de purge automatique est clairement expliquée:
"""
Conservation des données:
- Comptes auditeurs: suppression automatique après 5 ans d'inactivité
- Comptes créateurs: suppression après 5 ans d'inactivité + 2 ans sans écoute de leurs contenus
- Exception: créateurs dont les contenus sont encore écoutés régulièrement
- Notifications: 90j, 30j et 7j avant suppression
- Contenus créés: conservés de manière anonyme après suppression du compte
"""
Et les utilisateurs sont informés dès l'inscription

View File

@@ -0,0 +1,227 @@
# language: fr
@rgpd @cookies @analytics
Fonctionnalité: Cookies et analytics avec Matomo self-hosted
# 13.7 - Matomo self-hosted, zéro cookie tiers (souveraineté données)
Contexte:
Étant donné que je suis un utilisateur de l'application web RoadWave
# Liste des cookies utilisés
Scénario: Cookies strictement nécessaires - pas de consentement requis
Étant donné que j'accède à l'application web
Quand je me connecte
Alors les cookies techniques suivants sont déposés:
| cookie | type | durée | finalité | consentement |
| session | Technique | 30j | Authentification | Non requis |
| refresh_token | Technique | 30j | Session persistante | Non requis |
Et ces cookies sont essentiels au fonctionnement de l'application
Et ils sont exemptés de consentement selon l'article 82 de la loi Informatique et Libertés
Scénario: Cookie analytique Matomo - consentement requis
Étant donné que j'ai accepté le consentement "Analytique"
Quand je navigue sur l'application web
Alors le cookie `_pk_id` est déposé
Et la durée de conservation est de 13 mois
Et ce cookie sert à Matomo pour analytics
Et mon IP est automatiquement anonymisée (2 derniers octets)
Scénario: Refus du consentement analytique - pas de cookie Matomo
Étant donné que j'ai refusé le consentement "Analytique"
Quand je navigue sur l'application web
Alors aucun cookie `_pk_id` n'est déposé
Et aucune donnée d'usage n'est collectée
Et l'application fonctionne normalement sans analytics
# Matomo self-hosted
Scénario: Matomo hébergé sur les serveurs RoadWave
Étant donné que RoadWave utilise Matomo pour les analytics
Quand on analyse l'infrastructure
Alors Matomo est installé sur les serveurs RoadWave (Docker)
Et aucune donnée n'est envoyée à un service tiers
Et toutes les données restent dans l'UE
Et l'accès à Matomo est restreint à l'équipe RoadWave
Scénario: IP anonymisées automatiquement
Étant donné que Matomo collecte des données d'usage
Quand une requête est enregistrée
Alors l'adresse IP est automatiquement anonymisée
Et les 2 derniers octets sont remplacés par des zéros
Et une IP 192.168.1.100 devient 192.168.0.0
Et cette anonymisation est irréversible
Et elle est conforme aux recommandations CNIL
Scénario: Configuration Matomo conforme RGPD
Étant donné que Matomo est configuré pour RoadWave
Quand on vérifie les paramètres
Alors les configurations suivantes sont activées:
| paramètre | valeur |
| Anonymisation IP (2 octets) | activé |
| Respect Do Not Track | activé |
| Suppression auto anciens logs (25 mois)| activé |
| Géolocalisation IP désactivée | activé |
| User ID anonymisé | activé |
Et la configuration est RGPD-compliant
# Trackers interdits
Scénario: Aucun tracker tiers utilisé
Étant donné que j'accède à l'application web
Quand j'inspecte les requêtes réseau avec les DevTools
Alors aucune requête n'est envoyée vers les domaines suivants:
| domaine tiers interdit |
| google-analytics.com |
| facebook.com (Pixel) |
| hotjar.com |
| mixpanel.com |
| segment.io |
| amplitude.com |
Et toutes les requêtes analytics vont uniquement vers matomo.roadwave.fr
Scénario: Conformité zéro cookie tiers
Étant donné que j'analyse les cookies déposés sur roadwave.fr
Quand je consulte la liste des cookies
Alors tous les cookies sont first-party (domaine roadwave.fr)
Et aucun cookie tiers (third-party) n'est présent
Et cette politique respecte les recommandations CNIL 2020
# Alternative: Plausible (si besoin)
Scénario: Alternative Plausible SaaS (EU-hosted)
Étant donné que RoadWave pourrait utiliser Plausible au lieu de Matomo
Quand on compare les deux solutions
Alors Plausible a les caractéristiques suivantes:
| caractéristique | valeur |
| Hébergement | UE (Allemagne) |
| Conformité RGPD | Natif (pas de cookie) |
| Coût | 9/mois (50K pageviews) |
| IP anonymisées | Automatique |
| Consentement requis | Non (selon CNIL 2020) |
Mais Matomo self-hosted reste le choix prioritaire (0, contrôle total)
# Souveraineté des données
Scénario: Aucun transfert de données hors UE
Étant donné que Matomo est self-hosted
Quand on analyse les flux de données
Alors aucune donnée d'analytics n'est transférée hors de l'UE
Et les serveurs sont localisés en France
Et aucun transfert vers les US (pas de Privacy Shield / DPF requis)
Et la souveraineté des données est garantie
# Coût de la solution
Scénario: Matomo self-hosted - coût estimé
Étant donné que Matomo est hébergé sur l'infrastructure RoadWave
Quand on calcule le coût mensuel
Alors le coût est d'environ 5/mois:
| composant | coût |
| Serveur supplémentaire | 0 (mutualisé) |
| Base de données MySQL | 0 (mutualisé) |
| Stockage logs (25 mois) | ~5/mois |
| License Matomo | 0 (opensource) |
Et ce coût est marginal comparé à un SaaS tiers (9-50/mois)
# Respect Do Not Track
Scénario: Respect du signal Do Not Track (DNT)
Étant donné que mon navigateur envoie le header "DNT: 1"
Quand j'accède à l'application web
Alors Matomo détecte le signal DNT
Et aucune donnée d'usage n'est collectée
Et aucun cookie `_pk_id` n'est déposé
Et l'application fonctionne normalement
Et un message discret s'affiche: "Vos préférences de confidentialité sont respectées (DNT activé)"
# Suppression automatique des anciens logs
Scénario: Logs Matomo supprimés après 25 mois
Étant donné que Matomo collecte des données d'usage
Quand les logs atteignent 25 mois d'ancienneté
Alors un job automatique supprime les anciens logs
Et seules les données agrégées (rapports) sont conservées
Et les données brutes (logs) sont supprimées définitivement
Et cette politique respecte le principe de minimisation RGPD
# Métriques collectées par Matomo
Scénario: Données collectées par Matomo
Étant donné que j'ai accepté le consentement "Analytique"
Quand je navigue sur l'application web
Alors Matomo collecte les données suivantes:
| donnée collectée | anonymisée |
| Pages visitées | non |
| Durée de visite | non |
| Navigateur / OS | non |
| Résolution écran | non |
| Provenance (referrer) | non |
| IP (2 derniers octets) | oui |
| User ID (hashé) | oui |
Et aucune donnée personnelle identifiable n'est collectée
Scénario: User ID hashé pour analytics
Étant donné que je suis connecté à l'application
Et que j'ai accepté le consentement "Analytique"
Quand Matomo enregistre mes actions
Alors mon user_id est hashé (SHA-256)
Et le hash est 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
Et il est impossible de retrouver mon user_id original depuis ce hash
Et ce processus garantit l'anonymat
# Conformité CNIL 2020
Scénario: Conformité recommandations CNIL sur les cookies
Étant donné que RoadWave utilise Matomo self-hosted
Quand un auditeur CNIL vérifie la conformité
Alors le système respecte les recommandations CNIL 2020:
| recommandation CNIL | respecté |
| Consentement requis pour cookies analytics | oui |
| IP anonymisées | oui |
| Pas de transfert hors UE | oui |
| Durée conservation limitée (25 mois) | oui |
| Respect Do Not Track | oui |
| Transparence (liste cookies dans CGU) | oui |
# Tarteaucitron.js - gestion du consentement
Scénario: Intégration Tarteaucitron.js pour gérer Matomo
Étant donné que Tarteaucitron.js gère les consentements
Quand je personnalise mes consentements
Alors je vois l'option "Analytique (Matomo)"
Et une description est affichée:
"""
Matomo nous aide à comprendre comment vous utilisez l'application pour l'améliorer.
Vos données restent anonymes et ne sont jamais partagées avec des tiers.
"""
Et je peux activer ou désactiver Matomo indépendamment
Et si je désactive, le cookie `_pk_id` est supprimé immédiatement
# Application mobile - analytics
Scénario: Analytics sur application mobile
Étant donné que j'utilise l'application mobile
Quand j'accepte le consentement "Analytique"
Alors l'app utilise le SDK Matomo Mobile
Et les données sont envoyées à la même instance Matomo self-hosted
Et les mêmes règles d'anonymisation s'appliquent
Et aucun SDK tiers (Google Analytics, Firebase) n'est utilisé
Scénario: Refus analytics sur mobile
Étant donné que j'ai refusé le consentement "Analytique" sur mobile
Quand j'utilise l'application
Alors aucune donnée d'usage n'est collectée
Et le SDK Matomo est désactivé
Et l'application fonctionne normalement sans différence d'UX
# Opensource et auditabilité
Scénario: Matomo opensource et auditable
Étant donné que Matomo est opensource
Quand on consulte le code source
Alors le code est disponible publiquement sur GitHub
Et le code peut être audité par des experts indépendants
Et aucune backdoor ou collecte cachée n'est possible
Et cette transparence renforce la confiance utilisateur

View File

@@ -0,0 +1,224 @@
# language: fr
@rgpd @geoip @degraded-mode
Fonctionnalité: Mode dégradé avec GeoIP (sans GPS précis)
# 13.5 - GeoIP par défaut, GPS optionnel (progressive disclosure)
Contexte:
Étant donné que je suis un nouvel utilisateur
Et que je lance l'application pour la première fois
# Niveaux de précision
Plan du Scénario: Trois niveaux de géolocalisation disponibles
Étant donné que j'utilise le niveau de géolocalisation "<niveau>"
Quand le système détermine ma position
Alors la technologie utilisée est "<technologie>"
Et les contenus accessibles sont "<contenus>"
Et le consentement RGPD est "<consentement>"
Exemples:
| niveau | technologie | contenus | consentement |
| Pays | Aucune géoloc | Contenus nationaux uniquement | Non requis |
| Ville | GeoIP (IP2Location) | Contenus régionaux/ville | Non requis |
| Précis | GPS | Tous contenus (hyperlocaux inclus) | Requis |
# Démarrage avec GeoIP automatique
Scénario: GeoIP activé par défaut au premier lancement
Étant donné que je lance l'application pour la première fois
Et que je n'ai pas encore accepté le GPS précis
Quand l'application démarre
Alors le système utilise automatiquement GeoIP basé sur mon adresse IP
Et ma position est détectée au niveau ville: "Paris, France"
Et aucun consentement n'est requis (GeoIP ne collecte pas de données personnelles)
Et je peux accéder aux contenus régionaux et de ville
Scénario: Détection de ville avec IP2Location Lite
Étant donné que mon adresse IP est 93.184.216.34
Quand le système utilise GeoIP IP2Location Lite
Alors ma ville est détectée: "Paris"
Et ma région est détectée: "Île-de-France"
Et mon pays est détecté: "France"
Et la précision est d'environ 80% au niveau ville
Et aucune coordonnée GPS précise n'est révélée
# Banner in-app pour upgrade vers GPS
Scénario: Banner d'invitation à activer le GPS
Étant donné que j'utilise l'application en mode GeoIP
Quand je suis sur l'écran principal
Alors un banner discret s'affiche en haut:
"""
Activez la géolocalisation pour découvrir du contenu près de chez vous
[Activer]
"""
Et le banner n'est pas intrusif (pas de popup modale)
Et je peux le fermer temporairement avec un bouton X
Et le banner réapparaît tous les 7 jours si je ne l'active pas
Scénario: Upgrade volontaire vers GPS depuis le banner
Étant donné que le banner d'invitation au GPS est affiché
Quand je clique sur "Activer"
Alors un écran de consentement GPS s'affiche
Et l'écran explique les avantages:
"""
En activant la géolocalisation précise, vous pouvez:
- Découvrir du contenu hyperlocal près de vous
- Accéder aux audio-guides géolocalisés
- Recevoir des notifications quand vous passez près de points d'intérêt
"""
Et je peux accepter ou refuser
Et si j'accepte, la permission OS est demandée
# Contenus accessibles selon le niveau
Scénario: Contenus disponibles en mode Pays (aucune géoloc)
Étant donné que je n'autorise aucune géolocalisation
Quand le système recherche du contenu à me proposer
Alors seuls les contenus "National" sont disponibles
Et les contenus géolocalisés (Ancré, Contextuel) ne sont pas proposés
Et je vois un message: "Activez la géolocalisation pour plus de contenu local"
Scénario: Contenus disponibles en mode Ville (GeoIP)
Étant donné que j'utilise le mode GeoIP et que je suis détecté à Paris
Quand le système recherche du contenu à me proposer
Alors les contenus suivants sont disponibles:
| type_contenu | disponible |
| National | oui |
| Région Île-de-France | oui |
| Ville Paris | oui |
| Hyperlocal (GPS) | non |
| Audio-guides | non |
Et je reçois des recommandations pertinentes pour Paris
Scénario: Tous contenus disponibles en mode Précis (GPS)
Étant donné que j'ai activé la géolocalisation précise
Quand le système recherche du contenu à me proposer
Alors tous les types de contenus sont disponibles:
| type_contenu | disponible |
| National | oui |
| Régional | oui |
| Ville | oui |
| Hyperlocal (Ancré) | oui |
| Contextuel | oui |
| Audio-guides | oui |
# Pas de consentement requis pour GeoIP
Scénario: GeoIP ne nécessite pas de consentement RGPD
Étant donné que j'utilise le mode GeoIP
Quand un auditeur CNIL vérifie la conformité
Alors GeoIP n'est pas considéré comme une donnée personnelle
Et l'adresse IP n'est pas conservée après détection de la ville
Et seule la ville est stockée (non identifiant)
Et aucun consentement n'est requis conformément au RGPD
# Implémentation IP2Location Lite
Scénario: Base de données IP2Location self-hosted
Étant donné que RoadWave utilise IP2Location Lite
Quand on analyse l'infrastructure
Alors la base de données IP2Location est hébergée sur les serveurs RoadWave
Et aucune requête n'est envoyée à un service tiers
Et la base de données est mise à jour automatiquement chaque mois
Et le coût est de 0 (IP2Location Lite est gratuit)
Scénario: Mise à jour mensuelle de la base GeoIP
Étant donné que IP2Location publie des mises à jour mensuelles
Quand le 1er du mois arrive
Alors un job automatique télécharge la nouvelle base IP2Location Lite
Et la base est mise à jour sans interruption de service
Et un log est créé pour traçabilité
Et si la mise à jour échoue, une alerte est envoyée
# Progressive disclosure - UX dégradée acceptable
Scénario: UX acceptable en mode GeoIP
Étant donné que j'utilise le mode GeoIP à Paris
Quand je parcours l'application
Alors je peux écouter du contenu pertinent pour Paris et l'Île-de-France
Et l'expérience est satisfaisante même sans GPS précis
Et je ne suis pas bloqué dans l'utilisation de l'application
Et je peux choisir d'activer le GPS quand je le souhaite
Scénario: Incitation progressive à activer le GPS
Étant donné que j'utilise le mode GeoIP depuis 2 semaines
Et que je n'ai pas activé le GPS
Quand je consulte un audio-guide dans les résultats de recherche
Alors un message s'affiche:
"""
Cet audio-guide nécessite la géolocalisation précise pour fonctionner.
[Activer le GPS] [Plus tard]
"""
Et si je clique "Plus tard", je peux continuer à utiliser l'app normalement
Et l'incitation reste douce et non intrusive
# Passage d'un mode à l'autre
Scénario: Upgrade GeoIP vers GPS
Étant donné que j'utilise le mode GeoIP
Quand j'active la géolocalisation précise
Alors le système bascule immédiatement en mode GPS
Et les contenus hyperlocaux deviennent disponibles
Et mon feed se rafraîchit avec du contenu plus précis
Et un toast de confirmation s'affiche: "Géolocalisation activée"
Scénario: Downgrade GPS vers GeoIP
Étant donné que j'utilise le mode GPS précis
Quand je désactive la géolocalisation dans les paramètres OS
Alors le système bascule automatiquement en mode GeoIP
Et les contenus hyperlocaux ne sont plus proposés
Et un banner s'affiche: "Géolocalisation désactivée. Seul le contenu régional est disponible."
Et l'application continue de fonctionner normalement
# Détection automatique du mode
Scénario: Détection automatique au démarrage de l'app
Étant donné que j'ouvre l'application
Quand l'app vérifie les permissions de géolocalisation
Alors le système détecte automatiquement le mode disponible:
| permission GPS | consentement app | mode activé |
| Refusée | Non demandé | Pays |
| Refusée | Accepté | GeoIP |
| Accordée | Accepté | GPS précis |
Et le mode est appliqué sans interaction utilisateur
# Précision GeoIP ~80%
Scénario: Précision acceptable pour la plupart des cas
Étant donné que j'habite à Lyon
Et que mon IP est une IP résidentielle standard
Quand le système utilise GeoIP pour me localiser
Alors la ville détectée est "Lyon" (correct à 80%)
Et dans 20% des cas, la ville peut être légèrement erronée (banlieue proche)
Et cette précision est suffisante pour proposer du contenu régional pertinent
Scénario: GeoIP avec VPN ou proxy
Étant donné que j'utilise un VPN avec une IP sortante à Paris
Mais que je suis physiquement à Lyon
Quand le système utilise GeoIP
Alors la ville détectée est "Paris" (IP du VPN)
Et les contenus proposés sont pour Paris
Et je peux activer le GPS précis pour corriger la localisation
# Conformité RGPD
Scénario: Pas de donnée personnelle collectée avec GeoIP
Étant donné que j'utilise le mode GeoIP
Quand le système détermine ma ville via mon IP
Alors l'adresse IP n'est pas conservée après détection
Et seule la ville "Paris" est stockée en base de données
Et la ville seule n'est pas une donnée personnelle (RGPD)
Et aucun consentement n'est donc requis
# Coût de la solution: 0€
Scénario: Solution GeoIP gratuite et self-hosted
Étant donné que RoadWave utilise IP2Location Lite
Quand on calcule le coût de la solution
Alors le coût est de 0
Et la solution est opensource
Et la base de données est hébergée sur les serveurs RoadWave
Et aucun coût SaaS tiers

View File

@@ -0,0 +1,282 @@
# language: fr
@rgpd @data-portability
Fonctionnalité: Portabilité des données (Article 20 RGPD)
# 13.3 - Export JSON + HTML + ZIP, génération asynchrone
Contexte:
Étant donné que je suis un utilisateur connecté
Et que j'ai utilisé l'application depuis 6 mois
Scénario: Demande d'export depuis les paramètres
Étant donné que je suis dans "Paramètres > Confidentialité"
Quand je clique sur "Exporter mes données"
Alors une page d'information s'affiche expliquant:
"""
Vous allez recevoir une archive contenant toutes vos données personnelles:
- Profil et informations de compte
- Historique d'écoute complet
- Contenus audio que vous avez créés
- Abonnements et likes
- Centres d'intérêt
- Historique des consentements
L'export sera généré sous 48h maximum et vous recevrez un email avec un lien de téléchargement valide 7 jours.
"""
Et un bouton "Confirmer l'export" est disponible
Scénario: Confirmation et démarrage de l'export
Étant donné que je clique sur "Confirmer l'export"
Quand la demande est validée
Alors un message de confirmation s'affiche:
"""
Votre demande d'export a été prise en compte. Vous recevrez un email sous 48h avec un lien de téléchargement.
"""
Et un worker background démarre la génération de l'export
Et le statut de l'export est "En cours de génération"
Et je peux voir le statut dans "Paramètres > Confidentialité > Mes exports"
# Structure de l'export
Scénario: Contenu de l'archive ZIP
Étant donné que mon export est généré
Quand je télécharge et ouvre l'archive
Alors l'archive a la structure suivante:
"""
export-roadwave-[user_id]-[date].zip
export.json # Machine-readable (JSON complet)
index.html # Human-readable (stylé, navigation)
audio/
content-123.opus # Mes contenus créés
content-456.opus
...
README.txt # Instructions et informations
"""
Et tous les fichiers sont inclus
Scénario: Contenu du fichier export.json
Étant donné que j'ouvre le fichier export.json
Quand j'analyse le contenu
Alors le JSON contient les sections suivantes:
| section | description |
| profile | Email, pseudo, date inscription, bio |
| listening_history | Historique complet d'écoute |
| created_contents | Métadonnées des contenus créés |
| subscriptions | Liste des créateurs suivis |
| likes | Liste des contenus likés |
| interest_gauges | Valeurs des jauges d'intérêt |
| consent_history | Historique des consentements |
| premium_subscription | Informations abonnement Premium |
Et le JSON est formaté de manière lisible (indentation)
Et toutes les dates sont au format ISO 8601
Scénario: Contenu du fichier index.html
Étant donné que j'ouvre le fichier index.html dans un navigateur
Quand la page se charge
Alors je vois un site web stylé avec navigation
Et les sections suivantes sont affichées:
| section | contenu |
| Mon profil | Email, pseudo, date inscription, statistiques |
| Historique d'écoute | Liste paginée avec dates, titres, durées |
| Mes contenus | Liste avec lectures audio intégrées |
| Mes abonnements | Grille des créateurs suivis |
| Mes likes | Liste des contenus likés avec liens |
| Centres d'intérêt | Graphiques des jauges |
| Consentements | Historique des acceptations/refus |
Et la navigation est intuitive (menu latéral)
Et le design est responsive (mobile/desktop)
Scénario: Fichiers audio inclus dans l'export
Étant donné que j'ai créé 5 contenus audio
Quand mon export est généré
Alors le dossier `audio/` contient mes 5 fichiers
Et les fichiers sont au format Opus original
Et chaque fichier est nommé: `content-[id].opus`
Et les fichiers audio correspondent aux métadonnées dans export.json
Scénario: Fichier README.txt explicatif
Étant donné que j'ouvre le fichier README.txt
Quand je lis le contenu
Alors le fichier explique:
"""
Bienvenue dans votre export de données RoadWave.
Contenu de l'archive:
- export.json: Toutes vos données au format JSON (machine-readable)
- index.html: Visualisation web de vos données (ouvrir dans un navigateur)
- audio/: Vos contenus audio créés au format Opus
Cet export a été généré le [date] et contient toutes vos données personnelles conformément à l'article 20 du RGPD.
Pour toute question: dpo@roadwave.fr
"""
# Données exportées en détail
Plan du Scénario: Données de profil exportées
Étant donné que mon export est généré
Quand j'ouvre export.json et lis la section "profile"
Alors je trouve les données suivantes:
| champ | exemple |
| email | user@example.com |
| pseudo | @roadwave_user |
| date_inscription | 2025-01-15T10:30:00Z |
| bio | Passionné d'automobile... |
| avatar_url | https://cdn.roadwave.fr/... |
| compte_verifie | false |
| premium | true |
Scénario: Historique d'écoute exporté
Étant donné que j'ai écouté 150 contenus depuis 6 mois
Quand mon export est généré
Alors la section "listening_history" contient 150 entrées
Et chaque entrée contient:
| champ | exemple |
| content_id | C123 |
| content_title | Histoire de la Tour Eiffel |
| creator_name | @historien_paris |
| listened_at | 2025-01-20T15:30:00Z |
| duration_listened | 180 (secondes) |
| completion_rate | 0.85 (85%) |
| location | [geohash ou coords précises]|
Et les contenus sont triés par date décroissante
Scénario: Centres d'intérêt exportés
Étant donné que mes jauges d'intérêt sont:
| catégorie | valeur |
| Automobile | 78% |
| Voyage | 65% |
| Musique | 52% |
| Politique | 30% |
Quand mon export est généré
Alors la section "interest_gauges" contient ces valeurs
Et chaque jauge indique la date de dernière modification
Scénario: Historique des consentements exporté
Étant donné que j'ai modifié mes consentements plusieurs fois
Quand mon export est généré
Alors la section "consent_history" contient:
| date | consent_type | accepted | version |
| 2025-01-15T10:00 | Fonctionnel | oui | 1 |
| 2025-01-15T10:00 | Analytique | oui | 1 |
| 2025-01-15T10:00 | Marketing | non | 1 |
| 2025-03-20T14:30 | Analytique | non | 1 |
Et l'historique complet est visible
# Génération asynchrone
Scénario: Génération asynchrone pour éviter timeout
Étant donné que j'ai beaucoup de données (500 contenus créés, 10 000 écoutes)
Quand je demande un export
Alors la génération se fait en arrière-plan via un worker
Et la page web ne timeout pas
Et je peux continuer à utiliser l'application pendant la génération
Et je reçois un email quand l'export est prêt
Scénario: Délai de génération conforme RGPD
Étant donné que je demande un export le 2025-01-20 à 10:00
Quand le worker génère l'export
Alors l'export est disponible maximum 48h plus tard (avant le 2025-01-22 à 10:00)
Et la plupart des exports sont prêts en moins de 6h
Et le délai respecte l'article 20 du RGPD
Scénario: Email de notification avec lien de téléchargement
Étant donné que mon export est terminé
Quand le worker finalise la génération
Alors je reçois un email avec le sujet "Votre export de données RoadWave est prêt"
Et l'email contient:
"""
Bonjour,
Votre export de données est prêt. Vous pouvez le télécharger via le lien ci-dessous:
[Télécharger mon export] (lien sécurisé, expire dans 7 jours)
Taille de l'archive: 45 MB
Contenu: Profil, historique, contenus audio, abonnements, likes, centres d'intérêt
Pour toute question: dpo@roadwave.fr
"""
Et le lien de téléchargement est sécurisé (token unique)
Scénario: Lien de téléchargement expire après 7 jours
Étant donné que mon export a été généré le 2025-01-20
Et que je reçois le lien de téléchargement
Quand j'essaie d'accéder au lien le 2025-01-28 (8 jours plus tard)
Alors le lien est expiré
Et je reçois un message "Ce lien a expiré. Veuillez demander un nouvel export."
Et je peux demander un nouvel export si nécessaire
# Limite anti-abus
Scénario: Limite de 1 export par mois
Étant donné que j'ai demandé un export le 2025-01-15
Quand j'essaie de demander un nouvel export le 2025-01-20
Alors je reçois un message d'erreur:
"""
Vous avez déjà demandé un export ce mois-ci. Vous pourrez demander un nouvel export à partir du 2025-02-15.
"""
Et le bouton "Confirmer l'export" est désactivé
Et la date du prochain export possible est affichée
Scénario: Nouvel export possible après 1 mois
Étant donné que j'ai demandé un export le 2025-01-15
Quand la date atteint le 2025-02-15
Alors je peux demander un nouvel export
Et le bouton "Confirmer l'export" est actif
Et aucune limite ne s'applique
# Sécurité
Scénario: Lien de téléchargement sécurisé avec token unique
Étant donné que mon export est prêt
Quand je reçois le lien de téléchargement
Alors le lien contient un token unique et non devinable
Et le format du lien est: `https://roadwave.fr/exports/download/[token_unique]`
Et le token est valide uniquement pour mon compte
Et le token expire après 7 jours ou après 3 téléchargements
Scénario: Vérification de l'authentification avant téléchargement
Étant donné que je reçois le lien d'export
Quand je clique sur le lien
Alors le système vérifie que je suis connecté
Et si je ne suis pas connecté, je suis redirigé vers la page de connexion
Et après connexion, le téléchargement démarre automatiquement
Et seul le propriétaire du compte peut télécharger l'export
# Conformité RGPD Article 20
Scénario: Conformité portabilité des données
Étant donné que mon export est généré
Quand un auditeur RGPD vérifie la conformité
Alors l'export respecte les exigences de l'article 20:
| exigence RGPD | respecté |
| Format structuré (JSON) | oui |
| Format couramment utilisé | oui |
| Format lisible par machine | oui |
| Format interopérable | oui |
| Délai raisonnable (48h max) | oui |
| Exhaustivité des données | oui |
| Gratuité pour l'utilisateur | oui |
Scénario: Gratuité de l'export
Étant donné que je demande un export de mes données
Quand l'export est généré et téléchargé
Alors aucun coût n'est facturé
Et l'export est entièrement gratuit
Et aucune inscription Premium n'est requise
Et le droit à la portabilité est accessible à tous les utilisateurs
# Statut de l'export visible
Scénario: Suivi du statut de génération
Étant donné que j'ai demandé un export
Quand j'ouvre "Paramètres > Confidentialité > Mes exports"
Alors je vois le statut actuel:
| statut | description |
| En cours de génération| Worker en train de générer l'archive |
| Prêt au téléchargement| Lien de téléchargement disponible |
| Expiré | Lien expiré (>7j), nouvel export requis |
Et la date de demande est affichée
Et la taille estimée de l'archive est visible

View File

@@ -0,0 +1,254 @@
# language: fr
@rgpd @account-deletion
Fonctionnalité: Suppression du compte utilisateur (Article 17 RGPD - Droit à l'effacement)
# 13.4 - Grace period 30j + anonymisation contenus
Contexte:
Étant donné que je suis un utilisateur connecté
Et que j'ai utilisé l'application depuis plusieurs mois
Scénario: Demande de suppression depuis les paramètres
Étant donné que je suis dans "Paramètres > Compte"
Quand je clique sur "Supprimer mon compte"
Alors une page d'avertissement s'affiche avec le message:
"""
Attention: La suppression de votre compte est définitive.
Que se passe-t-il lors de la suppression?
- Votre compte sera désactivé immédiatement (connexion impossible)
- Vos données personnelles seront supprimées dans 30 jours
- Vos contenus créés resteront disponibles de manière anonyme
- Vous pouvez annuler la suppression dans les 30 jours
Voulez-vous vraiment continuer?
"""
Et deux boutons sont disponibles: "Annuler" et "Confirmer la suppression"
Scénario: Confirmation de suppression avec mot de passe
Étant donné que je clique sur "Confirmer la suppression"
Quand un formulaire de confirmation s'affiche
Alors je dois entrer mon mot de passe pour confirmer
Et je dois cocher "Je comprends que cette action est définitive"
Et un captcha peut être requis pour éviter les suppressions automatisées
Quand je valide le formulaire
Alors la suppression est initiée
# Désactivation immédiate
Scénario: Compte désactivé immédiatement après confirmation
Étant donné que j'ai confirmé la suppression de mon compte
Quand la demande est traitée
Alors mon compte est désactivé immédiatement
Et je suis déconnecté de toutes mes sessions
Et je ne peux plus me reconnecter
Et si j'essaie de me connecter, je reçois le message:
"""
Votre compte est en cours de suppression. Si vous souhaitez annuler, cliquez sur le lien reçu par email.
"""
Scénario: Contenus cachés pendant le grace period
Étant donné que mon compte est en cours de suppression
Quand un autre utilisateur recherche mes contenus
Alors mes contenus ne sont plus diffusés dans l'application
Et mes contenus n'apparaissent plus dans les recherches
Et mes contenus ne sont plus recommandés
Mais mes contenus ne sont pas encore supprimés définitivement
# Email de confirmation avec lien d'annulation
Scénario: Email de confirmation envoyé immédiatement
Étant donné que j'ai confirmé la suppression de mon compte
Quand la demande est traitée
Alors je reçois un email avec le sujet "Confirmation de suppression de votre compte RoadWave"
Et l'email contient:
"""
Bonjour,
Votre compte RoadWave est en cours de suppression. Vos données seront définitivement supprimées le [date + 30j].
Si vous changez d'avis, vous pouvez annuler la suppression en cliquant sur le lien ci-dessous (valide 30 jours):
[Annuler la suppression]
Que se passe-t-il ensuite?
- Votre compte est désactivé immédiatement
- Vos contenus sont masqués de l'application
- Vos données seront supprimées dans 30 jours
- Vous pouvez annuler à tout moment pendant ces 30 jours
Pour toute question: dpo@roadwave.fr
"""
Et le lien d'annulation est valide 30 jours
# Grace period de 30 jours
Scénario: Annulation de la suppression dans les 30 jours
Étant donné que j'ai demandé la suppression de mon compte le 2025-01-20
Et que je reçois l'email de confirmation
Quand je clique sur le lien "Annuler la suppression" le 2025-02-05 (16 jours plus tard)
Alors mon compte est réactivé immédiatement
Et je peux me reconnecter normalement
Et mes contenus redeviennent visibles dans l'application
Et toutes mes données sont restaurées
Et je reçois un email de confirmation: "Votre compte a été réactivé"
Scénario: Lien d'annulation expire après 30 jours
Étant donné que j'ai demandé la suppression de mon compte le 2025-01-20
Quand j'essaie de cliquer sur le lien d'annulation le 2025-02-25 (36 jours plus tard)
Alors le lien est expiré
Et je reçois un message "Ce lien a expiré. Votre compte a été définitivement supprimé."
Et la suppression effective a déjà eu lieu
# Suppression effective après 30 jours
Scénario: Suppression effective sans annulation
Étant donné que j'ai demandé la suppression de mon compte le 2025-01-20
Et que je n'ai pas cliqué sur le lien d'annulation
Quand la date atteint le 2025-02-19 (30 jours plus tard)
Alors un job automatique exécute la suppression définitive
Et toutes mes données personnelles sont supprimées
# Données supprimées
Scénario: Liste des données supprimées définitivement
Étant donné que la suppression effective est exécutée
Quand le job de suppression se termine
Alors les données suivantes sont supprimées:
| données | supprimé |
| Compte utilisateur (email, mdp) | oui |
| Profil (pseudo, bio, avatar) | oui |
| Historique d'écoute | oui |
| Historique GPS | oui |
| Centres d'intérêt (jauges) | oui |
| Sessions et tokens | oui |
| Likes et abonnements | oui |
| Notifications non lues | oui |
| Historique consentements | oui |
| Données de paiement | oui |
Et ces suppressions sont irréversibles
# Contenus conservés anonymement
Scénario: Anonymisation des contenus créés
Étant donné que j'ai créé 10 contenus audio
Quand la suppression effective est exécutée
Alors mes contenus audio restent disponibles dans l'application
Et le nom du créateur devient "Utilisateur supprimé"
Et mon pseudo n'est plus visible
Et les métadonnées (titre, description, tags, géolocalisation) sont conservées
Et les fichiers audio restent sur le CDN
Et les statistiques d'écoute sont conservées
Scénario: Justification de l'anonymisation (intérêt légitime)
Étant donné que mes contenus sont conservés anonymement
Quand un auditeur RGPD vérifie la conformité
Alors la conservation est justifiée par l'intérêt légitime de la communauté
Et les contenus ne contiennent plus de données personnelles identifiables
Et la suppression complète nuirait à l'expérience des autres utilisateurs
Et cette pratique est conforme au RGPD si anonymisation réelle
Scénario: Contenu anonymisé visible pour les autres utilisateurs
Étant donné que mon compte a été supprimé
Et que mes contenus ont été anonymisés
Quand un utilisateur consulte un de mes anciens contenus
Alors le créateur affiché est "Utilisateur supprimé"
Et le profil du créateur n'est plus accessible
Et le contenu reste écoutable normalement
Et les likes et statistiques sont conservés
# Likes et abonnements supprimés
Scénario: Suppression de mes likes avec conservation des compteurs
Étant donné que j'avais liké 50 contenus
Quand la suppression effective est exécutée
Alors mes likes sont supprimés de la base de données
Mais les compteurs de likes sur les contenus sont préservés
Et les créateurs ne perdent pas leurs statistiques
Et seule la relation "user X a liké content Y" est supprimée
Scénario: Suppression de mes abonnements
Étant donné que je suivais 20 créateurs
Quand la suppression effective est exécutée
Alors mes abonnements sont supprimés
Et les compteurs d'abonnés des créateurs sont décrémentés de 1
Et les créateurs ne reçoivent pas de notification de désabonnement
# Sessions et tokens révoqués
Scénario: Révocation de tous les tokens immédiatement
Étant donné que je suis connecté sur 3 appareils (mobile, tablette, web)
Quand je demande la suppression de mon compte
Alors tous mes tokens d'authentification sont révoqués immédiatement
Et je suis déconnecté de tous mes appareils
Et toute tentative de reconnexion échoue
# Notifications avant suppression
Scénario: Rappels par email pendant le grace period
Étant donné que j'ai demandé la suppression de mon compte le 2025-01-20
Quand le grace period s'écoule
Alors je reçois des emails de rappel:
| date | jours restants | sujet email |
| 2025-02-04 | 15 jours | Plus que 15 jours pour annuler la suppression |
| 2025-02-12 | 7 jours | Dernière semaine pour annuler la suppression |
| 2025-02-17 | 2 jours | Attention: suppression définitive dans 2 jours |
Et chaque email contient le lien d'annulation
# Conformité RGPD Article 17
Scénario: Conformité droit à l'effacement
Étant donné que la suppression de mon compte est complète
Quand un auditeur RGPD vérifie la conformité
Alors le processus respecte l'article 17 du RGPD:
| exigence RGPD | respecté |
| Suppression de toutes les données personnelles | oui |
| Délai raisonnable (30j grace period acceptable)| oui |
| Possibilité d'annulation (bonne pratique) | oui |
| Anonymisation des contenus (intérêt légitime) | oui |
| Révocation des tokens et sessions | oui |
| Suppression irréversible | oui |
# Cas particuliers
Scénario: Suppression d'un compte Premium
Étant donné que j'ai un abonnement Premium actif
Quand je demande la suppression de mon compte
Alors mon abonnement est annulé immédiatement
Et aucun remboursement n'est effectué (conformément aux CGV)
Et je reçois un email de confirmation d'annulation de l'abonnement
Et le reste du processus de suppression se déroule normalement
Scénario: Suppression d'un compte créateur avec revenus en attente
Étant donné que je suis un créateur avec 75 de revenus en attente de paiement
Quand je demande la suppression de mon compte
Alors un message m'informe:
"""
Vous avez 75 de revenus en attente. Souhaitez-vous recevoir ce paiement avant suppression?
"""
Et je peux choisir "Recevoir le paiement et attendre" ou "Renoncer au paiement"
Et si je choisis "Recevoir le paiement", la suppression est repoussée de 7 jours
Scénario: Suppression avec signalements de modération en cours
Étant donné que j'ai 2 signalements en cours de traitement
Quand je demande la suppression de mon compte
Alors les signalements sont automatiquement clôturés
Et les contenus signalés sont masqués immédiatement
Et aucune sanction n'est appliquée (compte déjà en suppression)
# Traçabilité pour audit
Scénario: Log de la suppression pour traçabilité
Étant donné que la suppression effective est exécutée
Quand le job de suppression se termine
Alors un log est créé avec:
| champ | valeur |
| user_id | [ID anonymisé] |
| deletion_requested_at | 2025-01-20T10:00:00Z |
| deletion_executed_at | 2025-02-19T02:00:00Z |
| deletion_cancelled | false |
| data_deleted | [liste des tables] |
| contents_anonymized | 10 |
Et ce log est conservé 5 ans pour audit RGPD
Et l'user_id est pseudonymisé pour anonymat