From bcce0071f1d7442063684bb5cf4aeeda350a27b9 Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Mon, 20 Apr 2026 08:59:24 +1000 Subject: [PATCH] feat: open full web CLI access and add panel redirect flow --- docs/custom-cli.md | 90 ++------------------------- docs/web-panel.md | 12 ++-- examples/simple_repeater/MyMesh.cpp | 96 ----------------------------- src/helpers/web/WebPanelServer.cpp | 60 ++++++++++++++---- src/helpers/web/WebPanelServer.h | 3 + 5 files changed, 63 insertions(+), 198 deletions(-) diff --git a/docs/custom-cli.md b/docs/custom-cli.md index 56cd394c..8a744438 100644 --- a/docs/custom-cli.md +++ b/docs/custom-cli.md @@ -82,93 +82,15 @@ Legacy dotted aliases are also accepted: - `get battery.reporting`: shows whether board battery reporting is enabled. Support is board-dependent. - `set battery.reporting on|off`: enables or disables battery voltage reporting on supported boards. This is currently useful for Heltec V3 boards where USB-only power can produce misleading battery readings. If your board needs this too, open an issue and support can be added board-by-board. -## Web Panel Allowlisted Commands +## Web Panel CLI Access -When the repeater web panel is enabled, it only allows a limited command set. +When the repeater web panel is enabled and you are authenticated, the browser CLI panel can run the same CLI commands accepted by the repeater. -That allowlist currently includes: +Notes: -- `clock` -- `region default` -- `region default ` -- `region default ` -- `get mqtt.status` -- `get web` -- `get web.status` -- `get web.stats.status` -- `advert` -- `reboot` -- `start ota` -- `memory` -- `stats-core` -- `stats-radio` -- `stats-packets` -- `get wifi.status` -- `get wifi.powersaving` -- `set wifi.ssid ` -- `set wifi.pwd ` -- `set wifi.powersaving on|off` -- `get mqtt.iata` -- `set mqtt.iata ` -- `get mqtt.owner` -- `set mqtt.owner <64-hex-char-public-key>` -- `get mqtt.email` -- `set mqtt.email ` -- `get mqtt.packets` -- `set mqtt.packets on|off` -- `get mqtt.raw` -- `set mqtt.raw on|off` -- `get mqtt.statuscfg` -- `set mqtt.status on|off` -- `get mqtt.tx` -- `set mqtt.tx on|off` -- `get mqtt.eastmesh-au` -- `set mqtt.eastmesh-au on|off` -- `get mqtt.eastmesh.au` -- `set mqtt.eastmesh.au on|off` -- `get mqtt.letsmesh-eu` -- `set mqtt.letsmesh-eu on|off` -- `get mqtt.letsmesh.eu` -- `set mqtt.letsmesh.eu on|off` -- `get mqtt.letsmesh-us` -- `set mqtt.letsmesh-us on|off` -- `get mqtt.letsmesh.us` -- `set mqtt.letsmesh.us on|off` -- `set web on|off` -- `set.web on|off` -- `set web.stats on|off` -- `set.web.stats on|off` -- `get name` -- `set name ` -- `get lat` -- `set lat ` -- `get lon` -- `set lon ` -- `get radio` -- `set radio ` -- `get prv.key` -- `get guest.password` -- `password ` -- `set guest.password ` -- `set prv.key <64-hex-char-private-key>` -- `get role` -- `get public.key` -- `get advert.interval` -- `set advert.interval ` -- `get agc.reset.interval` -- `set agc.reset.interval ` -- `get flood.advert.interval` -- `set flood.advert.interval ` -- `get repeat` -- `set repeat on|off` -- `get flood.max` -- `set flood.max ` -- `get path.hash.mode` -- `set path.hash.mode ` -- `get owner.info` -- `set owner.info ` -- `time ` -- `time.force ` +- the panel still uses the repeater admin password for access +- commands run with the same care as if you typed them into the repeater CLI directly +- this is intended for local admin use on a trusted network ## Companion WiFi Rescue Commands diff --git a/docs/web-panel.md b/docs/web-panel.md index 77f5b4ed..50d452c5 100644 --- a/docs/web-panel.md +++ b/docs/web-panel.md @@ -13,7 +13,7 @@ It gives you: - a password-gated local admin page at `/app` - a dedicated stats and trends page at `/stats` - quick `get` commands for common repeater and MQTT checks -- a terminal-style CLI panel for allowlisted commands +- a terminal-style CLI panel for full repeater CLI access - editable repeater settings - editable MQTT settings - a historical stats view with trends, neighbours, and recent events @@ -71,7 +71,7 @@ Example: - the panel uses the same admin password as the repeater CLI - the connection is HTTPS, but the certificate is self-signed - browsers will warn the first time you connect -- the panel only exposes an allowlisted subset of CLI commands +- the panel exposes the repeater CLI after login This is intended for local admin use on a trusted network, not for open internet exposure. @@ -134,13 +134,13 @@ These are useful for quick checks without typing into the CLI field. ## Run CLI Command -This is a small terminal for allowlisted commands. +This is a small terminal for the repeater CLI. - press `Enter` to run the command - command history is shown in the terminal box below - save buttons elsewhere in the page also show the generated command and the reply here - `clock` is available here if you want to check the repeater's current board time -- the full current allowlist is documented in `docs/custom-cli.md` under `Web Panel Allowlisted Commands` +- authenticated sessions can run the same CLI commands accepted by the repeater This makes it easy to see exactly what the panel sent to the repeater. @@ -310,9 +310,9 @@ Check: For fixed installations where MQTT uptime matters more than browser access, use the panel briefly and then disable it again. -### A command says it is not allowlisted +### HTTP opens instead of HTTPS -The panel intentionally limits what can be run from the browser. Use the serial CLI for commands outside the web allowlist. `clock` is included, but most maintenance and debug commands are still serial-only. +The repeater now redirects plain `http://` requests to the local `https://` panel URL. If the browser still shows a connection problem after redirecting, open `https:///` directly and accept the self-signed certificate warning first. ### Stats or settings do not refresh diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c536f70d..b937a084 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1992,102 +1992,6 @@ void MyMesh::runWebCommand(const char* command, char* reply, size_t reply_size) return; } - auto matches_exact = [command](const char* candidate) -> bool { - return strcmp(command, candidate) == 0; - }; - auto matches_prefix = [command](const char* candidate) -> bool { - size_t len = strlen(candidate); - return strncmp(command, candidate, len) == 0; - }; - - bool allowed = - matches_exact("clock") || - matches_exact("region default") || - matches_exact("get mqtt.status") || - matches_exact("get web.status") || - matches_exact("get web.stats.status") || - matches_exact("get web") || - matches_exact("advert") || - matches_exact("reboot") || - matches_exact("start ota") || - matches_exact("get wifi.status") || - matches_exact("get wifi.powersaving") || - matches_exact("stats-core") || - matches_exact("stats-radio") || - matches_exact("stats-packets") || - matches_exact("memory") || - matches_exact("get mqtt.iata") || - matches_exact("get mqtt.owner") || - matches_exact("get mqtt.email") || - matches_exact("get mqtt.packets") || - matches_exact("get mqtt.raw") || - matches_exact("get mqtt.statuscfg") || - matches_exact("get mqtt.tx") || - matches_exact("get mqtt.eastmesh-au") || - matches_exact("get mqtt.eastmesh.au") || - matches_exact("get mqtt.letsmesh-eu") || - matches_exact("get mqtt.letsmesh.eu") || - matches_exact("get mqtt.letsmesh-us") || - matches_exact("get mqtt.letsmesh.us") || - matches_exact("get name") || - matches_exact("get lat") || - matches_exact("get lon") || - matches_exact("get radio") || - matches_exact("get prv.key") || - matches_exact("get role") || - matches_exact("get public.key") || - matches_exact("get advert.interval") || - matches_exact("get agc.reset.interval") || - matches_exact("get flood.advert.interval") || - matches_exact("get repeat") || - matches_exact("get flood.max") || - matches_exact("get path.hash.mode") || - matches_exact("get owner.info") || - matches_exact("get guest.password") || - matches_prefix("set wifi.ssid ") || - matches_prefix("set wifi.pwd ") || - matches_prefix("set wifi.powersaving ") || - matches_prefix("set mqtt.iata ") || - matches_prefix("set mqtt.owner ") || - matches_prefix("set mqtt.email ") || - matches_prefix("set mqtt.packets ") || - matches_prefix("set mqtt.raw ") || - matches_prefix("set mqtt.status ") || - matches_prefix("set mqtt.tx ") || - matches_prefix("set web ") || - matches_prefix("set.web ") || - matches_prefix("set web.stats ") || - matches_prefix("set.web.stats ") || - matches_prefix("set mqtt.eastmesh-au ") || - matches_prefix("set mqtt.eastmesh.au ") || - matches_prefix("set mqtt.letsmesh-eu ") || - matches_prefix("set mqtt.letsmesh.eu ") || - matches_prefix("set mqtt.letsmesh-us ") || - matches_prefix("set mqtt.letsmesh.us ") || - matches_prefix("set name ") || - matches_prefix("set lat ") || - matches_prefix("set lon ") || - matches_prefix("set radio ") || - matches_prefix("password ") || - matches_prefix("set guest.password ") || - matches_prefix("set prv.key ") || - matches_prefix("set advert.interval ") || - matches_prefix("set agc.reset.interval ") || - matches_prefix("set flood.advert.interval ") || - matches_prefix("set repeat ") || - matches_prefix("set flood.max ") || - matches_prefix("set path.hash.mode ") || - matches_prefix("region default ") || - matches_prefix("time ") || - matches_prefix("time.force ") || - matches_prefix("set owner.info "); - - if (!allowed) { - strncpy(reply, "Err - command not allowlisted for web access", reply_size - 1); - reply[reply_size - 1] = 0; - return; - } - char command_buf[192]; StrHelper::strncpy(command_buf, command, sizeof(command_buf)); handleCommand(0, command_buf, reply); diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index 9ee20e4e..56c7aaab 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -256,10 +256,10 @@ const char kWebPanelLoginHtml[] PROGMEM = R"HTML(