146 lines
5.5 KiB
HTML
146 lines
5.5 KiB
HTML
|
|
{% extends "shared/frontend/base.html" %}
|
||
|
|
|
||
|
|
{% block title %}2FA Setup - BMC Hub{% endblock %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
<div class="container">
|
||
|
|
<div class="row justify-content-center align-items-center" style="min-height: 80vh;">
|
||
|
|
<div class="col-md-6 col-lg-5">
|
||
|
|
<div class="card shadow-sm">
|
||
|
|
<div class="card-body p-4">
|
||
|
|
<div class="text-center mb-4">
|
||
|
|
<h2 class="fw-bold" style="color: var(--primary-color);">2FA Setup</h2>
|
||
|
|
<p class="text-muted">Opsaet tofaktor for din konto</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="statusMessage" class="alert alert-info" role="alert">
|
||
|
|
Klik "Generer 2FA" for at starte opsaetningen.
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="d-grid gap-2 mb-3">
|
||
|
|
<button class="btn btn-primary" id="generateBtn">
|
||
|
|
<i class="bi bi-shield-lock me-2"></i>
|
||
|
|
Generer 2FA
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="setupDetails" class="d-none">
|
||
|
|
<div class="mb-3">
|
||
|
|
<label class="form-label">Secret</label>
|
||
|
|
<input type="text" class="form-control" id="totpSecret" readonly>
|
||
|
|
</div>
|
||
|
|
<div class="mb-3">
|
||
|
|
<label class="form-label">Provisioning URI</label>
|
||
|
|
<textarea class="form-control" id="provisioningUri" rows="3" readonly></textarea>
|
||
|
|
</div>
|
||
|
|
<div class="mb-3">
|
||
|
|
<label class="form-label">2FA-kode</label>
|
||
|
|
<input type="text" class="form-control" id="otpCode" placeholder="Indtast 2FA-kode">
|
||
|
|
</div>
|
||
|
|
<div class="d-grid gap-2">
|
||
|
|
<button class="btn btn-success" id="enableBtn">
|
||
|
|
<i class="bi bi-check-circle me-2"></i>
|
||
|
|
Aktiver 2FA
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="text-center mt-3">
|
||
|
|
<a href="/" class="text-decoration-none text-muted">Spring over for nu</a>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
const statusMessage = document.getElementById('statusMessage');
|
||
|
|
const generateBtn = document.getElementById('generateBtn');
|
||
|
|
const enableBtn = document.getElementById('enableBtn');
|
||
|
|
const setupDetails = document.getElementById('setupDetails');
|
||
|
|
const totpSecret = document.getElementById('totpSecret');
|
||
|
|
const provisioningUri = document.getElementById('provisioningUri');
|
||
|
|
const otpCode = document.getElementById('otpCode');
|
||
|
|
|
||
|
|
async function ensureAuthenticated() {
|
||
|
|
const token = localStorage.getItem('access_token');
|
||
|
|
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/v1/auth/me', { headers, credentials: 'include' });
|
||
|
|
if (!response.ok) {
|
||
|
|
window.location.href = '/login';
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
window.location.href = '/login';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
generateBtn.addEventListener('click', async () => {
|
||
|
|
statusMessage.className = 'alert alert-info';
|
||
|
|
statusMessage.textContent = 'Genererer 2FA...';
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/v1/auth/2fa/setup', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
credentials: 'include'
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
if (!response.ok) {
|
||
|
|
statusMessage.className = 'alert alert-danger';
|
||
|
|
statusMessage.textContent = data.detail || 'Kunne ikke generere 2FA.';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
totpSecret.value = data.secret || '';
|
||
|
|
provisioningUri.value = data.provisioning_uri || '';
|
||
|
|
setupDetails.classList.remove('d-none');
|
||
|
|
statusMessage.className = 'alert alert-success';
|
||
|
|
statusMessage.textContent = '2FA secret genereret. Indtast koden fra din authenticator.';
|
||
|
|
} catch (error) {
|
||
|
|
statusMessage.className = 'alert alert-danger';
|
||
|
|
statusMessage.textContent = 'Kunne ikke generere 2FA.';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
enableBtn.addEventListener('click', async () => {
|
||
|
|
const code = (otpCode.value || '').trim();
|
||
|
|
if (!code) {
|
||
|
|
statusMessage.className = 'alert alert-warning';
|
||
|
|
statusMessage.textContent = 'Indtast 2FA-koden.';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch('/api/v1/auth/2fa/enable', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
credentials: 'include',
|
||
|
|
body: JSON.stringify({ otp_code: code })
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
if (!response.ok) {
|
||
|
|
statusMessage.className = 'alert alert-danger';
|
||
|
|
statusMessage.textContent = data.detail || 'Kunne ikke aktivere 2FA.';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
statusMessage.className = 'alert alert-success';
|
||
|
|
statusMessage.textContent = '2FA aktiveret. Du bliver sendt videre.';
|
||
|
|
setTimeout(() => {
|
||
|
|
window.location.href = '/';
|
||
|
|
}, 1200);
|
||
|
|
} catch (error) {
|
||
|
|
statusMessage.className = 'alert alert-danger';
|
||
|
|
statusMessage.textContent = 'Kunne ikke aktivere 2FA.';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
ensureAuthenticated();
|
||
|
|
</script>
|
||
|
|
{% endblock %}
|