veza/veza-backend-api/docs/AUTH_PASSWORD_RESET.md
okinrev b7955a680c P0: stabilisation backend/chat/stream + nouvelle base migrations v1
Backend Go:
- Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN.
- Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError).
- Sécurisation de config.go, CORS, statuts de santé et monitoring.
- Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles).
- Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés.
- Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*.

Chat server (Rust):
- Refonte du pipeline JWT + sécurité, audit et rate limiting avancé.
- Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing).
- Nettoyage des panics, gestion d’erreurs robuste, logs structurés.
- Migrations chat alignées sur le schéma UUID et nouvelles features.

Stream server (Rust):
- Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core.
- Transactions P0 pour les jobs et segments, garanties d’atomicité.
- Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION).

Documentation & audits:
- TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services.
- Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3).
- Scripts de reset et de cleanup pour la lab DB et la V1.

Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 11:14:38 +01:00

12 KiB

AUTH_PASSWORD_RESET.md

📋 Vue d'ensemble

Ce document décrit le système complet de réinitialisation de mot de passe (password reset) implémenté dans veza-backend-api. Le système permet aux utilisateurs de réinitialiser leur mot de passe de manière sécurisée via un flux en deux étapes : demande de reset et confirmation avec token.

🎯 Objectifs

  • Permettre aux utilisateurs de réinitialiser leur mot de passe en cas d'oubli
  • Garantir la sécurité via des tokens à usage unique avec expiration
  • Prévenir l'énumération d'emails (email enumeration)
  • Invalider automatiquement les sessions existantes après reset

🔄 Flux global

1. User → POST /api/v1/auth/password/reset-request
   └─> Email fourni
   └─> Si email existe → Génération token + Stockage DB + Envoi email
   └─> Réponse générique (toujours succès pour sécurité)

2. User → Email reçu avec lien contenant token
   └─> Clic sur lien → Frontend avec token en paramètre

3. User → POST /api/v1/auth/password/reset
   └─> Token + Nouveau mot de passe
   └─> Vérification token (valide, non expiré, non utilisé)
   └─> Hash nouveau mot de passe
   └─> Mise à jour password_hash en DB
   └─> Invalidation token (marqué comme utilisé)
   └─> Invalidation sessions utilisateur (revoke refresh tokens)

📡 Contrat API

Endpoint 1 : Request Password Reset

Route : POST /api/v1/auth/password/reset-request

Request Body :

{
  "email": "user@example.com"
}

Response (200 OK) :

{
  "message": "If the email exists, a reset link has been sent"
}

Comportement :

  • Si l'email existe : génération token, stockage DB, envoi email
  • Si l'email n'existe pas : même réponse (prévention énumération)
  • Toujours retourne 200 OK avec message générique

Codes d'erreur :

  • 400 Bad Request : Email invalide (format)
  • 500 Internal Server Error : Erreur serveur (génération token, stockage DB)

Endpoint 2 : Confirm Password Reset

Route : POST /api/v1/auth/password/reset

Request Body :

{
  "token": "base64-url-safe-token-here",
  "new_password": "NewSecurePassword123!"
}

Response (200 OK) :

{
  "message": "Password reset successfully"
}

Codes d'erreur :

  • 400 Bad Request :
    • Token invalide ou expiré
    • Token déjà utilisé
    • Mot de passe trop faible (validation)
    • Format de requête invalide

Comportement :

  • Vérifie token (existe, non expiré, non utilisé)
  • Valide force du mot de passe
  • Hash nouveau mot de passe (bcrypt, cost 12)
  • Met à jour password_hash dans table users
  • Marque token comme utilisé
  • Invalide toutes les sessions utilisateur (revoke refresh tokens)

🔒 Sécurité

Tokens

  • Génération : 32 bytes aléatoires, encodés en base64 URL-safe
  • Expiration : 1 heure (configurable via PasswordResetService)
  • Usage unique : Token marqué comme used = TRUE après utilisation
  • Invalidation : Tous les tokens précédents d'un utilisateur sont invalidés lors d'une nouvelle demande

Prévention d'énumération

  • Réponse uniforme : Toujours retourner le même message, même si l'email n'existe pas
  • Pas de timing attack : Même temps de traitement pour email existant/non existant
  • Logs sécurisés : Jamais logger le token complet, seulement un preview (8 premiers caractères)

