fix: harden auth and fix frontend issues
- Add builtin admin user with full access, immutable permissions (lib/db.py, lib/auth_users.py, webui/static/pages/users.js) - Fix passkeys TypeError on string throws (webui/static/pages/passkeys.js) - Add zero-permission warning in create user modal (webui/static/pages/users.js) - Restore readonly on proxy paths textarea (webui/static/pages/proxy.js) - Mask credential ownership errors to prevent enumeration (lib/webauthn.py, tests/test_auth.py)
This commit is contained in:
@@ -108,7 +108,7 @@ function addCredentialModal() {
|
||||
});
|
||||
|
||||
if (!beginRes.ok) {
|
||||
throw beginRes.error || 'Registration failed';
|
||||
throw new Error(beginRes.error || 'Registration failed');
|
||||
}
|
||||
|
||||
const options = beginRes.data;
|
||||
@@ -129,15 +129,16 @@ function addCredentialModal() {
|
||||
});
|
||||
|
||||
if (!finishRes.ok) {
|
||||
throw finishRes.error || 'Registration verification failed';
|
||||
throw new Error(finishRes.error || 'Registration verification failed');
|
||||
}
|
||||
|
||||
toast('Passkey registered', 'success');
|
||||
closeModal();
|
||||
loadCredentials();
|
||||
} catch (e) {
|
||||
if (!e.message.toLowerCase().includes('cancelled')) {
|
||||
toast(e.message || 'Registration failed', 'error');
|
||||
const msg = typeof e === 'string' ? e : (e.message || 'Registration failed');
|
||||
if (!msg.toLowerCase().includes('cancelled')) {
|
||||
toast(msg, 'error');
|
||||
}
|
||||
} finally {
|
||||
setModalProcessing(false);
|
||||
@@ -180,14 +181,15 @@ function confirmRemove(credentialId, credentialName) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw res.error || 'Removal failed';
|
||||
throw new Error(res.error || 'Removal failed');
|
||||
}
|
||||
|
||||
toast('Passkey removed', 'success');
|
||||
closeModal();
|
||||
loadCredentials();
|
||||
} catch (e) {
|
||||
toast(e.message || 'Removal failed', 'error');
|
||||
const msg = typeof e === 'string' ? e : (e.message || 'Removal failed');
|
||||
toast(msg, 'error');
|
||||
} finally {
|
||||
setModalProcessing(false);
|
||||
refreshModals();
|
||||
|
||||
@@ -165,7 +165,7 @@ function editDomain(d, state) {
|
||||
postRender: (inner) => {
|
||||
const certSelect = inner.querySelector('#pe-cert');
|
||||
if (certSelect) certSelect.value = selectedCert;
|
||||
for (const id of ['pe-domain', 'pe-backend']) {
|
||||
for (const id of ['pe-domain', 'pe-backend', 'pe-paths']) {
|
||||
const el = inner.querySelector('#' + id);
|
||||
if (el) { el.readOnly = true; el.style.background = '#f5f5f5'; }
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import { h, definePage, reactive } from '/static/hoover/index.js';
|
||||
import { html, PageHeader, Table, Badge, ConfirmDelete, Empty, Card, openModal, closeModal, formModal, apiFetch, toast, esc } from '/static/hoover/index.js';
|
||||
|
||||
const BUILTIN_ADMIN = 'admin';
|
||||
|
||||
const SUBSYSTEMS = [
|
||||
{ key: 'firewall', label: 'Firewall' },
|
||||
{ key: 'network', label: 'Network' },
|
||||
@@ -157,11 +159,16 @@ function openEditPermissionsModal(user) {
|
||||
for (const sub of SUBSYSTEMS) {
|
||||
const level = document.getElementById('edit-perm-' + sub.key).value;
|
||||
if (level && level !== '—') {
|
||||
perms[sub.key] = level;
|
||||
perms[sub.key] = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res = await apiFetch('/api/auth/users/' + encodeURIComponent(user.username), {
|
||||
if (user.username === BUILTIN_ADMIN) {
|
||||
toast('Cannot modify permissions for builtin admin', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await apiFetch('/api/auth/users/' + encodeURIComponent(user.username), {
|
||||
method: 'POST',
|
||||
body: { permissions: perms },
|
||||
});
|
||||
@@ -215,7 +222,8 @@ function UsersPage() {
|
||||
<td class="text-sm">${u.credCount || 0}</td>
|
||||
<td class="text-sm text-muted">${permBadges.length ? permBadges.join(' ') : '—'}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline" onClick=${() => openEditPermissionsModal(u)}>Edit</button>
|
||||
${u.username === BUILTIN_ADMIN ? html`<span class="text-muted text-sm">(builtin)</span>` :
|
||||
html`<button class="btn btn-sm btn-outline" onClick=${() => openEditPermissionsModal(u)}>Edit</button>`}
|
||||
${isMe ? html`<span class="text-muted text-sm">(you)</span>` :
|
||||
html`<${ConfirmDelete}
|
||||
url=${'/api/auth/users/' + encodeURIComponent(u.username)}
|
||||
|
||||
Reference in New Issue
Block a user