# ALDANA Phase 3 — Auth & Espace membre Implementation Plan

**Goal:** Mettre en place l'authentification complète (login, register, password reset, email verification), les sessions sécurisées, le CSRF, le rate limiting login, et un espace membre minimal (dashboard + édition profil). Les sous-sections /mes-reservations, /mes-forfaits, /mes-factures sont créées en pages "à venir" (elles seront peuplées en P4/P5 quand les données existeront).

**Architecture:** AuthService centralise toute la logique métier (signup, signin, password hashing, token generation, lockout). UserRepository fait les requêtes PDO. AuthController et AccountController appellent le service. Middleware `AuthMiddleware` protège `/mon-compte/*`. Helpers globaux `auth_user()` / `is_logged_in()` / `csrf_check()`. Layout auth dépouillé pour login/register, layout account avec sidebar pour les pages membre.

**Tech Stack:** PHP 8.2+ Argon2id, Symfony Mailer (mode `null://null` en dev, log dans storage/logs/mail.log), PDO prepared statements, sessions PHP sécurisées (httponly + secure + samesite + strict_mode + regenerate au login).

**Pré-requis :** P0/P1/P2 livrées. Table `t_users` peuplée (Aurane admin existante).

---

## Sécurité critique (résumé spec §7)

| Item | Implémentation |
|---|---|
| Passwords | `password_hash($pw, PASSWORD_ARGON2ID)` + `password_verify` |
| Sessions | httponly + secure(HTTPS) + samesite=Lax + strict_mode (déjà OK P0) + `session_regenerate_id(true)` au login |
| CSRF | Token par session, champ `_csrf` sur tout POST, middleware bloquant |
| Rate limit login | `failed_login_attempts` + `locked_until` sur t_users (déjà existants migration 001) ; 5 échecs → lockout 1h |
| Reset token | `random_bytes(32)` hex (64 chars), expirable 1h, single-use (cleared après usage) |
| Email verify | Token similaire, expirable 24h, non bloquant pour V1 (on autorise login avant vérif) |
| Email envoi | DSN `null://null` en dev, le contenu est logué dans `storage/logs/mail.log` |

---

## File Structure

```
src/
├── Controllers/
│   ├── AuthController.php          ← Task 9 (login, register, forgot, reset, verify, logout)
│   └── AccountController.php       ← Task 14 (dashboard, profil, placeholders P4/P5)
├── Middleware/
│   ├── AuthMiddleware.php          ← Task 4 (redirect /login si pas auth)
│   └── CsrfMiddleware.php          ← Task 4 (vérif _csrf sur POST)
├── Repositories/
│   └── UserRepository.php          ← Task 1
├── Services/
│   ├── AuthService.php             ← Task 3 (signup/signin/reset/verify/tokens)
│   └── EmailService.php            ← Task 2 (Symfony Mailer wrapper)
└── Helpers/
    └── functions.php               ← MODIF Task 5 (auth_user, is_logged_in, csrf_check, old)

views/
├── layouts/
│   ├── auth.php                    ← Task 6 (dépouillé, centré)
│   └── account.php                 ← Task 7 (avec sidebar)
├── partials/
│   ├── account-sidebar.php         ← Task 8
│   └── navbar.php                  ← MODIF Task 16 (lien Mon compte/Se connecter)
└── pages/
    ├── auth/                       ← Task 10 (5 pages)
    │   ├── login.php
    │   ├── register.php
    │   ├── forgot.php
    │   ├── reset.php
    │   └── verify-sent.php
    └── account/                    ← Task 15 (5 pages)
        ├── dashboard.php
        ├── profil.php
        ├── reservations.php        (placeholder "P4")
        ├── forfaits.php            (placeholder "P5")
        └── factures.php            (placeholder "P5")

config/
├── routes.php                      ← MODIF Task 17 (ajout 18 routes)
└── mail.php                        ← Task 2

public/
└── index.php                       ← MODIF Task 17 (support POST + dispatch middleware)

database/
└── set_password.php                ← Task 18 (CLI : reset password d'un user)
```

---

## Liste des tâches (20 au total)

