Harden sign-in and password reset

- OTP attempt limits, constant-time compare, random reference codes
- DB-backed rate limits (429) on sign-in, OTP, reset, register, onboarding
- one generic sign-in failure message; reset request no longer reveals accounts
- no password kept in the session; real status codes on failures
This commit is contained in:
Thanakorn
2026-09-24 14:53:40 +07:00
parent ae98dcdcdd
commit 73c680e844
16 changed files with 538 additions and 282 deletions
+8 -1
View File
@@ -48,6 +48,7 @@ require_once '../../../config.php';
require_once '../../../dbconn.php';
require_once '../../../assets/utils/db_helpers.php';
require_once '../../../assets/utils/classes/PasswordManager.php';
require_once '../../../assets/utils/rate_limit.php';
header('Content-Type: application/json; charset=utf-8');
@@ -58,7 +59,7 @@ $answer = ['success' => 0, 'message' => ''];
// stored in session. This prevents cross-site request forgery on the register form.
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrf = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (empty($csrf) || $csrf !== ($_SESSION['csrf_token'] ?? '')) {
if (empty($csrf) || !hash_equals((string)($_SESSION['csrf_token'] ?? ''), $csrf)) {
http_response_code(403);
$answer['message'] = 'Invalid request.';
exit(json_encode($answer));
@@ -67,6 +68,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = json_decode($_POST['json'] ?? '{}', true) ?: [];
// ── Step 1b: Throttle — every registration sends a verification email ────────
rate_limit_guard($pdo1, [
['register_ip', rate_limit_client_ip(), 10, 3600],
['register_email', strtolower(trim((string)($data['email'] ?? ''))), 3, 3600],
]);
try {
// ── Step 2: Sanitise input ────────────────────────────────────────────────