63 lines
2.5 KiB
HTML
63 lines
2.5 KiB
HTML
|
|
{{define "mfa_setup"}}
|
|||
|
|
{{template "base" .}}
|
|||
|
|
{{end}}
|
|||
|
|
|
|||
|
|
{{define "mfa_setup_content"}}
|
|||
|
|
<div class="max-w-lg mx-auto p-6">
|
|||
|
|
<h1 class="text-2xl font-bold text-white mb-2">Set up Multi‑Factor Authentication</h1>
|
|||
|
|
<p class="text-gray-400 mb-6">Scan the QR code below with your authenticator app (Google Authenticator, Authy, 1Password, etc.), or enter the secret manually, then enter a code to confirm.</p>
|
|||
|
|
|
|||
|
|
<div class="bg-slate-800 border border-slate-700 rounded p-4 mb-4">
|
|||
|
|
<div class="flex items-start gap-4">
|
|||
|
|
<img alt="QR Code" class="bg-white rounded p-2" src="https://api.qrserver.com/v1/create-qr-code/?size=180x180&data={{urlquery .OTPAuthURI}}" width="180" height="180"/>
|
|||
|
|
<div class="flex-1">
|
|||
|
|
<div class="text-sm text-gray-400">Secret</div>
|
|||
|
|
<div class="font-mono text-lg text-white break-all">{{.Secret}}</div>
|
|||
|
|
<div class="text-sm text-gray-400 mt-2">URI</div>
|
|||
|
|
<div class="text-xs text-gray-300 break-all">{{.OTPAuthURI}}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<form id="mfa-verify-form" class="space-y-4">
|
|||
|
|
<div>
|
|||
|
|
<label for="code" class="block text-sm font-medium text-gray-300 mb-2">Enter 6‑digit code</label>
|
|||
|
|
<input id="code" name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" class="form-input" placeholder="123456" required />
|
|||
|
|
</div>
|
|||
|
|
<div class="flex justify-end">
|
|||
|
|
<button type="submit" class="btn-primary"><i class="fas fa-check mr-2"></i>Verify and Enable</button>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
{{end}}
|
|||
|
|
|
|||
|
|
{{define "mfa_setup_scripts"}}
|
|||
|
|
<script>
|
|||
|
|
function getCSRF() {
|
|||
|
|
const m = document.cookie.match(/(?:^|; )csrf_token=([^;]+)/);
|
|||
|
|
return m && m[1] ? decodeURIComponent(m[1]) : '';
|
|||
|
|
}
|
|||
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|||
|
|
const form = document.getElementById('mfa-verify-form');
|
|||
|
|
if (!form) return;
|
|||
|
|
form.addEventListener('submit', async (e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
const fd = new FormData(form);
|
|||
|
|
const params = new URLSearchParams(Array.from(fd.entries()));
|
|||
|
|
try {
|
|||
|
|
const res = await fetch('/editor/profile/mfa/verify', { method: 'POST', headers: { 'X-CSRF-Token': getCSRF() }, body: params });
|
|||
|
|
const data = await res.json().catch(() => ({}));
|
|||
|
|
if (res.ok && data.success) {
|
|||
|
|
showNotification('MFA enabled', 'success');
|
|||
|
|
window.location.href = '/editor/profile';
|
|||
|
|
} else {
|
|||
|
|
throw new Error(data.error || res.statusText);
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
showNotification('Verification failed: ' + e.message, 'error');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
{{end}}
|