Прежняя git-история утрачена при переносе проекта на машину владельца (снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной истории. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2 KiB
PHP
68 lines
2 KiB
PHP
<?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');
|
|
}
|