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 -6
View File
@@ -11,12 +11,12 @@
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
require '../include_header.php';
require __DIR__ . '/include_login_header.php';
?>
<body>
<script src="https://cdn.jsdelivr.net/npm/zxcvbn@4.4.2/dist/zxcvbn.js"></script>
<script src="<?php echo $server_url?>assets/vendor/zxcvbn/4.4.2/zxcvbn.js"></script>
<div class="container d-flex align-items-center justify-content-center min-vh-100">
<div class="card" style="max-width:420px; width:100%;">
@@ -50,8 +50,8 @@
<div id="step_reset" class="d-none">
<div class="alert alert-info small py-2 mb-4">
<i class="ti ti-mail me-1"></i>
OTP sent to <strong id="masked_email"></strong>
— reference <strong id="ref_code"></strong>
<span id="request_message"></span>
Reference <strong id="ref_code"></strong>
</div>
<div class="mb-3">
<label class="form-label">OTP <span class="text-danger">*</span></label>
@@ -163,8 +163,10 @@
autoPrepare: false,
data: { json: JSON.stringify({ action: 'read', identifier: identifier }) },
onSuccess: function (r) {
$('#masked_email').text(r.masked_email);
$('#ref_code').text(r.reference);
// The server answers the same way whether or not the account exists,
// so there is no masked email to show — only its generic message.
$('#request_message').text(r.message || '');
$('#ref_code').text(r.reference || '');
$('#step_request').addClass('d-none');
$('#step_reset').removeClass('d-none');
$('#subtitle').text('Enter the OTP from your email and choose a new password.');