Compare commits
3 Commits
7668ee2040
...
200f87ea48
Author | SHA1 | Date |
---|---|---|
|
200f87ea48 | |
|
7676bcd1c1 | |
|
925df9b915 |
|
@ -11,6 +11,7 @@ class User {
|
|||
*/
|
||||
private $db;
|
||||
private $rateLimiter;
|
||||
private $twoFactorAuth;
|
||||
|
||||
/**
|
||||
* User constructor.
|
||||
|
@ -19,8 +20,16 @@ class User {
|
|||
* @param object $database The database object to initialize the connection.
|
||||
*/
|
||||
public function __construct($database) {
|
||||
if ($database instanceof PDO) {
|
||||
$this->db = $database;
|
||||
} else {
|
||||
$this->db = $database->getConnection();
|
||||
}
|
||||
require_once __DIR__ . '/ratelimiter.php';
|
||||
require_once __DIR__ . '/twoFactorAuth.php';
|
||||
|
||||
$this->rateLimiter = new RateLimiter($database);
|
||||
$this->twoFactorAuth = new TwoFactorAuthentication($database);
|
||||
}
|
||||
|
||||
|
||||
|
@ -86,10 +95,11 @@ class User {
|
|||
*
|
||||
* @param string $username The username of the user.
|
||||
* @param string $password The password of the user.
|
||||
* @param string $twoFactorCode Optional. The 2FA code if 2FA is enabled.
|
||||
*
|
||||
* @return bool True if login is successful, false otherwise.
|
||||
* @return array Login result with status and any necessary data
|
||||
*/
|
||||
public function login($username, $password) {
|
||||
public function login($username, $password, $twoFactorCode = null) {
|
||||
// Get user's IP address
|
||||
require_once __DIR__ . '/../helpers/logs.php';
|
||||
$ipAddress = getUserIP();
|
||||
|
@ -110,14 +120,41 @@ class User {
|
|||
|
||||
$user = $query->fetch(PDO::FETCH_ASSOC);
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
// Check if 2FA is enabled
|
||||
if ($this->twoFactorAuth->isEnabled($user['id'])) {
|
||||
if ($twoFactorCode === null) {
|
||||
return [
|
||||
'status' => 'requires_2fa',
|
||||
'user_id' => $user['id'],
|
||||
'username' => $user['username']
|
||||
];
|
||||
}
|
||||
|
||||
// Verify 2FA code
|
||||
if (!$this->twoFactorAuth->verify($user['id'], $twoFactorCode)) {
|
||||
return [
|
||||
'status' => 'invalid_2fa',
|
||||
'message' => 'Invalid 2FA code'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Login successful
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
return true;
|
||||
return [
|
||||
'status' => 'success',
|
||||
'user_id' => $user['id'],
|
||||
'username' => $user['username']
|
||||
];
|
||||
}
|
||||
|
||||
// Get remaining attempts AFTER this failed attempt
|
||||
$remainingAttempts = $this->rateLimiter->getRemainingAttempts($username, $ipAddress);
|
||||
throw new Exception("Invalid credentials. {$remainingAttempts} attempts remaining.");
|
||||
return [
|
||||
'status' => 'failed',
|
||||
'message' => "Invalid credentials. {$remainingAttempts} attempts remaining."
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
@ -450,4 +487,97 @@ class User {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users for messaging
|
||||
*
|
||||
* @return array List of users with their IDs and usernames
|
||||
*/
|
||||
public function getUsers() {
|
||||
$sql = "SELECT id, username
|
||||
FROM users
|
||||
ORDER BY username ASC";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable two-factor authentication for a user
|
||||
*
|
||||
* @param int $userId User ID
|
||||
* @return array Result of enabling 2FA
|
||||
*/
|
||||
public function enableTwoFactor($userId) {
|
||||
return $this->twoFactorAuth->enable($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable two-factor authentication for a user
|
||||
*
|
||||
* @param int $userId User ID
|
||||
* @return bool True if disabled successfully
|
||||
*/
|
||||
public function disableTwoFactor($userId) {
|
||||
return $this->twoFactorAuth->disable($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a two-factor authentication code
|
||||
*
|
||||
* @param int $userId User ID
|
||||
* @param string $code The verification code
|
||||
* @return bool True if verified
|
||||
*/
|
||||
public function verifyTwoFactor($userId, $code) {
|
||||
return $this->twoFactorAuth->verify($userId, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two-factor authentication is enabled for a user
|
||||
*
|
||||
* @param int $userId User ID
|
||||
* @return bool True if enabled
|
||||
*/
|
||||
public function isTwoFactorEnabled($userId) {
|
||||
return $this->twoFactorAuth->isEnabled($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a user's password
|
||||
*
|
||||
* @param int $userId User ID
|
||||
* @param string $currentPassword Current password for verification
|
||||
* @param string $newPassword New password to set
|
||||
* @return bool True if password was changed successfully
|
||||
*/
|
||||
public function changePassword($userId, $currentPassword, $newPassword) {
|
||||
try {
|
||||
// First verify the current password
|
||||
$sql = "SELECT password FROM users WHERE id = :user_id";
|
||||
$query = $this->db->prepare($sql);
|
||||
$query->execute([':user_id' => $userId]);
|
||||
$user = $query->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$user || !password_verify($currentPassword, $user['password'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hash the new password
|
||||
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
|
||||
// Update the password
|
||||
$sql = "UPDATE users SET password = :password WHERE id = :user_id";
|
||||
$query = $this->db->prepare($sql);
|
||||
return $query->execute([
|
||||
':password' => $hashedPassword,
|
||||
':user_id' => $userId
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error changing password: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* User credentials management
|
||||
*
|
||||
* This page ("credentials") handles all credential-related actions including:
|
||||
* - Two-factor authentication (2FA) setup, verification, and management
|
||||
* - Password changes and resets
|
||||
*
|
||||
* Actions handled:
|
||||
* - `setup`: Initial 2FA setup and verification
|
||||
* - `verify`: Verify 2FA codes during login
|
||||
* - `disable`: Disable 2FA
|
||||
* - `password`: Change password
|
||||
*/
|
||||
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
$item = $_REQUEST['item'] ?? '';
|
||||
|
||||
// if a form is submitted
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
// Validate CSRF token
|
||||
require_once '../app/helpers/security.php';
|
||||
$security = SecurityHelper::getInstance();
|
||||
if (!$security->verifyCsrfToken($_POST['csrf_token'] ?? '')) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Invalid security token. Please try again.');
|
||||
header("Location: $app_root?page=credentials");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Apply rate limiting
|
||||
require_once '../app/includes/rate_limit_middleware.php';
|
||||
checkRateLimit($db, 'credentials', $user_id);
|
||||
|
||||
switch ($item) {
|
||||
case '2fa':
|
||||
require_once '../app/helpers/2fa.php';
|
||||
|
||||
switch ($action) {
|
||||
case 'setup':
|
||||
// Validate the setup code
|
||||
$code = $_POST['code'] ?? '';
|
||||
$secret = $_POST['secret'] ?? '';
|
||||
|
||||
if ($userObject->enableTwoFactor($user_id, $secret, $code)) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', 'Two-factor authentication has been enabled successfully.');
|
||||
header("Location: $app_root?page=credentials");
|
||||
exit();
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Invalid verification code. Please try again.');
|
||||
header("Location: $app_root?page=credentials&action=edit");
|
||||
exit();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'verify':
|
||||
$code = $_POST['code'] ?? '';
|
||||
if ($userObject->verifyTwoFactor($user_id, $code)) {
|
||||
$_SESSION['2fa_verified'] = true;
|
||||
header("Location: $app_root?page=dashboard");
|
||||
exit();
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Invalid verification code. Please try again.');
|
||||
header("Location: $app_root?page=credentials&action=verify");
|
||||
exit();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'disable':
|
||||
if ($userObject->disableTwoFactor($user_id)) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', 'Two-factor authentication has been disabled.');
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Failed to disable two-factor authentication.');
|
||||
}
|
||||
header("Location: $app_root?page=credentials");
|
||||
exit();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'password':
|
||||
require_once '../app/classes/validator.php';
|
||||
|
||||
$validator = new Validator($_POST);
|
||||
$rules = [
|
||||
'current_password' => [
|
||||
'required' => true,
|
||||
'min' => 8
|
||||
],
|
||||
'new_password' => [
|
||||
'required' => true,
|
||||
'min' => 8
|
||||
],
|
||||
'confirm_password' => [
|
||||
'required' => true,
|
||||
'matches' => 'new_password'
|
||||
]
|
||||
];
|
||||
|
||||
if (!$validator->validate($rules)) {
|
||||
Feedback::flash('ERROR', 'DEFAULT', $validator->getFirstError());
|
||||
header("Location: $app_root?page=credentials");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($userObject->changePassword($user_id, $_POST['current_password'], $_POST['new_password'])) {
|
||||
Feedback::flash('NOTICE', 'DEFAULT', 'Password has been changed successfully.');
|
||||
} else {
|
||||
Feedback::flash('ERROR', 'DEFAULT', 'Failed to change password. Please verify your current password.');
|
||||
}
|
||||
header("Location: $app_root?page=credentials");
|
||||
exit();
|
||||
break;
|
||||
}
|
||||
|
||||
// no form submitted, show the templates
|
||||
} else {
|
||||
// Get user timezone for templates
|
||||
$userTimezone = !empty($userDetails[0]['timezone']) ? $userDetails[0]['timezone'] : 'UTC';
|
||||
|
||||
// Generate CSRF token if not exists
|
||||
require_once '../app/helpers/security.php';
|
||||
$security = SecurityHelper::getInstance();
|
||||
$security->generateCsrfToken();
|
||||
|
||||
// Get 2FA status for the template
|
||||
$has2fa = $userObject->isTwoFactorEnabled($user_id);
|
||||
|
||||
switch ($action) {
|
||||
case 'edit':
|
||||
if (!$has2fa) {
|
||||
require_once '../app/helpers/2fa.php';
|
||||
$secret = $userObject->generateTwoFactorSecret();
|
||||
$qrCode = $userObject->generateTwoFactorQR($user_id, $secret);
|
||||
$backupCodes = $userObject->generateBackupCodes();
|
||||
}
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the 2FA setup template
|
||||
include '../app/templates/credentials-2fa-setup.php';
|
||||
break;
|
||||
|
||||
case 'verify':
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the 2FA verification template
|
||||
include '../app/templates/credentials-2fa-verify.php';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Get any new feedback messages
|
||||
include '../app/helpers/feedback.php';
|
||||
|
||||
// Load the combined management template
|
||||
include '../app/templates/credentials-manage.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
/**
|
||||
* Two-factor authentication setup template
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Set up two-factor authentication</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<p>Two-factor authentication adds an extra layer of security to your account. Once enabled, you'll need to enter both your password and a code from your authenticator app when signing in.</p>
|
||||
</div>
|
||||
|
||||
<?php if (isset($error)): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($setupData)): ?>
|
||||
<div class="setup-steps">
|
||||
<h4>1. Install an authenticator app</h4>
|
||||
<p>If you haven't already, install an authenticator app on your mobile device:</p>
|
||||
<ul>
|
||||
<li>Google Authenticator</li>
|
||||
<li>Microsoft Authenticator</li>
|
||||
<li>Authy</li>
|
||||
</ul>
|
||||
|
||||
<h4 class="mt-4">2. Scan the QR code</h4>
|
||||
<p>Open your authenticator app and scan this QR code:</p>
|
||||
|
||||
<div class="text-center my-4">
|
||||
<div id="qrcode"></div>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">Can't scan? Use this code instead:</small><br>
|
||||
<code class="secret-key"><?php echo htmlspecialchars($setupData['secret']); ?></code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="mt-4">3. Verify setup</h4>
|
||||
<p>Enter the 6-digit code from your authenticator app to verify the setup:</p>
|
||||
|
||||
<form method="post" action="?page=credentials&action=setup" class="mt-3">
|
||||
<div class="form-group">
|
||||
<input type="text"
|
||||
name="code"
|
||||
class="form-control"
|
||||
pattern="[0-9]{6}"
|
||||
maxlength="6"
|
||||
required
|
||||
placeholder="Enter 6-digit code">
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="secret" value="<?php echo htmlspecialchars($setupData['secret']); ?>">
|
||||
|
||||
<button type="submit" class="btn btn-primary mt-3">
|
||||
Verify and enable 2FA
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>Backup codes</h4>
|
||||
<p class="text-warning">
|
||||
<strong>Important:</strong> Save these backup codes in a secure place.
|
||||
If you lose access to your authenticator app, you can use these codes to sign in.
|
||||
Each code can only be used once.
|
||||
</p>
|
||||
<div class="backup-codes bg-light p-3 rounded">
|
||||
<?php foreach ($setupData['backupCodes'] as $code): ?>
|
||||
<code class="d-block"><?php echo htmlspecialchars($code); ?></code>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button class="btn btn-secondary mt-2" onclick="window.print()">
|
||||
Print backup codes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<form method="post" action="?page=credentials&action=setup" class="mt-3">
|
||||
<p>Click the button below to begin setting up two-factor authentication:</p>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Begin setup
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($setupData)): ?>
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new QRCode(document.getElementById("qrcode"), {
|
||||
text: <?php echo json_encode($setupData['otpauthUrl']); ?>,
|
||||
width: 200,
|
||||
height: 200
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
/**
|
||||
* Two-factor authentication verification template
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Two-factor authentication</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if (isset($error)): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<p>Enter the 6-digit code from your authenticator app:</p>
|
||||
|
||||
<form method="post" action="?page=credentials&action=verify" class="mt-3">
|
||||
<div class="form-group">
|
||||
<input type="text"
|
||||
name="code"
|
||||
class="form-control form-control-lg text-center"
|
||||
pattern="[0-9]{6}"
|
||||
maxlength="6"
|
||||
inputmode="numeric"
|
||||
autocomplete="one-time-code"
|
||||
required
|
||||
autofocus
|
||||
placeholder="000000">
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="user_id" value="<?php echo htmlspecialchars($userId); ?>">
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block mt-4">
|
||||
Verify code
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-4">
|
||||
<p class="text-muted text-center">
|
||||
Lost access to your authenticator app?<br>
|
||||
<a href="#" data-toggle="collapse" data-target="#backupCodeForm">
|
||||
Use a backup code
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div class="collapse mt-3" id="backupCodeForm">
|
||||
<form method="post" action="?page=credentials&action=verify" class="mt-3">
|
||||
<div class="form-group">
|
||||
<label>Enter backup code:</label>
|
||||
<input type="text"
|
||||
name="backup_code"
|
||||
class="form-control"
|
||||
pattern="[a-f0-9]{8}"
|
||||
maxlength="8"
|
||||
required
|
||||
placeholder="Enter backup code">
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="user_id" value="<?php echo htmlspecialchars($userId); ?>">
|
||||
|
||||
<button type="submit" class="btn btn-secondary btn-block">
|
||||
Use backup code
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Auto-submit when 6 digits are entered
|
||||
document.querySelector('input[name="code"]').addEventListener('input', function(e) {
|
||||
if (e.target.value.length === 6 && e.target.checkValidity()) {
|
||||
e.target.form.submit();
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
/**
|
||||
* Combined credentials management template
|
||||
* Handles both password changes and 2FA management
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<!-- Password Management -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h3>change password</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="?page=credentials&item=password">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="current_password">current password</label>
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
id="current_password"
|
||||
name="current_password"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="form-group mt-3">
|
||||
<label for="new_password">new password</label>
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
id="new_password"
|
||||
name="new_password"
|
||||
pattern=".{8,}"
|
||||
title="Password must be at least 8 characters long"
|
||||
required>
|
||||
<small class="form-text text-muted">minimum 8 characters</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group mt-3">
|
||||
<label for="confirm_password">confirm new password</label>
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
pattern=".{8,}"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">change password</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2FA Management -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>two-factor authentication</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-4">Two-factor authentication adds an extra layer of security to your account. Once enabled, you'll need to enter both your password and a code from your authenticator app when signing in.</p>
|
||||
|
||||
<?php if ($has2fa): ?>
|
||||
<div class="alert alert-success">
|
||||
<i class="fas fa-check-circle"></i> two-factor authentication is enabled
|
||||
</div>
|
||||
<form method="post" action="?page=credentials&item=2fa&action=disable">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to disable two-factor authentication? This will make your account less secure.')">
|
||||
disable two-factor authentication
|
||||
</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i> two-factor authentication is not enabled
|
||||
</div>
|
||||
<form method="post" action="?page=credentials&item=2fa&action=edit">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
set up two-factor authentication
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('confirm_password').addEventListener('input', function() {
|
||||
if (this.value !== document.getElementById('new_password').value) {
|
||||
this.setCustomValidity('Passwords do not match');
|
||||
} else {
|
||||
this.setCustomValidity('');
|
||||
}
|
||||
});</script>
|
|
@ -0,0 +1,51 @@
|
|||
|
||||
<!-- Two-Factor Authentication -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h3>Two-factor authentication</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if ($has2fa): ?>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<p class="mb-0">
|
||||
<i class="fas fa-shield-alt text-success"></i>
|
||||
Two-factor authentication is enabled
|
||||
</p>
|
||||
<small class="text-muted">
|
||||
Your account is protected with an authenticator app
|
||||
</small>
|
||||
</div>
|
||||
<form method="post" class="ml-3">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<input type="hidden" name="item" value="2fa">
|
||||
<input type="hidden" name="action" value="disable">
|
||||
<button type="submit" class="btn btn-outline-danger"
|
||||
onclick="return confirm('Are you sure you want to disable two-factor authentication? This will make your account less secure.')">
|
||||
Disable 2FA
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<p class="mb-0">
|
||||
<i class="fas fa-shield-alt text-muted"></i>
|
||||
Two-factor authentication is not enabled
|
||||
</p>
|
||||
<small class="text-muted">
|
||||
Add an extra layer of security to your account by requiring both your password and an authentication code
|
||||
</small>
|
||||
</div>
|
||||
<form method="post" class="ml-3">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<input type="hidden" name="item" value="2fa">
|
||||
<input type="hidden" name="action" value="enable">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Enable 2FA
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
|
@ -35,8 +35,24 @@
|
|||
|
||||
<ul class="menu-right">
|
||||
<?php if ( isset($_SESSION['username']) ) { ?>
|
||||
<li><a href="<?= htmlspecialchars($app_root) ?>?page=profile"><?= htmlspecialchars($currentUser) ?></a></li>
|
||||
<li><a href="<?= htmlspecialchars($app_root) ?>?page=logout">logout</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
||||
<i class="fas fa-user"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<h6 class="dropdown-header"><?= htmlspecialchars($currentUser) ?></h6>
|
||||
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=profile">
|
||||
<i class="fas fa-id-card"></i>Profile details
|
||||
</a>
|
||||
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=credentials">
|
||||
<i class="fas fa-shield-alt"></i>Login credentials
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="<?= htmlspecialchars($app_root) ?>?page=logout">
|
||||
<i class="fas fa-sign-out-alt"></i>Logout
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<?php } else { ?>
|
||||
<li><a href="<?= htmlspecialchars($app_root) ?>?page=login">login</a></li>
|
||||
<li><a href="<?= htmlspecialchars($app_root) ?>?page=register">register</a></li>
|
||||
|
|
|
@ -53,6 +53,7 @@ $allowed_urls = [
|
|||
'config',
|
||||
|
||||
'profile',
|
||||
'credentials',
|
||||
|
||||
'settings',
|
||||
'security',
|
||||
|
|
|
@ -44,6 +44,57 @@ html, body {
|
|||
.menu-left li a:hover, .menu-right li a:hover {
|
||||
background-color: #111;
|
||||
}
|
||||
/* Dropdown menu styles */
|
||||
.dropdown-menu {
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0,0,0,.15);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,.175);
|
||||
margin-top: 0;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.dropdown-menu .dropdown-item {
|
||||
color: #333;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-menu .dropdown-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
color: #16181b;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.dropdown-menu .dropdown-item i {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.dropdown-menu .dropdown-item:hover i {
|
||||
color: #16181b;
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
margin: 4px 0;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.dropdown-toggle::after {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
vertical-align: middle;
|
||||
content: "";
|
||||
border-top: 4px solid;
|
||||
border-right: 4px solid transparent;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
/* /menu */
|
||||
|
||||
.error {
|
||||
|
|
Loading…
Reference in New Issue