Refactor admin panel: enhance user management and security features
- Added role-based access control for user management links in admin navigation. - Improved user account display with a dedicated account link. - Implemented form submission confirmation for delete actions in the admin interface. - Introduced a password visibility toggle on the login page. - Added login throttling to prevent brute-force attacks, recording failed attempts. - Created a CLI script for creating an admin user, replacing the previous hardcoded admin credentials. - Updated database schema to support login attempt tracking. - Enhanced user editing functionality, allowing role changes with restrictions. - Improved modal handling for user actions, ensuring better focus management. - Added styles for new account management page and improved modal transitions.
This commit is contained in:
1 parent
91ec95a4a9
commit
242c5165e1
21 files changed
+669
-77
No files matched your search
+27
-25
@@ -1,17 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../inc/auth.php';
|
||||
require_once __DIR__ . '/../inc/login-throttle.php';
|
||||
require_once __DIR__ . '/../inc/assets.php';
|
||||
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
$username = is_string($_POST['username'] ?? null) ? $_POST['username'] : '';
|
||||
$password = is_string($_POST['password'] ?? null) ? $_POST['password'] : '';
|
||||
$ip = client_ip();
|
||||
$throttleKey = strtolower(trim($username));
|
||||
$lockSeconds = login_lock_seconds($ip, $throttleKey);
|
||||
|
||||
if (authenticate_user($username, $password)) {
|
||||
if ($lockSeconds > 0) {
|
||||
http_response_code(429);
|
||||
$error = 'Too many failed attempts. Try again in ' . (int) ceil($lockSeconds / 60) . ' minute(s).';
|
||||
} elseif (authenticate_user($username, $password)) {
|
||||
clear_login_failures($ip, $throttleKey);
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
} else {
|
||||
record_login_failure($ip, $throttleKey);
|
||||
$error = 'Invalid username or password.';
|
||||
}
|
||||
}
|
||||
@@ -49,10 +58,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<div class="password-input-wrap">
|
||||
<input type="password" id="password" name="password" class="form-input" required autocomplete="current-password">
|
||||
<button type="button" class="password-toggle-btn" aria-label="Show password" id="btn-toggle-login-password">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="icon-eye">
|
||||
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
<span class="js-eye-open">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="icon-eye">
|
||||
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="js-eye-off" hidden>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="icon-eye-off">
|
||||
<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24" />
|
||||
<path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" />
|
||||
<path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" />
|
||||
<line x1="2" y1="2" x2="22" y2="22" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,23 +81,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<a href="../" class="back-link">← Return to Pub Website</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var btn = document.getElementById('btn-toggle-login-password');
|
||||
var input = document.getElementById('password');
|
||||
if (!btn || !input) return;
|
||||
|
||||
var eyeOpen = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="icon-eye"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>';
|
||||
var eyeOff = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="icon-eye-off"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" y1="2" x2="22" y2="22"/></svg>';
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
var isPassword = input.type === 'password';
|
||||
input.type = isPassword ? 'text' : 'password';
|
||||
btn.setAttribute('aria-label', isPassword ? 'Hide password' : 'Show password');
|
||||
btn.innerHTML = isPassword ? eyeOff : eyeOpen;
|
||||
input.focus();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script src="login.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user