Adds credentials page

main
Yasen Pramatarov 2025-04-07 16:21:35 +03:00
parent 7668ee2040
commit 925df9b915
6 changed files with 506 additions and 0 deletions

View File

@ -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';
}
}

View File

@ -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; ?>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -53,6 +53,7 @@ $allowed_urls = [
'config', 'config',
'profile', 'profile',
'credentials',
'settings', 'settings',
'security', 'security',