Remove prompt() dialogs, breach warnings, cost display; fix 2FA disable UI

- Replace browser prompt() with inline UI for: 2FA disable (password field),
  admin password reset (inline input), admin test email (inline input)
- Remove all password breach warning UI (login, register, settings)
  Backend HIBP check endpoint remains but is no longer called from frontend
- Remove model cost display from dropdown and header badge
- Hide empty cost-badge element in header
- Fix model dropdown to flat list (no category grouping)
This commit is contained in:
Daniel 2026-04-09 02:27:55 +02:00
parent 04f3aa56cb
commit 4fa2b58d75
5 changed files with 83 additions and 69 deletions

View file

@ -95,7 +95,6 @@
<div class="form-group" style="max-width:340px;">
<label>New Password (8+ characters)</label>
<input type="password" id="pw-new" minlength="8" placeholder="Enter new password">
<div id="pw-new-warning" class="pw-breach-warning" style="display:none;"></div>
</div>
<div class="form-group" style="max-width:340px;">
<label>Confirm New Password</label>
@ -110,7 +109,17 @@
<h3><i class="fas fa-shield-halved"></i> Two-Factor Authentication</h3>
<p id="2fa-status">Status: Loading...</p>
<button id="btn-setup-2fa" class="btn-sm btn-primary">Enable 2FA</button>
<button id="btn-disable-2fa" class="btn-sm btn-ghost hidden">Disable 2FA</button>
<button id="btn-disable-2fa" class="btn-sm btn-ghost hidden" style="color:var(--red);">Disable 2FA</button>
<div id="2fa-disable-confirm" class="hidden" style="margin-top:10px;padding:12px;background:var(--g50);border-radius:8px;border:1px solid var(--g200);max-width:340px;">
<div class="form-group" style="margin-bottom:8px;">
<label>Enter your password to confirm:</label>
<input type="password" id="2fa-disable-password" placeholder="Password">
</div>
<div style="display:flex;gap:6px;">
<button id="btn-disable-2fa-confirm" class="btn-sm btn-primary" style="background:var(--red);border-color:var(--red);">Confirm Disable</button>
<button id="btn-disable-2fa-cancel" class="btn-sm btn-ghost">Cancel</button>
</div>
</div>
<div id="2fa-setup" class="hidden">
<p>Scan this QR code with your authenticator app:</p>
<img id="2fa-qr" src="" alt="QR Code">

View file

@ -46,7 +46,6 @@
<div class="form-group">
<label>Password</label>
<input type="password" id="login-password" required placeholder="••••••••">
<div id="login-pw-warning" class="pw-breach-warning" style="display:none;"></div>
</div>
<div id="totp-group" class="form-group hidden">
<label>2FA Code</label>
@ -85,7 +84,6 @@
<div class="form-group">
<label>Password (8+ characters)</label>
<input type="password" id="reg-password" required minlength="8" placeholder="••••••••">
<div id="reg-pw-warning" class="pw-breach-warning" style="display:none;"></div>
</div>
<div class="cf-turnstile" data-sitekey="0x4AAAAAAC0VtKAhC8rzpMx6" data-theme="light"></div>
<button type="submit" class="btn-auth">Create Account</button>
@ -143,7 +141,7 @@
<div class="model-selector">
<label><i class="fas fa-robot"></i></label>
<select id="global-model-select"></select>
<span id="model-cost-badge" class="cost-badge"></span>
<span id="model-cost-badge" class="cost-badge" style="display:none;"></span>
</div>
<div class="header-buttons">
<button id="btn-settings" class="btn-header" title="Settings">

View file

