Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
115
status/js/totp-auth.js
Normal file
115
status/js/totp-auth.js
Normal file
@@ -0,0 +1,115 @@
|
||||
// ===== TOTP AUTHENTICATION GATE =====
|
||||
(function() {
|
||||
function updateTotpLogo() {
|
||||
const card = document.querySelector('.totp-card');
|
||||
if (!card) return;
|
||||
const bg = getComputedStyle(card).backgroundColor;
|
||||
const m = bg.match(/\d+/g);
|
||||
if (!m) return;
|
||||
const lum = (0.299 * +m[0] + 0.587 * +m[1] + 0.114 * +m[2]) / 255;
|
||||
const dark = card.querySelector('.totp-logo-dark');
|
||||
const light = card.querySelector('.totp-logo-light');
|
||||
if (dark) dark.style.display = lum > 0.5 ? 'none' : '';
|
||||
if (light) light.style.display = lum > 0.5 ? '' : 'none';
|
||||
}
|
||||
|
||||
function showTotpOverlay() {
|
||||
const overlay = document.getElementById('totp-overlay');
|
||||
if (overlay) {
|
||||
overlay.classList.add('show');
|
||||
setTimeout(updateTotpLogo, 50);
|
||||
const firstInput = overlay.querySelector('.totp-digits input');
|
||||
if (firstInput) setTimeout(() => firstInput.focus(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
function hideTotpOverlay() {
|
||||
const overlay = document.getElementById('totp-overlay');
|
||||
if (overlay) overlay.classList.remove('show');
|
||||
}
|
||||
|
||||
// Setup digit input UX
|
||||
const container = document.getElementById('totp-digits');
|
||||
if (container) {
|
||||
const inputs = container.querySelectorAll('input');
|
||||
inputs.forEach((input, idx) => {
|
||||
input.addEventListener('input', (e) => {
|
||||
const val = e.target.value.replace(/\D/g, '');
|
||||
e.target.value = val.slice(0, 1);
|
||||
if (val && idx < inputs.length - 1) inputs[idx + 1].focus();
|
||||
const code = Array.from(inputs).map(i => i.value).join('');
|
||||
if (code.length === 6) submitTotpCode(code);
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Backspace' && !e.target.value && idx > 0) {
|
||||
inputs[idx - 1].focus();
|
||||
inputs[idx - 1].value = '';
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('paste', (e) => {
|
||||
e.preventDefault();
|
||||
const pasted = (e.clipboardData.getData('text') || '').replace(/\D/g, '');
|
||||
if (pasted.length >= 6) {
|
||||
inputs.forEach((inp, i) => { inp.value = pasted[i] || ''; });
|
||||
inputs[5].focus();
|
||||
submitTotpCode(pasted.slice(0, 6));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function submitTotpCode(code) {
|
||||
const errorEl = document.getElementById('totp-error');
|
||||
errorEl.textContent = 'Verifying...';
|
||||
errorEl.className = 'totp-error verifying';
|
||||
|
||||
try {
|
||||
const res = await secureFetch('/api/v1/totp/verify', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ code })
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
errorEl.textContent = '';
|
||||
hideTotpOverlay();
|
||||
// Check if redirected here from another service
|
||||
const redirect = safeSessionGet('totp_redirect');
|
||||
if (redirect) {
|
||||
try { sessionStorage.removeItem('totp_redirect'); } catch (_) {}
|
||||
window.location.href = redirect;
|
||||
return;
|
||||
}
|
||||
// Initialize dashboard
|
||||
if (typeof window.initializeDashboard === 'function') {
|
||||
window.initializeDashboard();
|
||||
}
|
||||
} else {
|
||||
errorEl.textContent = data.error || 'Invalid code';
|
||||
errorEl.className = 'totp-error';
|
||||
const inputs = document.querySelectorAll('#totp-digits input');
|
||||
inputs.forEach(i => { i.value = ''; });
|
||||
inputs[0]?.focus();
|
||||
}
|
||||
} catch (e) {
|
||||
errorEl.textContent = 'Connection error';
|
||||
errorEl.className = 'totp-error';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle ?auth=required redirect from Caddy SSO
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('auth') === 'required') {
|
||||
const returnUrl = urlParams.get('return');
|
||||
if (returnUrl && returnUrl.includes(SITE.tld)) {
|
||||
safeSessionSet('totp_redirect', returnUrl);
|
||||
}
|
||||
// Clean URL
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
}
|
||||
|
||||
window._showTotpOverlay = showTotpOverlay;
|
||||
})();
|
||||
Reference in New Issue
Block a user