Adds credentials page
parent
7668ee2040
commit
925df9b915
|
@ -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>
|
|
@ -53,6 +53,7 @@ $allowed_urls = [
|
||||||
'config',
|
'config',
|
||||||
|
|
||||||
'profile',
|
'profile',
|
||||||
|
'credentials',
|
||||||
|
|
||||||
'settings',
|
'settings',
|
||||||
'security',
|
'security',
|
||||||
|
|
Loading…
Reference in New Issue