Conversation
When web_viewer_password is set, Dashboard, Contacts, and Mesh Graph are readable without logging in, along with the read-only APIs they need. Radio, Config, Logs, every mutating API, /api/channels, and the live Socket.IO streams require an admin session, since those can expose channel keys and decrypted traffic. The Realtime page still renders publicly but shows "Admin login required" instead of connecting. Anything not on the public allowlist fails closed. Login now compares HMAC digests with hmac.compare_digest and clears the session before setting authenticated_admin. Logout clears the whole session and returns to the dashboard. Co-authored-by: Gerard Hickey <hickey@kinetic-compute.com>
…dmin buttons contacts.html declared its own top-level const IS_ADMIN, but base.html already declares one in an earlier script on the same page. Classic scripts share one global lexical scope, so the redeclaration threw a SyntaxError and the whole Contacts script never ran, leaving the table on its loading placeholder in every mode. Contacts now uses the global from base.html. The zombie-recover and clear-offline banner buttons were still rendered for anonymous viewers even though their /api/admin/* endpoints now return 401. They are gated on the same auth_enabled/is_admin check as the other admin controls.
…ts, allow HEAD on public paths Logging out cleared the HTTP session but left that login's Socket.IO connections open, and a socket keeps the session it connected with, so a /realtime tab kept streaming packets, decrypted messages, and logs after logout. Each login now gets an admin_login_id, sockets record it on connect, and logout disconnects the sockets opened under that login. Other admin logins stay connected. The login next check passed ///host and /\host because urlparse sees no netloc in either, but browsers resolve both as another origin. Next targets are now rejected if they start with //, contain a backslash, or contain a tab or newline. This check predates the admin split. Public pages and GET APIs only accepted literal GET, so HEAD (and OPTIONS) on them returned 401 and made uptime checks report the viewer as down. They now accept HEAD and OPTIONS too; admin paths are unchanged.
…t can parse base.html
The frontend lint job failed with "Parsing error: Unexpected token {" because base.html's inline script assigned IS_ADMIN from a raw Jinja expression, which isn't valid JS until rendered. The flag now lives in data-is-admin on <body> and the script reads it from document.body.dataset. I also dropped the unused AUTH_ENABLED constant and declared IS_ADMIN as a readonly ESLint global, since contacts.html reads it from base.html.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This supersedes #289. It keeps @hickey's admin privilege level, reworked on current dev, and he's credited as co-author on the main commit.
What changes when
web_viewer_passwordis set/api/decode-path,/api/mesh/resolve-path)./api/channels, and the live Socket.IO streams require an admin session, since they can expose channel keys, decrypted traffic, and operator logs. The Realtime page still renders publicly but shows "Admin login required" instead of connecting./api/stream_datais exempt from the session check because the bot authenticates it withX-Stream-Token.hmac.compare_digestand clears the session before settingauthenticated_admin. Logout clears the whole session and returns to the dashboard.With no password configured, nothing changes: every page and API stays open as before.
Fixes on top of the #289 rework
const IS_ADMINafter base.html had already declared it. Classic scripts share one global scope, so that threw a SyntaxError and the Contacts page never loaded, in every mode. It now uses the global from base.html.nextcheck let///hostand/\hostthrough (no netloc perurlparse, but browsers treat both as another origin), so a crafted login link could redirect off-site after sign-in. This predates the admin split. Targets starting with//or containing a backslash, tab, or newline now fall back to/.Testing
pytest tests/test_web_viewer.py: 359 passed, including new tests that fail on each bug above.node --test tests/js/*.test.mjs: 6 passed.