email ?? '')); // Anti-enumeration: невалидный/несуществующий email — тот же 200. if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { json_out(['ok' => true]); } $stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?'); $stmt->execute([$email]); if ($stmt->fetch() === false) { json_out(['ok' => true]); } $token = bin2hex(random_bytes(32)); $stmt = $pdo->prepare( 'INSERT INTO password_resets (email, token_hash, expires_at) VALUES (?, ?, ?)' ); $stmt->execute([$email, hash('sha256', $token), time() + 3600]); $config = golearn_config(); $baseUrl = rtrim((string)($config['base_url'] ?? ''), '/'); $mailFrom = (string)($config['mail_from'] ?? ''); $link = $baseUrl . '/password-reset.html?token=' . $token; $subject = 'Восстановление пароля — Го'; $text = "Здравствуйте!\n\n" . "Вы запросили восстановление пароля на сайте обучения игре Го.\n" . "Чтобы задать новый пароль, перейдите по ссылке:\n\n" . $link . "\n\n" . "Ссылка действует 1 час. Если вы не запрашивали восстановление,\n" . "просто проигнорируйте это письмо — пароль не изменится.\n"; if (!send_reset_mail($email, $subject, $text, $mailFrom)) { error_out(503, 'mail_unavailable'); } json_out(['ok' => true]); } catch (Throwable $e) { error_log('password-reset/request: ' . $e->getMessage()); error_out(500, 'internal'); }