Invalidation des sessions

Après un reset de mot de passe réussi :

  • Tous les refresh tokens de l'utilisateur sont révoqués
  • Les sessions actives sont invalidées
  • L'utilisateur doit se reconnecter avec le nouveau mot de passe

Hash des mots de passe

  • Algorithme : bcrypt
  • Cost : 12 (équilibre sécurité/performance)
  • Stockage : Champ password_hash dans table users

🗄️ Modèle de données

Table password_reset_tokens

CREATE TABLE public.password_reset_tokens (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
    
    -- Token
    token VARCHAR(255) NOT NULL UNIQUE,
    token_hash VARCHAR(255) NOT NULL,  -- Pour future amélioration
    
    -- Status
    used BOOLEAN NOT NULL DEFAULT false,
    used_at TIMESTAMPTZ,
    expires_at TIMESTAMPTZ NOT NULL,
    
    -- Metadata
    ip_address INET,
    user_agent TEXT,
    
    -- Timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    CONSTRAINT chk_password_reset_expires CHECK (expires_at > created_at)
);

Indexes :

  • idx_password_reset_tokens_user_id sur user_id
  • idx_password_reset_tokens_token_hash sur token_hash
  • idx_password_reset_tokens_expires_at sur expires_at

Règles :

  • Un token est valide si : used = FALSE ET expires_at > NOW()
  • Sur nouvelle demande, tous les tokens précédents (used = FALSE) sont invalidés

🏗️ Architecture

Services

PasswordResetService (internal/services/password_reset_service.go)

Méthodes principales :

  • GenerateToken() (string, error) : Génère un token aléatoire sécurisé
  • StoreToken(userID uuid.UUID, token string) error : Stocke le token en DB
  • VerifyToken(token string) (uuid.UUID, error) : Vérifie et retourne userID
  • MarkTokenAsUsed(token string) error : Marque le token comme utilisé
  • InvalidateOldTokens(userID uuid.UUID) error : Invalide tous les tokens précédents

PasswordService (internal/services/password_service.go)

Méthodes utilisées :

  • GetUserByEmail(email string) (*UserInfo, error) : Récupère utilisateur par email
  • ValidatePassword(password string) error : Valide la force du mot de passe
  • UpdatePassword(userID uuid.UUID, newPassword string) error : Met à jour le mot de passe

EmailService (internal/services/email_service.go)

Méthodes utilisées :

  • SendPasswordResetEmail(userID uuid.UUID, email string, token string) error : Envoie l'email de reset

AuthService (internal/core/auth/service.go)

Méthodes principales :

  • RequestPasswordReset(ctx context.Context, email string) error : Orchestre la demande de reset
  • ResetPassword(ctx context.Context, token string, newPassword string) error : Orchestre la confirmation de reset

Handlers

RequestPasswordReset (internal/handlers/password_reset_handler.go)

Handler HTTP pour la demande de reset :

  • Valide l'email
  • Trouve l'utilisateur (ou retourne succès générique)
  • Génère et stocke le token
  • Envoie l'email
  • Retourne réponse générique

ResetPassword (internal/handlers/password_reset_handler.go)

Handler HTTP pour la confirmation de reset :

  • Valide le token
  • Valide le nouveau mot de passe
  • Met à jour le mot de passe
  • Marque le token comme utilisé
  • Invalide les sessions utilisateur

⚙️ Configuration

Variables d'environnement

# URL du frontend (pour construire le lien de reset)
FRONTEND_URL=http://localhost:5173  # Défaut si non défini

# Configuration SMTP (pour envoi emails)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-email@example.com
SMTP_PASSWORD=your-password
FROM_EMAIL=noreply@veza.com
FROM_NAME=Veza

Configuration du service

Le PasswordResetService utilise une expiration de 1 heure par défaut (non configurable actuellement, hardcodé dans StoreToken).


🧪 Tests

Tests unitaires

Fichier : internal/core/auth/service_test.go (à créer)

