Add HTTPS support and a management-token gate for the web UI
Build and push server image / build-and-push (push) Successful in 31s

ESP32 side can now reach the tools server over HTTPS: the Tools Server
field accepts an https:// address for a TLS-terminating reverse proxy
in front of the server (which still only ever speaks plain HTTP
itself), trusting Cloudflare's Origin CA root (embedded at build time)
since that's the common way to get a real cert on a private origin.
Every URL the device builds -- image fetch, config check, manage-menu
data, the QR codes' own links -- goes through one build_url() helper
that picks the scheme from what's configured.

Also adds an optional MANAGEMENT_TOKEN (docker-compose.yml) that gates
the web UI (/, /api/*) behind a shared secret -- unset by default, so
existing trusted-LAN deployments are unaffected. The same token is
entered once during the ESP32's captive-portal setup and gets baked
into the manage-menu's QR code (?token=...), so scanning it just works;
visiting the page without a valid token shows a plain entry prompt
instead of the config UI, and a valid query-param hit sets a cookie so
the page's own fetch()/<img> calls stay authorized for the rest of the
visit. Device-facing /frame/* endpoints are unaffected -- a separate,
already-documented trust boundary.
This commit is contained in:
2026-07-19 09:42:38 -04:00
parent 461beed04f
commit 6c7468a36e
12 changed files with 253 additions and 30 deletions
+17 -1
View File
@@ -72,6 +72,16 @@ esp_err_t frame_config_load(frame_config_t *out)
return pass_err;
}
/* Also optional -- most deployments won't set a server-side
* MANAGEMENT_TOKEN at all, in which case this stays empty and the
* manage-menu QR just links to the page with no ?token=. */
len = sizeof(out->access_token);
esp_err_t token_err = nvs_get_str(handle, "access_token", out->access_token, &len);
if (token_err != ESP_OK && token_err != ESP_ERR_NVS_NOT_FOUND) {
nvs_close(handle);
return token_err;
}
nvs_close(handle);
return ESP_OK;
}
@@ -91,6 +101,9 @@ esp_err_t frame_config_save(const frame_config_t *cfg)
if (err == ESP_OK) {
err = nvs_set_str(handle, "toolsserver", cfg->toolsserver);
}
if (err == ESP_OK) {
err = nvs_set_str(handle, "access_token", cfg->access_token);
}
if (err == ESP_OK) {
/* Fresh (re)provisioning -- the next successful connection should
* show the status screen again. */
@@ -139,6 +152,7 @@ void frame_config_clear(void)
nvs_erase_key(handle, "sta_ssid");
nvs_erase_key(handle, "sta_pass");
nvs_erase_key(handle, "toolsserver");
nvs_erase_key(handle, "access_token");
nvs_erase_key(handle, "connected_once");
nvs_commit(handle);
nvs_close(handle);
@@ -311,6 +325,7 @@ static esp_err_t save_config_post_handler(httpd_req_t *req)
extract_form_value(body, "ssid", cfg.sta_ssid, sizeof(cfg.sta_ssid));
extract_form_value(body, "password", cfg.sta_password, sizeof(cfg.sta_password));
extract_form_value(body, "toolsserver", cfg.toolsserver, sizeof(cfg.toolsserver));
extract_form_value(body, "access_token", cfg.access_token, sizeof(cfg.access_token));
if (strlen(cfg.sta_ssid) == 0 || strlen(cfg.toolsserver) == 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "SSID and Tools Server are required");
@@ -324,7 +339,8 @@ static esp_err_t save_config_post_handler(httpd_req_t *req)
return ESP_FAIL;
}
ESP_LOGI(TAG, "Saved config: ssid='%s' toolsserver='%s'", cfg.sta_ssid, cfg.toolsserver);
ESP_LOGI(TAG, "Saved config: ssid='%s' toolsserver='%s' access_token=%s", cfg.sta_ssid, cfg.toolsserver,
strlen(cfg.access_token) ? "set" : "none");
static const char resp[] =
"<html><body><h3>Saved. Restarting and connecting to your WiFi...</h3></body></html>";