chore: восстановление репозитория из снапшота v0.3.1
Прежняя git-история утрачена при переносе проекта на машину владельца (снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной истории. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
1c85091186
184 changed files with 33303 additions and 0 deletions
74
server/api/register.php
Normal file
74
server/api/register.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* POST /api/register.php {email, password, captcha}
|
||||
* → 201 {ok:true} | 403 | 413 | 422 (невалидный email / пароль < 8 /
|
||||
* капча) | 409 (email занят) | 429.
|
||||
*
|
||||
* Этап 8.1: обязательное поле captcha (код из /api/captcha.php).
|
||||
* Проверка одноразовая: ЛЮБАЯ попытка register сжигает код из сессии.
|
||||
* Порядок проверок по контракту: rate limit (429) → заголовок (403)
|
||||
* → капча (422 captcha) → валидация (422) → занят (409) → 201.
|
||||
*/
|
||||
|
||||
require __DIR__ . '/../lib/http.php';
|
||||
require __DIR__ . '/../lib/db.php';
|
||||
require __DIR__ . '/../lib/auth.php';
|
||||
require __DIR__ . '/../lib/ratelimit.php';
|
||||
|
||||
http_security_headers();
|
||||
|
||||
try {
|
||||
method_must('POST');
|
||||
|
||||
$pdo = db();
|
||||
$ip = client_ip();
|
||||
rate_check($pdo, $ip, 'register');
|
||||
require_client_header();
|
||||
rate_record($pdo, $ip, 'register');
|
||||
|
||||
$body = read_json_object();
|
||||
$email = trim((string)($body->email ?? ''));
|
||||
$password = (string)($body->password ?? '');
|
||||
$captcha = (string)($body->captcha ?? '');
|
||||
|
||||
// Капча — одноразовая: код изымается из сессии при любой попытке.
|
||||
session_start_secure();
|
||||
$captchaHash = $_SESSION['captcha_hash'] ?? null;
|
||||
$captchaExpires = $_SESSION['captcha_expires'] ?? null;
|
||||
unset($_SESSION['captcha_hash'], $_SESSION['captcha_expires']);
|
||||
$captchaOk = is_string($captchaHash)
|
||||
&& is_int($captchaExpires)
|
||||
&& $captchaExpires >= time()
|
||||
&& $captcha !== ''
|
||||
&& hash_equals($captchaHash, hash('sha256', strtolower($captcha)));
|
||||
if (!$captchaOk) {
|
||||
error_out(422, 'captcha');
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($password) < 8) {
|
||||
error_out(422, 'validation');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
|
||||
$stmt->execute([$email]);
|
||||
if ($stmt->fetch() !== false) {
|
||||
error_out(409, 'email_taken');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO users (email, pass_hash, created_at) VALUES (?, ?, ?)'
|
||||
);
|
||||
$stmt->execute([
|
||||
$email,
|
||||
password_hash($password, PASSWORD_DEFAULT),
|
||||
gmdate('Y-m-d\TH:i:s\Z'),
|
||||
]);
|
||||
|
||||
json_out(['ok' => true], 201);
|
||||
} catch (Throwable $e) {
|
||||
error_log('register: ' . $e->getMessage());
|
||||
error_out(500, 'internal');
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue