Redesigns credentials/2FA pages

main
Yasen Pramatarov 2025-11-19 22:54:16 +02:00
parent 5422d63d83
commit 1b01a0a0eb
5 changed files with 316 additions and 227 deletions

View File

@ -23,8 +23,11 @@ $item = $_REQUEST['item'] ?? '';
// if a form is submitted // if a form is submitted
if ($_SERVER['REQUEST_METHOD'] == 'POST') { if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Ensure security helper is available
require_once '../app/helpers/security.php';
$security = SecurityHelper::getInstance();
// Validate CSRF token // Validate CSRF token
$security->verifyCsrfToken($_POST['csrf_token'] ?? '');
if (!$security->verifyCsrfToken($_POST['csrf_token'] ?? '')) { if (!$security->verifyCsrfToken($_POST['csrf_token'] ?? '')) {
Feedback::flash('ERROR', 'DEFAULT', 'Invalid security token. Please try again.'); Feedback::flash('ERROR', 'DEFAULT', 'Invalid security token. Please try again.');
header("Location: $app_root?page=credentials"); header("Location: $app_root?page=credentials");

View File

@ -4,80 +4,78 @@
*/ */
?> ?>
<div class="container mt-4"> <div class="tm-cred-card mx-auto">
<div class="row justify-content-center"> <div class="tm-profile-header">
<div class="col-md-8"> <p class="tm-profile-eyebrow">Security</p>
<div class="card"> <h2 class="tm-profile-title">Set up two-factor authentication</h2>
<div class="card-header"> <p class="tm-profile-subtitle">Protect your account with an extra verification step whenever you sign in.</p>
<h3>Set up two-factor authentication</h3>
</div> </div>
<div class="card-body">
<div class="alert alert-info"> <div class="tm-cred-intro 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> Two-factor authentication adds an extra layer of protection. After setup, you will sign in with both your password and a code from your authenticator app.
</div> </div>
<?php if (isset($error)): ?> <?php if (isset($error)): ?>
<div class="alert alert-danger"> <div class="alert alert-danger mb-4">
<?php echo htmlspecialchars($error); ?> <?php echo htmlspecialchars($error); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<?php if (isset($setupData) && is_array($setupData)): ?> <?php if (isset($setupData) && is_array($setupData)): ?>
<div class="setup-steps"> <div class="tm-cred-steps">
<h4>1. Install an authenticator app</h4> <div class="tm-cred-step">
<p>If you haven't already, install an authenticator app on your mobile device:</p> <h3>1. Install an authenticator app</h3>
<ul> <p>Use any TOTP-compatible app such as Google Authenticator, Microsoft Authenticator, or Authy.</p>
<li>Google Authenticator</li> </div>
<li>Microsoft Authenticator</li>
<li>Authy</li>
</ul>
<h4 class="mt-4">2. Scan the QR code</h4> <div class="tm-cred-step">
<p>Open your authenticator app and scan this QR code:</p> <h3>2. Scan the QR code</h3>
<p>Open your authenticator app and scan the QR code below.</p>
<div class="text-center my-4"> <div class="tm-cred-qr">
<div id="qrcode"></div> <div id="qrcode"></div>
<div class="mt-2"> <div class="tm-cred-secret">
<small class="text-muted">Can't scan? Use this code instead:</small><br> <small>Can&apos;t scan? Enter this code manually:</small>
<code class="secret-key"><?php echo htmlspecialchars($setupData['secret']); ?></code> <code><?php echo htmlspecialchars($setupData['secret']); ?></code>
</div>
</div> </div>
</div> </div>
<h4 class="mt-4">3. Verify setup</h4> <div class="tm-cred-step">
<p>Enter the 6-digit code from your authenticator app to verify the setup:</p> <h3>3. Verify setup</h3>
<p>Enter the 6-digit code shown in your authenticator app.</p>
<form method="post" action="?page=credentials&item=2fa&action=setup" class="mt-3"> <form method="post" action="?page=credentials&item=2fa&action=setup" class="tm-cred-form" novalidate>
<div class="form-group"> <div class="mb-3">
<label for="setup_code" class="form-label">One-time code</label>
<input type="text" <input type="text"
id="setup_code"
name="code" name="code"
class="form-control" class="form-control"
pattern="[0-9]{6}" pattern="[0-9]{6}"
maxlength="6" maxlength="6"
required required
placeholder="Enter 6-digit code"> placeholder="000000">
</div> </div>
<input type="hidden" name="secret" value="<?php echo htmlspecialchars($setupData['secret']); ?>"> <input type="hidden" name="secret" value="<?php echo htmlspecialchars($setupData['secret']); ?>">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>"> <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<button type="submit" class="btn btn-primary mt-3"> <button type="submit" class="btn btn-primary tm-contact-submit w-100">
Verify and enable 2FA Verify and enable 2FA
</button> </button>
</form> </form>
</div>
<div class="mt-4"> <div class="tm-cred-step">
<h4>Backup codes</h4> <h3>Backup codes</h3>
<p class="text-warning"> <p class="text-danger mb-3">
<strong>Important:</strong> Save these backup codes in a secure place. Save these codes somewhere secure. Each code can be used once if you lose access to your authenticator app.
If you lose access to your authenticator app, you can use these codes to sign in.
Each code can only be used once.
</p> </p>
<div class="backup-codes bg-light p-3 rounded"> <div class="tm-cred-backup">
<?php foreach ($setupData['backupCodes'] as $code): ?> <?php foreach ($setupData['backupCodes'] as $code): ?>
<code class="d-block"><?php echo htmlspecialchars($code); ?></code> <code><?php echo htmlspecialchars($code); ?></code>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<button class="btn btn-secondary mt-2" onclick="window.print()"> <button class="btn btn-outline-secondary mt-3" onclick="window.print()">
Print backup codes Print backup codes
</button> </button>
</div> </div>
@ -88,10 +86,6 @@
</div> </div>
<a href="?page=credentials" class="btn btn-primary">Back to credentials</a> <a href="?page=credentials" class="btn btn-primary">Back to credentials</a>
<?php endif; ?> <?php endif; ?>
</div>
</div>
</div>
</div>
</div> </div>
<?php if (isset($setupData) && is_array($setupData)): ?> <?php if (isset($setupData) && is_array($setupData)): ?>

View File

@ -4,27 +4,27 @@
*/ */
?> ?>
<div class="container mt-4"> <div class="auth-card mx-auto">
<div class="row justify-content-center"> <div class="auth-card-body">
<div class="col-md-6"> <div class="auth-header">
<div class="card"> <p class="auth-eyebrow">Security check</p>
<div class="card-header"> <h2 class="auth-title">Two-factor authentication</h2>
<h3>Two-factor authentication</h3> <p class="auth-subtitle">Enter the 6-digit code from your authenticator app to continue.</p>
</div> </div>
<div class="card-body">
<?php if (isset($error)): ?> <?php if (isset($error)): ?>
<div class="alert alert-danger"> <div class="alert alert-danger mb-4">
<?php echo htmlspecialchars($error); ?> <?php echo htmlspecialchars($error); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<p>Enter the 6-digit code from your authenticator app:</p> <form method="post" action="?page=login&action=verify" class="auth-form" novalidate>
<div class="text-center mb-3">
<form method="post" action="?page=login&action=verify" class="mt-3"> <label for="code" class="form-label">One-time code</label>
<div class="form-group">
<input type="text" <input type="text"
id="code"
name="code" name="code"
class="form-control form-control-lg text-center" class="form-control text-center auth-otp-input"
pattern="[0-9]{6}" pattern="[0-9]{6}"
maxlength="6" maxlength="6"
inputmode="numeric" inputmode="numeric"
@ -36,44 +36,40 @@
<input type="hidden" name="user_id" value="<?php echo htmlspecialchars($userId); ?>"> <input type="hidden" name="user_id" value="<?php echo htmlspecialchars($userId); ?>">
<button type="submit" class="btn btn-primary btn-block mt-4"> <button type="submit" class="btn btn-primary auth-submit w-100">
Verify code Verify code
</button> </button>
</form> </form>
<div class="mt-4"> <div class="mt-4 text-center">
<p class="text-muted text-center"> <p class="text-muted mb-2">Lost access to your authenticator app?</p>
Lost access to your authenticator app?<br> <button class="btn btn-link auth-link p-0" type="button" data-toggle="collapse" data-target="#backupCodeForm">
<a href="#" data-toggle="collapse" data-target="#backupCodeForm">
Use a backup code Use a backup code
</a> </button>
</p> </div>
<div class="collapse mt-3" id="backupCodeForm"> <div class="collapse mt-3" id="backupCodeForm">
<form method="post" action="?page=login&action=verify" class="mt-3"> <form method="post" action="?page=login&action=verify" class="auth-form" novalidate>
<div class="form-group"> <div class="mb-3">
<label>Enter backup code:</label> <label for="backup_code" class="form-label">Backup code</label>
<input type="text" <input type="text"
id="backup_code"
name="backup_code" name="backup_code"
class="form-control" class="form-control"
pattern="[a-f0-9]{8}" pattern="[a-f0-9]{8}"
maxlength="8" maxlength="8"
required required
placeholder="Enter backup code"> placeholder="Enter 8-character code">
</div> </div>
<input type="hidden" name="user_id" value="<?php echo htmlspecialchars($userId); ?>"> <input type="hidden" name="user_id" value="<?php echo htmlspecialchars($userId); ?>">
<button type="submit" class="btn btn-secondary btn-block"> <button type="submit" class="btn btn-secondary w-100">
Use backup code Use backup code
</button> </button>
</form> </form>
</div> </div>
</div> </div>
</div>
</div>
</div>
</div>
</div> </div>
<script> <script>

View File

@ -5,88 +5,80 @@
*/ */
?> ?>
<div class="container mt-4"> <div class="tm-cred-card mx-auto">
<div class="row justify-content-center"> <div class="tm-profile-header">
<div class="col-md-8"> <p class="tm-profile-eyebrow">Security</p>
<!-- Password Management --> <h2 class="tm-profile-title">Manage credentials</h2>
<div class="card mb-4"> <p class="tm-profile-subtitle">Update your password and keep two-factor authentication status in one place.</p>
<div class="card-header">
<h3>change password</h3>
</div> </div>
<div class="card-body">
<form method="post" action="?page=credentials&item=password"> <div class="tm-cred-grid">
<section class="tm-cred-panel">
<div class="tm-cred-panel-head">
<div>
<h3>Change password</h3>
<p>Choose a strong password to keep your account safe.</p>
</div>
<span class="badge bg-light text-dark">Required</span>
</div>
<form method="post" action="?page=credentials&item=password" class="tm-cred-form" novalidate>
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>"> <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<div class="form-group"> <div class="mb-3">
<label for="current_password">current password</label> <label for="current_password" class="form-label">Current password</label>
<input type="password" <input type="password" class="form-control" id="current_password" name="current_password" required>
class="form-control"
id="current_password"
name="current_password"
required>
</div> </div>
<div class="form-group mt-3"> <div class="mb-3">
<label for="new_password">new password</label> <label for="new_password" class="form-label">New password</label>
<input type="password" <input type="password" class="form-control" id="new_password" name="new_password" pattern=".{8,}" title="Password must be at least 8 characters long" required>
class="form-control" <small class="form-text text-muted">Minimum 8 characters</small>
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>
<div class="form-group mt-3"> <div class="mb-4">
<label for="confirm_password">confirm new password</label> <label for="confirm_password" class="form-label">Confirm new password</label>
<input type="password" <input type="password" class="form-control" id="confirm_password" name="confirm_password" pattern=".{8,}" required>
class="form-control"
id="confirm_password"
name="confirm_password"
pattern=".{8,}"
required>
</div> </div>
<div class="mt-4"> <button type="submit" class="btn btn-primary tm-contact-submit w-100">Save new password</button>
<button type="submit" class="btn btn-primary">change password</button>
</div>
</form> </form>
</div> </section>
</div>
<!-- 2FA Management --> <section class="tm-cred-panel">
<div class="card"> <div class="tm-cred-panel-head">
<div class="card-header"> <div>
<h3>two-factor authentication</h3> <h3>Two-factor authentication</h3>
<p>Strengthen security with a verification code from your authenticator app.</p>
</div>
<span class="badge <?= $has2fa ? 'bg-success' : 'bg-warning text-dark' ?>">
<?= $has2fa ? 'Enabled' : 'Disabled' ?>
</span>
</div> </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): ?> <?php if ($has2fa): ?>
<div class="alert alert-success"> <div class="alert alert-success d-flex align-items-center gap-2">
<i class="fas fa-check-circle"></i> two-factor authentication is enabled <i class="fas fa-shield-check"></i>
<span>Two-factor authentication is currently enabled.</span>
</div> </div>
<form method="post" action="?page=credentials&item=2fa&action=disable"> <form method="post" action="?page=credentials&item=2fa&action=disable" class="tm-cred-form">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>"> <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.')"> <button type="submit" class="btn btn-outline-danger w-100" onclick="return confirm('Disable two-factor authentication? This will make your account less secure.')">
disable two-factor authentication Disable 2FA
</button> </button>
</form> </form>
<?php else: ?> <?php else: ?>
<div class="alert alert-warning"> <div class="alert alert-warning d-flex align-items-center gap-2">
<i class="fas fa-exclamation-triangle"></i> two-factor authentication is not enabled <i class="fas fa-lock"></i>
<span>Two-factor authentication is not enabled yet.</span>
</div> </div>
<form method="post" action="?page=credentials&item=2fa&action=setup"> <form method="post" action="?page=credentials&item=2fa&action=setup" class="tm-cred-form">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>"> <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-outline-primary w-100">
set up two-factor authentication Set up 2FA
</button> </button>
</form> </form>
<?php endif; ?> <?php endif; ?>
</div> </section>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -5,10 +5,114 @@ html, body {
padding: 0; padding: 0;
} }
/* Credentials management */
.tm-cred-card {
max-width: 900px;
background: rgba(255,255,255,0.97);
border-radius: 1.5rem;
box-shadow: 0 25px 60px rgba(15, 23, 42, 0.12);
padding: 2.5rem;
margin: 2.5rem auto;
}
.tm-cred-intro {
border-radius: 1rem;
border: none;
}
.tm-cred-steps {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.tm-cred-step {
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 1.2rem;
padding: 1.5rem;
background: rgba(248, 250, 252, 0.75);
}
.tm-cred-qr {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
padding: 1.5rem;
border-radius: 1rem;
background: #fff;
border: 1px dashed rgba(148, 163, 184, 0.5);
}
.tm-cred-secret code,
.tm-cred-backup code {
display: inline-block;
padding: 0.35rem 0.6rem;
border-radius: 0.6rem;
background: #0f172a;
color: #f8fafc;
font-weight: 600;
letter-spacing: 0.08em;
}
.tm-cred-backup {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.75rem;
padding: 1rem;
border-radius: 1rem;
background: #fff;
border: 1px solid rgba(148, 163, 184, 0.2);
}
.tm-cred-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
.tm-cred-panel {
border: 1px solid rgba(148, 163, 184, 0.25);
border-radius: 1.25rem;
padding: 1.5rem;
background: rgba(248, 250, 252, 0.75);
}
.tm-cred-panel-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
gap: 1rem;
}
.tm-cred-panel-head h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
}
.tm-cred-form .form-label {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.tm-cred-form .form-control {
border-radius: 0.85rem;
border: 1px solid rgba(148, 163, 184, 0.7);
}
@media (max-width: 768px) {
.tm-cred-card {
padding: 2rem 1.5rem;
}
}
/* Profile form */ /* Profile form */
.tm-profile-card { .tm-profile-card {
max-width: 1100px; max-width: 1100px;
margin: 2.5rem auto; margin: 0rem auto 1.5rem;
background: rgba(255, 255, 255, 0.97); background: rgba(255, 255, 255, 0.97);
border-radius: 1.5rem; border-radius: 1.5rem;
box-shadow: 0 30px 70px rgba(15, 23, 42, 0.12); box-shadow: 0 30px 70px rgba(15, 23, 42, 0.12);