@ -210,12 +210,27 @@
break;
case 'reset-pw':
var newPw = prompt('New password for ' + email + ' (8+ characters):');
if (!newPw) return;
if (newPw.length < 8) { showToast('Password must be 8+ characters', 'error'); return; }
adminPost('/api/admin/users/' + userId + '/reset-password', { newPassword: newPw }, function() {
showToast('Password reset for ' + email, 'success');
});
// Show inline password input instead of prompt()
var existingResetRow = document.getElementById('admin-reset-pw-row');
if (existingResetRow) existingResetRow.remove();
var actionCell = btn.closest('td') || btn.parentElement;
var resetRow = document.createElement('div');
resetRow.id = 'admin-reset-pw-row';
resetRow.style.cssText = 'margin-top:8px;display:flex;gap:6px;align-items:center;';
resetRow.innerHTML = '<input type="password" id="admin-reset-pw-input" placeholder="New password (8+)" style="padding:5px 8px;border:1px solid var(--g300);border-radius:6px;font-size:12px;width:160px;">'
+ '<button class="btn-sm btn-primary" id="admin-reset-pw-submit" style="font-size:11px;">Set</button>'
+ '<button class="btn-sm btn-ghost" id="admin-reset-pw-cancel" style="font-size:11px;">Cancel</button>';
actionCell.appendChild(resetRow);
document.getElementById('admin-reset-pw-input').focus();
document.getElementById('admin-reset-pw-submit').onclick = function() {
var val = document.getElementById('admin-reset-pw-input').value;
if (!val || val.length < 8) { showToast('Password must be 8+ characters', 'error'); return; }
adminPost('/api/admin/users/' + userId + '/reset-password', { newPassword: val }, function() {
showToast('Password reset for ' + email, 'success');
resetRow.remove();
});
};
document.getElementById('admin-reset-pw-cancel').onclick = function() { resetRow.remove(); };
break;
case 'delete':
@ -388,19 +403,34 @@
function sendTestEmail() {
var template = document.getElementById('cms-email-template').value;
var to = prompt('Send test email to (enter email address):');
if (!to || !to.trim()) return;
fetch('/api/admin/config/test-email', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ to: to.trim(), template: template })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) showToast('Test email sent to ' + to, 'success');
else showToast(data.error || 'Send failed', 'error');
})
.catch(function() { showToast('Request failed', 'error'); });
var sendBtn = document.getElementById('btn-test-email');
// Show inline email input instead of prompt()
var existing = document.getElementById('admin-test-email-row');
if (existing) { existing.remove(); return; }
var row = document.createElement('div');
row.id = 'admin-test-email-row';
row.style.cssText = 'margin-top:8px;display:flex;gap:6px;align-items:center;';
row.innerHTML = '<input type="email" id="admin-test-email-input" placeholder="recipient@example.com" style="padding:5px 8px;border:1px solid var(--g300);border-radius:6px;font-size:12px;width:220px;">'
+ '<button class="btn-sm btn-primary" id="admin-test-email-submit" style="font-size:11px;">Send</button>'
+ '<button class="btn-sm btn-ghost" id="admin-test-email-cancel" style="font-size:11px;">Cancel</button>';
if (sendBtn) sendBtn.parentElement.appendChild(row);
document.getElementById('admin-test-email-input').focus();
document.getElementById('admin-test-email-submit').onclick = function() {
var to = document.getElementById('admin-test-email-input').value.trim();
if (!to) { showToast('Enter an email address', 'error'); return; }
fetch('/api/admin/config/test-email', {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({ to: to, template: template })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) { showToast('Test email sent to ' + to, 'success'); row.remove(); }
else showToast(data.error || 'Send failed', 'error');
})
.catch(function() { showToast('Request failed', 'error'); });
};
document.getElementById('admin-test-email-cancel').onclick = function() { row.remove(); };
}
// ---- AI PROMPTS ----

View file

