fix: harden retry JSON parsing and exempt personal auth routes
webui/static/hoover/api.js
Guard retryRes.json() with .catch(() => null) so non-JSON
responses (e.g. nginx 502/503) don't throw and lose the
actual status code. Falls back to 'HTTP <status>' error string.
lib/db_sqlite.py
Replace unsafe sql.split(';') loop with conn.executescript()
which properly handles semicolons inside string literals.
webui/server.py
Add _AUTH_PERSONAL set and _is_personal_auth() so personal
auth operations (session, password, logout, webauthn creds)
skip subsystem permission checks. Users with only firewall:read
can now manage their own credentials without needing auth:rw.
This commit is contained in:
+22
-2
@@ -125,6 +125,18 @@ _AUTH_EXEMPT = {
|
||||
("POST", "/api/auth/webauthn/authenticate-finish"),
|
||||
}
|
||||
|
||||
# ── Personal auth routes (operates on own account, no subsystem permission needed) ──
|
||||
# These routes require a valid JWT but do NOT require an "auth" permission entry.
|
||||
# A user with only "firewall:read" can still view session, change password, logout, etc.
|
||||
_AUTH_PERSONAL = {
|
||||
("GET", "/api/auth/session"),
|
||||
("POST", "/api/auth/password"),
|
||||
("POST", "/api/auth/logout"),
|
||||
("GET", "/api/auth/webauthn/credentials"),
|
||||
}
|
||||
|
||||
# Pattern: DELETE /api/auth/webauthn/creds/<id> — match prefix only
|
||||
|
||||
|
||||
def _subsystem_from_path(path: str) -> str | None:
|
||||
"""Extract subsystem name from API path."""
|
||||
@@ -136,6 +148,14 @@ def _subsystem_from_path(path: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _is_personal_auth(method: str, path: str) -> bool:
|
||||
"""Check if route is a personal auth operation (no subsystem permission needed)."""
|
||||
if (method, path) in _AUTH_PERSONAL:
|
||||
return True
|
||||
# Personal credential deletion: DELETE /api/auth/webauthn/creds/<id>
|
||||
return method == "DELETE" and path.startswith("/api/auth/webauthn/creds/")
|
||||
|
||||
|
||||
def _has_permission(perms: dict, subsystem: str, method: str) -> bool:
|
||||
"""Check if user has permission for subsystem + method."""
|
||||
level = perms.get(subsystem)
|
||||
@@ -188,9 +208,9 @@ def _auth_middleware():
|
||||
|
||||
user_permissions = payload.get("permissions", {})
|
||||
|
||||
# Check subsystem permissions
|
||||
# Check subsystem permissions (skip personal auth routes)
|
||||
subsystem = _subsystem_from_path(path)
|
||||
if subsystem:
|
||||
if subsystem and not _is_personal_auth(method, path):
|
||||
if subsystem not in user_permissions:
|
||||
return jsonify({"ok": False, "error": "forbidden"}), 403
|
||||
if not _has_permission(user_permissions, subsystem, method):
|
||||
|
||||
Reference in New Issue
Block a user