Tests à implémenter :

  • TestAuthService_RequestPasswordReset_UserExists : Token généré et stocké
  • TestAuthService_RequestPasswordReset_UserNotExists : Retourne nil (pas d'erreur)
  • TestAuthService_ResetPassword_ValidToken : Mot de passe mis à jour
  • TestAuthService_ResetPassword_ExpiredToken : Erreur "token expired"
  • TestAuthService_ResetPassword_UsedToken : Erreur "token already used"
  • TestAuthService_ResetPassword_InvalidToken : Erreur "invalid token"

Tests d'intégration

Fichier : tests/integration/password_reset_test.go (à créer)

Test complet du flux :

  1. Créer un utilisateur en DB
  2. Appeler /api/v1/auth/password/reset-request
  3. Récupérer le token en DB
  4. Appeler /api/v1/auth/password/reset avec le token
  5. Vérifier que le nouveau mot de passe permet un login

Note : Peut être marqué comme t.Skip si l'infra de test n'est pas configurée.

Lancer les tests

# Tests unitaires du service auth
cd veza-backend-api
go test ./internal/core/auth -run TestAuthService.*PasswordReset -v

# Tests d'intégration (si configurés)
go test ./tests/integration -run TestPasswordReset -v

📝 Logs

Ce qui est loggé

  • RequestPasswordReset :

    • Info : "Password reset requested successfully" (avec email, user_id, token preview)
    • Error : Erreurs de génération token, stockage, envoi email
    • Warn : Échec invalidation anciens tokens (non bloquant)
  • ResetPassword :

    • Info : "Password reset completed successfully" (avec user_id)
    • Warn : Token invalide/expiré/utilisé, validation mot de passe échouée
    • Error : Erreurs de mise à jour mot de passe
    • Warn : Échec marquage token comme utilisé (non bloquant)
    • Warn : Échec invalidation sessions (non bloquant)

Ce qui n'est JAMAIS loggé

  • Token complet : Seulement un preview (8 premiers caractères + "...")
  • Nouveau mot de passe : Jamais loggé, même hashé
  • Email utilisateur : Loggé uniquement pour debugging (peut être masqué en production)

🔧 Maintenance

Nettoyage des tokens expirés

Les tokens expirés peuvent être nettoyés périodiquement via un job de maintenance :

DELETE FROM password_reset_tokens 
WHERE expires_at < NOW() - INTERVAL '7 days' 
  AND used = TRUE;

Note : Un job de cleanup n'est pas encore implémenté, mais peut être ajouté dans internal/jobs/.

Monitoring

Métriques à surveiller :

  • Nombre de demandes de reset par jour
  • Taux d'échec de vérification token (tokens expirés/invalides)
  • Taux de succès de reset (token utilisé avec succès)
  • Temps moyen entre demande et confirmation

🐛 Dépannage

Problème : Token invalide ou expiré

Causes possibles :

  • Token déjà utilisé
  • Token expiré (> 1h)
  • Token incorrect (copie/collage partiel)

Solution : Demander un nouveau token via /api/v1/auth/password/reset-request

Problème : Email non reçu

Causes possibles :

  • Configuration SMTP incorrecte
  • Email dans spam
  • Email invalide

Vérifications :

  • Logs serveur pour erreurs SMTP
  • Vérifier SMTP_* variables d'environnement
  • Vérifier que l'utilisateur existe en DB

Problème : Sessions non invalidées après reset

Cause : Échec de refreshTokenService.RevokeAll()

Solution : Vérifier les logs, le mot de passe est déjà mis à jour (non bloquant)


📚 Références

  • Migration : migrations/010_auth_and_users.sql (table password_reset_tokens)
  • Service : internal/services/password_reset_service.go
  • Handler : internal/handlers/password_reset_handler.go
  • Auth Service : internal/core/auth/service.go
  • Router : internal/api/router.go (routes /api/v1/auth/password/*)

Checklist de validation

  • Endpoints fonctionnels (/reset-request et /reset)
  • Tokens stockés en DB avec expiration
  • Tokens invalidés après usage
  • Prévention énumération emails (réponse uniforme)
  • Invalidation sessions après reset
  • Validation force mot de passe
  • Logs sécurisés (pas de token complet)
  • Documentation complète
  • Tests unitaires complets (à compléter)
  • Test d'intégration (à compléter si infra disponible)

Dernière mise à jour : 2025-01-XX
Version : 1.0.0
Auteur : Équipe Veza Backend