@ -159,11 +159,6 @@ document.addEventListener('DOMContentLoaded', function() {
if (typeof renderAudioBackups === 'function') renderAudioBackups();
if (typeof loadDocuments === 'function') loadDocuments();
initBrowserWhisperSettings();
// Wire HIBP check on change-password new password field
var pwNewEl = document.getElementById('pw-new');
if (pwNewEl && typeof checkPasswordBreach === 'function') {
pwNewEl.onblur = function() { checkPasswordBreach(pwNewEl, 'pw-new-warning'); };
}
}
if (e.detail && e.detail.tab === 'faq') {
// Wire FAQ accordion after component loads
@ -299,7 +294,7 @@ document.addEventListener('DOMContentLoaded', function() {
window._currentModels.forEach(function(m) {
var opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.name + (m.cost && m.cost !== '?' ? ' (' + m.cost + ')' : '');
opt.textContent = m.name;
selectEl.appendChild(opt);
});
}
@ -311,8 +306,7 @@ document.addEventListener('DOMContentLoaded', function() {
window._buildModelOptions(modelSelect);
// Select the admin-configured default
if (defaultModelId) modelSelect.value = defaultModelId;
var defaultM = window._currentModels.find(function(x) { return x.id === modelSelect.value; });
if (costBadge) costBadge.textContent = window._currentProvider.toUpperCase() + ' | ' + (defaultM ? defaultM.cost : '');
if (costBadge) costBadge.textContent = '';
}
// Populate all per-tab model selectors already in DOM
@ -326,7 +320,6 @@ document.addEventListener('DOMContentLoaded', function() {
if (modelSelect) {
modelSelect.addEventListener('change', function() {
var m = window._currentModels.find(function(x) { return x.id === modelSelect.value; });
if (m && costBadge) costBadge.textContent = window._currentProvider.toUpperCase() + ' | ' + m.cost;
showToast('Model: ' + modelSelect.value.split('/').pop(), 'info');
});
}

View file

@ -286,14 +286,32 @@ document.addEventListener('DOMContentLoaded', function() {
// Disable 2FA
if (target.id === 'btn-disable-2fa' || target.closest('#btn-disable-2fa')) {
e.preventDefault();
var pw = prompt('Enter password to disable 2FA:');
if (!pw) return;
var confirmBox = document.getElementById('2fa-disable-confirm');
if (confirmBox) { confirmBox.classList.remove('hidden'); }
return;
}
if (target.id === 'btn-disable-2fa-cancel' || target.closest('#btn-disable-2fa-cancel')) {
e.preventDefault();
var confirmBox = document.getElementById('2fa-disable-confirm');
if (confirmBox) { confirmBox.classList.add('hidden'); }
var pwField = document.getElementById('2fa-disable-password');
if (pwField) pwField.value = '';
return;
}
if (target.id === 'btn-disable-2fa-confirm' || target.closest('#btn-disable-2fa-confirm')) {
e.preventDefault();
var pwField = document.getElementById('2fa-disable-password');
var pw = pwField ? pwField.value : '';
if (!pw) { showToast('Enter your password', 'error'); return; }
fetch('/api/auth/disable-2fa', {
method: 'POST', headers: getAuthHeaders(),
body: JSON.stringify({ password: pw })
})
.then(function(r) { return r.json(); })
.then(function(data) {
var confirmBox = document.getElementById('2fa-disable-confirm');
if (confirmBox) confirmBox.classList.add('hidden');
if (pwField) pwField.value = '';
if (data.success) { showToast('2FA disabled', 'info'); load2FAStatus(); }
else showToast(data.error || 'Failed', 'error');
});
@ -505,38 +523,6 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
// ---- PASSWORD BREACH CHECK (inline, on blur) ----
window.checkPasswordBreach = function checkPasswordBreach(inputEl, warningId) {
var pw = inputEl.value;
var warningEl = document.getElementById(warningId);
if (!warningEl) return;
if (!pw || pw.length < 8) { warningEl.style.display = 'none'; return; }
fetch('/api/auth/check-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: pw })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.breached) {
warningEl.innerHTML = '<i class="fas fa-triangle-exclamation"></i> This password has appeared in ' + data.count.toLocaleString() + ' data breaches. You can still use it, but consider a different one.';
warningEl.style.display = 'block';
} else {
warningEl.style.display = 'none';
}
})
.catch(function() { warningEl.style.display = 'none'; });
}
var regPwInput = document.getElementById('reg-password');
if (regPwInput) {
regPwInput.addEventListener('blur', function() { checkPasswordBreach(regPwInput, 'reg-pw-warning'); });
}
var loginPwInput = document.getElementById('login-password');
if (loginPwInput) {
loginPwInput.addEventListener('blur', function() { checkPasswordBreach(loginPwInput, 'login-pw-warning'); });
}
// ---- SESSION MANAGEMENT ----
function timeAgo(date) {
var s = Math.floor((Date.now() - date.getTime()) / 1000);
@ -630,8 +616,6 @@ document.addEventListener('DOMContentLoaded', function() {
if (data.passwordWarning) setTimeout(function() { showToast(data.passwordWarning, 'warning'); }, 1500);
current.value = ''; newPw.value = ''; confirm.value = '';
if (status) { status.textContent = ''; }
var warning = document.getElementById('pw-new-warning');
if (warning) warning.style.display = 'none';
if (typeof loadSessions === 'function') loadSessions();
} else {
showToast(data.error || 'Failed', 'error');