| # | Tâche |
|---|---|
| 1 | `UserRepository` |
| 2 | `EmailService` + config/mail.php |
| 3 | `AuthService` (signUp/signIn/signOut/requestReset/reset/verify) |
| 4 | `AuthMiddleware` + `CsrfMiddleware` |
| 5 | Helpers fonctions : `auth_user`, `is_logged_in`, `csrf_check`, `old` |
| 6 | Layout `auth.php` |
| 7 | Layout `account.php` |
| 8 | Partial `account-sidebar.php` |
| 9 | `AuthController` (8 actions GET/POST) |
| 10 | 5 vues `views/pages/auth/*.php` |
| 11 | CSS additionnel pour formulaires auth + sidebar account |
| 12 | Smoke test register → login → logout flow |
| 13 | Smoke test forgot → reset flow |
| 14 | `AccountController` (5 actions) |
| 15 | 5 vues `views/pages/account/*.php` |
| 16 | Navbar : ajout lien Mon compte / Se connecter selon état auth |
| 17 | `routes.php` enrichi + `index.php` (POST + middleware dispatch) |
| 18 | Script CLI `database/set_password.php` (reset password d'un user) |
| 19 | Reset password Aurane via le CLI script |
| 20 | Commit P3 |

---

## Routes ajoutées (18 nouvelles)

```
GET  /login                            AuthController::loginForm
POST /login                            AuthController::loginSubmit
GET  /register                         AuthController::registerForm
POST /register                         AuthController::registerSubmit
GET  /mot-de-passe-oublie              AuthController::forgotForm
POST /mot-de-passe-oublie              AuthController::forgotSubmit
GET  /reinitialiser-mot-de-passe       AuthController::resetForm
POST /reinitialiser-mot-de-passe       AuthController::resetSubmit
GET  /verifier-email                   AuthController::verifyEmail
GET  /inscription-en-attente           AuthController::verifySent
POST /logout                           AuthController::logout

GET  /mon-compte                       AccountController::dashboard   [auth]
GET  /mon-compte/profil                AccountController::profil      [auth]
POST /mon-compte/profil                AccountController::profilSubmit [auth]
GET  /mon-compte/reservations          AccountController::reservations [auth]
GET  /mon-compte/forfaits              AccountController::forfaits     [auth]
GET  /mon-compte/factures              AccountController::factures     [auth]
```

Les routes `[auth]` sont protégées via `AuthMiddleware` (redirect `/login?return=<url>` si non connecté).

---

## Détail des tâches critiques (les autres sont mécaniques, exécutées inline)

### Task 3 — AuthService : signature des méthodes

```php
class AuthService {
    public function signUp(string $email, string $password, string $fullName, ?string $phone = null): array
        // Retourne ['user' => array, 'verification_token' => string]
        // Throws AuthException si email déjà pris ou password trop court

    public function signIn(string $email, string $password): array
        // Retourne user array
        // Throws AuthException si bad credentials OR account locked OR rate limit

    public function signOut(): void
        // Détruit la session

    public function requestPasswordReset(string $email): ?string
        // Génère token, l'enregistre dans t_users, retourne le token (à envoyer par mail)
        // Retourne null si email pas trouvé (mais ne révèle pas l'info à l'utilisateur)

    public function resetPassword(string $token, string $newPassword): bool
        // Valide token + expiration, met à jour password_hash, clear token, retourne true
        // Throws AuthException si token invalide/expiré

    public function verifyEmail(string $token): bool
        // Marque email_verified_at, clear token

    public function login(array $user): void
        // session_regenerate_id(true), $_SESSION['user_id'] = ..., reset failed_login_attempts
}
```

### Task 4 — Middlewares

```php
// AuthMiddleware
public static function check(): void {
    if (!is_logged_in()) {
        $return = urlencode($_SERVER['REQUEST_URI'] ?? '/');
        header("Location: /login?return={$return}");
        exit;
    }
}

// CsrfMiddleware
public static function check(): void {
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $sent = $_POST['_csrf'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
        if (!hash_equals($_SESSION['csrf_token'] ?? '', $sent)) {
            http_response_code(403);
            echo 'CSRF token invalide';
            exit;
        }
    }
}
```

### Task 17 — Routes étendues (format avec middleware optionnel)

```php
return [
    // Format : 'METHOD URI' => [Controller, action, ['middleware' => 'auth']]
    'GET /'           => ['App\Controllers\PublicController', 'home'],
    'POST /login'     => ['App\Controllers\AuthController', 'loginSubmit'],
    'GET /mon-compte' => ['App\Controllers\AccountController', 'dashboard', ['middleware' => 'auth']],
    ...
];
```

Le router étendu dans index.php :

```php
[$ctrl, $action, $opts] = array_pad($routes[$key], 3, []);
CsrfMiddleware::check();
if (($opts['middleware'] ?? null) === 'auth') {
    AuthMiddleware::check();
}
(new $ctrl())->$action();
```

---

## Self-Review (à appliquer après écriture)

- [ ] Couverture spec §7 : Argon2id ✓, sessions ✓, CSRF ✓, rate limit ✓, reset ✓, email verify ✓
- [ ] Pas de leak SQL : toutes les requêtes via PDO prepared dans UserRepository
- [ ] CSRF middleware appelé sur TOUT POST (pas seulement auth)
- [ ] `session_regenerate_id(true)` après login (anti-fixation)
- [ ] Tokens reset/verify générés avec `random_bytes(32)` (cryptographique)
- [ ] hash_equals pour comparer tokens (timing-safe)
- [ ] Le formulaire forgot ne révèle JAMAIS si l'email existe (message générique "Si cet email existe...")
- [ ] Lockout : check `locked_until > NOW()` avant signin, message clair "Compte verrouillé, réessayez dans X minutes"
- [ ] Helper `e()` partout sur sortie HTML utilisateur (form values, error messages)
- [ ] Pas de redirect open : valider le `return` URL est interne avant redirect

---

## Sortie attendue fin P3

- 18 nouvelles routes opérationnelles
- Un visiteur peut s'inscrire, recevoir un email de vérification (logué en dev), se connecter, voir son /mon-compte
- Un utilisateur peut éditer son profil (nom, email, téléphone, password)
- Forgot/reset password fonctionnel
- Rate limit en place (lockout après 5 essais ratés)
- Aurane peut se loguer après reset de son password via CLI
- Tests de smoke passés
- Commit P3

**Durée estimée** : 4-5 jours dev humain · ~45 min exécution inline.

**Prochain plan** : `2026-05-18-aldana-p4-booking.md` (transaction SQL atomique de réservation + politique J-24h + email confirmation + tests PHPUnit Feature).
