beginTransaction(); try { // récupère lien non consommé et non expiré $sql = 'SELECT id, email, token, created_at, expires_at, consumed_at, return_to FROM auth_magic_links WHERE token = :t FOR UPDATE'; $stmt = $pdo->prepare($sql); $stmt->execute([':t' => $token]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if (!$row) { throw new RuntimeException('Lien inconnu.'); } if ($row['consumed_at'] !== null) { throw new RuntimeException('Lien déjà utilisé.'); } if (strtotime((string)$row['expires_at']) < time()) { throw new RuntimeException('Lien expiré.'); } // consomme le lien $pdo->prepare('UPDATE auth_magic_links SET consumed_at = NOW() WHERE id = :id')->execute([':id' => $row['id']]); $pdo->commit(); // ouvre une session applicative « anonyme authentifiée par email » if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } $_SESSION['auth'] = [ 'method' => 'magic', 'email' => (string)$row['email'], 'ts' => time(), ]; // Aucun create user ici, conforme à la demande $dest = $row['return_to'] ?? '/'; // sécurité: ne renvoyer que des chemins relatifs if (!is_string($dest) || !str_starts_with($dest, '/')) { $dest = '/'; } header('Location: ' . $dest, true, 303); exit; } catch (\Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } http_response_code(400); echo htmlspecialchars($e->getMessage(), ENT_QUOTES); }