Files
domnitor/app/Views/2fa/backup-codes.php
Hosteroid 6e8fef9b79 Add two-factor authentication (2FA) support
Introduces two-factor authentication (2FA) with TOTP, backup codes, and email codes. Adds controllers, services, views, and migration for 2FA setup, verification, and management. Updates user and settings models, email helper, and relevant controllers to support 2FA policy enforcement, configuration, and user flows. Enhances security by allowing admins to require or disable 2FA, and provides backup code generation and management for account recovery.
2025-10-16 17:25:06 +03:00

210 lines
8.6 KiB
PHP

<?php
$title = '2FA Backup Codes';
$pageTitle = '2FA Backup Codes';
$pageDescription = 'Save these backup codes in a safe place';
$pageIcon = 'fas fa-key';
ob_start();
?>
<div class="max-w-2xl mx-auto">
<div class="bg-white shadow rounded-lg">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-semibold text-gray-900">2FA Backup Codes</h3>
<p class="text-sm text-gray-600 mt-1">Save these codes in a safe place - they can be used to access your account if you lose your authenticator device</p>
</div>
<div class="p-6">
<!-- Warning -->
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<div class="flex items-start">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-triangle text-red-400"></i>
</div>
<div class="ml-3">
<h4 class="text-sm font-medium text-red-800">Important Security Notice</h4>
<p class="text-sm text-red-700 mt-1">
These backup codes are shown only once. Each code can only be used once. Store them securely and never share them with anyone.
</p>
</div>
</div>
</div>
<!-- Backup Codes -->
<div class="mb-6">
<div class="flex items-center justify-between mb-4">
<h4 class="text-md font-semibold text-gray-900">Your Backup Codes</h4>
<button onclick="printCodes()" class="text-sm text-primary hover:text-primary-dark">
<i class="fas fa-print mr-1"></i>
Print Codes
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3" id="backup-codes">
<?php foreach ($backupCodes as $index => $code): ?>
<div class="flex items-center justify-between bg-gray-50 p-3 rounded-lg border">
<code class="font-mono text-sm text-gray-900"><?= htmlspecialchars($code) ?></code>
<button onclick="copyCode('<?= htmlspecialchars($code) ?>', this)"
class="ml-2 px-2 py-1 text-xs bg-gray-200 text-gray-700 rounded hover:bg-gray-300">
<i class="fas fa-copy"></i>
</button>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Instructions -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<h4 class="text-sm font-medium text-blue-800 mb-2">How to use backup codes:</h4>
<ul class="text-sm text-blue-700 space-y-1">
<li>• When logging in, enter a backup code instead of your 2FA code</li>
<li>• Each backup code can only be used once</li>
<li>• After using a code, it will be automatically removed from your account</li>
<li>• If you run out of backup codes, you'll need to disable and re-enable 2FA</li>
</ul>
</div>
<!-- Actions -->
<div class="flex items-center justify-between pt-4 border-t border-gray-200">
<a href="/profile" class="text-sm text-gray-600 hover:text-gray-500">
<i class="fas fa-arrow-left mr-1"></i>
Back to Profile
</a>
<div class="flex space-x-3">
<button onclick="downloadCodes()"
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
<i class="fas fa-download mr-2"></i>
Download
</button>
<a href="/"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-primary hover:bg-primary-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
<i class="fas fa-check mr-2"></i>
I've Saved These Codes
</a>
</div>
</div>
</div>
</div>
</div>
<script>
function copyCode(code, button) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(code).then(() => {
showCopySuccess(button);
}).catch(() => {
fallbackCopyTextToClipboard(code);
});
} else {
fallbackCopyTextToClipboard(code);
}
}
function showCopySuccess(button) {
const originalHTML = button.innerHTML;
button.innerHTML = '<i class="fas fa-check"></i>';
button.classList.remove('bg-gray-200', 'hover:bg-gray-300', 'text-gray-700');
button.classList.add('bg-green-500', 'text-white');
setTimeout(() => {
button.innerHTML = originalHTML;
button.classList.remove('bg-green-500', 'text-white');
button.classList.add('bg-gray-200', 'hover:bg-gray-300', 'text-gray-700');
}, 2000);
}
function fallbackCopyTextToClipboard(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = '0';
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
alert('Code copied to clipboard!');
} catch (err) {
console.error('Copy failed:', err);
alert('Failed to copy code');
}
document.body.removeChild(textArea);
}
function printCodes() {
const printWindow = window.open('', '_blank');
const codes = <?= json_encode($backupCodes) ?>;
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>2FA Backup Codes - <?= htmlspecialchars($user['full_name'] ?? $user['username']) ?></title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; line-height: 1.6; }
h1 { color: #333; margin-bottom: 20px; }
.codes { background: #f5f5f5; padding: 15px; border: 1px solid #ddd; margin: 20px 0; }
.code { margin: 5px 0; font-family: monospace; }
.warning { background: #fee; border: 1px solid #fcc; padding: 10px; margin: 10px 0; }
</style>
</head>
<body>
<h1>2FA Backup Codes</h1>
<p><strong>Account:</strong> <?= htmlspecialchars($user['full_name'] ?? $user['username']) ?> (<?= htmlspecialchars($user['email']) ?>)</p>
<p><strong>Generated:</strong> ${new Date().toLocaleString()}</p>
<div class="warning">
<strong>Important:</strong> Store these codes in a safe place. Each code can only be used once.
</div>
<div class="codes">
${codes.map((code, index) => `<div class="code">${index + 1}. ${code}</div>`).join('')}
</div>
</body>
</html>
`);
printWindow.document.close();
printWindow.print();
}
function downloadCodes() {
const codes = <?= json_encode($backupCodes) ?>;
const content = `2FA Backup Codes
Account: <?= htmlspecialchars($user['full_name'] ?? $user['username']) ?> (<?= htmlspecialchars($user['email']) ?>)
Generated: ${new Date().toLocaleString()}
IMPORTANT: Store these codes in a safe place. Each code can only be used once.
${codes.map((code, index) => `${index + 1}. ${code}`).join('\n')}
If you lose access to your authenticator app, you can use these codes to log in.
Generate new codes if you run out or if you suspect they've been compromised.`;
const blob = new Blob([content], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '2fa-backup-codes.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
</script>
<?php
$content = ob_get_clean();
require __DIR__ . '/../layout/base.php';
?>