fix #29 : envoyer le lien magique par email (envoyer_mail_smtp)
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# Authentification — Lien magique
|
||||
|
||||
Mécanisme d'authentification par email sans mot de passe. L'utilisateur reçoit un lien à usage unique, valide un temps limité, qui ouvre une session PHP.
|
||||
|
||||
## Fichiers concernés
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---|---|
|
||||
| `public/login/index.php` | Formulaire de demande + génération du token |
|
||||
| `public/login/magic.php` | Consommation du token + ouverture de session |
|
||||
| Table BDD `auth_magic_links` | Persistance des tokens |
|
||||
|
||||
## Configuration (`.env`)
|
||||
|
||||
| Variable | Défaut | Description |
|
||||
|---|---|---|
|
||||
| `MAGIC_LINK_TTL_MINUTES` | `30` | Durée de validité du lien |
|
||||
| `MAGIC_COOLDOWN_MINUTES` | `5` | Délai minimal entre deux demandes pour le même email |
|
||||
| `MAGIC_WINDOW_HOURS` | `12` | Fenêtre glissante pour le plafond |
|
||||
| `MAGIC_MAX_PER_WINDOW` | `5` | Nombre maximal de liens émis par fenêtre |
|
||||
|
||||
## Schéma BDD — `auth_magic_links`
|
||||
|
||||
```sql
|
||||
id UUID PRIMARY KEY (gen_random_uuid())
|
||||
email TEXT NOT NULL
|
||||
token TEXT NOT NULL UNIQUE
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
expires_at TIMESTAMPTZ NOT NULL
|
||||
consumed_at TIMESTAMPTZ NULL -- NULL = non consommé
|
||||
ip TEXT
|
||||
user_agent TEXT
|
||||
return_to TEXT NULL -- chemin relatif de redirection post-login
|
||||
```
|
||||
|
||||
## Phase 1 — Demande du lien (`index.php`, POST)
|
||||
|
||||
```
|
||||
[Formulaire email] → POST /login
|
||||
│
|
||||
├─ Validation CSRF
|
||||
├─ Validation syntaxe email
|
||||
│
|
||||
├─ Purge BDD : DELETE liens expirés ou consommés pour cet email
|
||||
│
|
||||
├─ Cooldown : un lien < MAGIC_COOLDOWN_MINUTES → refus
|
||||
├─ Plafond : >= MAGIC_MAX_PER_WINDOW liens dans MAGIC_WINDOW_HOURS → refus
|
||||
│
|
||||
├─ Génération token : random_bytes(32) → base64url (43 chars, URL-safe)
|
||||
├─ INSERT auth_magic_links (expires_at = NOW() + TTL)
|
||||
│
|
||||
└─ [stub] Envoi email : /login/magic.php?token=<token>
|
||||
```
|
||||
|
||||
Le token est un base64url sans padding (`+/` remplacés par `-_`, `=` supprimés) — il ne contient que des caractères `[A-Za-z0-9\-_]`.
|
||||
|
||||
L'envoi de l'email est actuellement un **stub** : le code construit `$magicUrl` mais l'appel SMTP n'est pas implémenté (`index.php:142`).
|
||||
|
||||
## Phase 2 — Consommation du lien (`magic.php`, GET)
|
||||
|
||||
```
|
||||
[Clic sur le lien] → GET /login/magic.php?token=<token>
|
||||
│
|
||||
├─ Validation format token (regex [A-Za-z0-9\-\_])
|
||||
│
|
||||
├─ BEGIN TRANSACTION
|
||||
│ ├─ SELECT ... FOR UPDATE (verrou anti double-consommation)
|
||||
│ ├─ Token inconnu → 400
|
||||
│ ├─ consumed_at non null → "Lien déjà utilisé"
|
||||
│ ├─ expires_at dépassé → "Lien expiré"
|
||||
│ ├─ UPDATE consumed_at = NOW()
|
||||
│ └─ COMMIT
|
||||
│
|
||||
├─ session_start() + session_regenerate_id(true)
|
||||
├─ $_SESSION['user_email'] = email
|
||||
│
|
||||
└─ Redirection 303 → return_to (validé : doit commencer par /)
|
||||
```
|
||||
|
||||
Le `FOR UPDATE` garantit qu'un token ne peut pas être consommé deux fois en cas de double-clic ou de requêtes concurrentes.
|
||||
|
||||
## Sécurités notables
|
||||
|
||||
- **CSRF** sur le formulaire de demande
|
||||
- **Rate limiting** double (cooldown + plafond glissant) par email
|
||||
- **Token URL-safe** généré par CSPRNG (`random_bytes`)
|
||||
- **Verrou transactionnel** (`FOR UPDATE`) à la consommation
|
||||
- **`session_regenerate_id(true)`** — prévient la fixation de session
|
||||
- **`return_to` filtré** : seuls les chemins relatifs (commençant par `/`) sont acceptés
|
||||
|
||||
## État actuel — ce qui reste à câbler
|
||||
|
||||
L'envoi SMTP n'est pas implémenté. Le token est correctement généré et persisté en BDD, mais le mail n'est pas envoyé. Voir `public/login/index.php:142` :
|
||||
|
||||
```php
|
||||
$magicUrl = url('/login/magic.php') . '?token=' . urlencode($token);
|
||||
/* envoyer_mail_smtp(...) ou mail(...) */
|
||||
```
|
||||
Reference in New Issue
Block a user