chore: восстановление репозитория из снапшота v0.3.1

Прежняя git-история утрачена при переносе проекта на машину владельца
(снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной
истории.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
sab.code.lab 2026-08-07 09:34:02 +02:00
commit 1c85091186
184 changed files with 33303 additions and 0 deletions

View file

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
/**
* POST /api/password-reset/confirm.php {token, password}
* 200 {ok:true} (пароль обновлён, токен удалён, просроченные токены
* очищены) | 400 {error:"token_invalid"} | 403 | 413 | 422 (пароль < 8)
* | 429.
*
* Rate limit: 10 за 10 минут на IP.
* Контракт: docs/INTERFACES.md, раздел «Кабинет 2.0 (этап 8.1)».
*/
require __DIR__ . '/../../lib/http.php';
require __DIR__ . '/../../lib/db.php';
require __DIR__ . '/../../lib/ratelimit.php';
http_security_headers();
try {
method_must('POST');
$pdo = db();
$ip = client_ip();
rate_check($pdo, $ip, 'reset_confirm', GOLEARN_RATE_MAX_RESET_CONFIRM);
require_client_header();
rate_record($pdo, $ip, 'reset_confirm');
$body = read_json_object();
$token = (string)($body->token ?? '');
$password = (string)($body->password ?? '');
if (strlen($password) < 8) {
error_out(422, 'validation');
}
$stmt = $pdo->prepare(
'SELECT id, email, expires_at FROM password_resets
WHERE token_hash = ?'
);
$stmt->execute([hash('sha256', $token)]);
$reset = $stmt->fetch();
if (
!is_array($reset)
|| (int)$reset['expires_at'] < time()
) {
error_out(400, 'token_invalid');
}
$stmt = $pdo->prepare('UPDATE users SET pass_hash = ? WHERE email = ?');
$stmt->execute([
password_hash($password, PASSWORD_DEFAULT),
(string)$reset['email'],
]);
// Токен одноразовый: удаляем использованный; заодно чистим просроченные.
$stmt = $pdo->prepare('DELETE FROM password_resets WHERE id = ?');
$stmt->execute([(int)$reset['id']]);
$stmt = $pdo->prepare('DELETE FROM password_resets WHERE expires_at < ?');
$stmt->execute([time()]);
json_out(['ok' => true]);
} catch (Throwable $e) {
error_log('password-reset/confirm: ' . $e->getMessage());
error_out(500, 'internal');
}