From a3c7d9aa287e412ea3bedf6e1df5f6842af7ba1c Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Tue, 23 Jun 2026 23:39:43 +0200 Subject: [PATCH] Authentication added --- src/main.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 2fb7c0c..e58757d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #define PWM_FREQ 5000 #define PWM_RES 10 #define MQTT_MANUFACTURER "Baumann Enkataleiptics" +#define AUTH_USERNAME "admin" struct MeterConfig { int pin; @@ -32,10 +33,16 @@ struct MqttConfig { char prefix[32] = "m1730"; }; +struct AuthConfig { + bool enabled = false; + char pass[32] = ""; +}; + static char hostname[32] = HOSTNAME_DEFAULT; static int meterCount = 0; static MeterConfig meters[MAX_METERS] = {}; static MqttConfig mqttCfg; +static AuthConfig authCfg; static WiFiManagerParameter hostnameParam("hostname", "Device hostname", hostname, 32); static WebServer server(80); static WiFiClient wifiClient; @@ -70,6 +77,12 @@ static void loadConfig() { strlcpy(mqttCfg.prefix, mq["prefix"] | "m1730", sizeof(mqttCfg.prefix)); } + JsonObject auth = doc["auth"]; + if (!auth.isNull()) { + authCfg.enabled = auth["en"] | false; + strlcpy(authCfg.pass, auth["pass"] | "", sizeof(authCfg.pass)); + } + JsonArray arr = doc["meters"].as(); meterCount = min((int)arr.size(), MAX_METERS); for (int i = 0; i < meterCount; i++) { @@ -97,6 +110,10 @@ static void saveConfig() { mq["pass"] = mqttCfg.pass; mq["prefix"] = mqttCfg.prefix; + JsonObject auth = doc["auth"].to(); + auth["en"] = authCfg.enabled; + auth["pass"] = authCfg.pass; + JsonArray arr = doc["meters"].to(); for (int i = 0; i < meterCount; i++) { JsonObject m = arr.add(); @@ -346,11 +363,19 @@ static String escHtml(const String& s) { return out; } +static bool requireAuth() { + if (!authCfg.enabled) return true; + if (server.authenticate(AUTH_USERNAME, authCfg.pass)) return true; + server.requestAuthentication(); + return false; +} + // --------------------------------------------------------------------------- // Web handlers // --------------------------------------------------------------------------- static void handleRoot() { + if (!requireAuth()) { Serial.println("[HTTP] GET / -> 401"); return; } Serial.println("[HTTP] GET /"); String meterRows; for (int i = 0; i < meterCount; i++) { @@ -385,6 +410,21 @@ static void handleRoot() { countOpts += ">" + String(i) + ""; } + String authSection; + { + String checked = authCfg.enabled ? " checked" : ""; + const char* passHint = strlen(authCfg.pass) > 0 ? "set — leave blank to keep" : "enter password"; + + authSection += + "
Security" + "
" + "" + "" + "
" + "
"; + authSection += "
"; + } + String mqttSection; { char portStr[8]; @@ -437,10 +477,10 @@ static void handleRoot() { html += ".count-row label{margin-bottom:4px}"; html += ".btn{width:100%;margin-top:8px;padding:12px;background:#4361ee;color:#fff;border:none;border-radius:8px;font-size:15px;font-weight:600;cursor:pointer;transition:background .2s}"; html += ".btn:hover{background:#3651d4}.mac{font-size:12px;color:#999;text-align:center;margin-top:20px}"; - html += ".tgl-lbl{margin-bottom:0!important;line-height:28px}.switch{position:relative;display:inline-block;width:44px;height:24px;margin-left:auto;flex-shrink:0}"; - html += ".switch input{opacity:0;width:0;height:0}.slid{position:absolute;cursor:pointer;inset:0;background:#ccc;border-radius:24px;transition:.3s}"; - html += ".slid::before{content:\"\";position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.3s}"; - html += ".switch input:checked+.slid{background:#4361ee}.switch input:checked+.slid::before{transform:translateX(20px)}"; +html += ".tgl-lbl{margin-bottom:0!important;line-height:20px}.switch{position:relative;display:inline-block;width:36px;height:20px;margin-left:auto;flex-shrink:0}"; +html += ".switch input{opacity:0;width:0;height:0}.slid{position:absolute;cursor:pointer;inset:0;background:#ccc;border-radius:20px;transition:.3s}"; +html += ".slid::before{content:\"\";position:absolute;height:16px;width:16px;left:2px;bottom:2px;background:#fff;border-radius:50%;transition:.3s}"; +html += ".switch input:checked+.slid{background:#4361ee}.switch input:checked+.slid::before{transform:translateX(16px)}"; html += "
"; html += "

M1730

Configuration
"; html += "
Hostname" + String(hostname) + "
"; @@ -450,6 +490,7 @@ static void handleRoot() { html += "
"; html += "
"; html += meterRows; + html += authSection; html += mqttSection; html += ""; html += "
" + WiFi.macAddress() + "
"; @@ -468,6 +509,7 @@ static void handleRoot() { } static void handleConfig() { + if (!requireAuth()) { Serial.println("[HTTP] POST /config -> 401"); return; } Serial.println("[HTTP] POST /config"); if (server.hasArg("hostname")) { String val = server.arg("hostname"); @@ -495,6 +537,16 @@ static void handleConfig() { Serial.println("[HTTP] mqtt disabled"); } + if (server.hasArg("auth_en")) { + if (server.hasArg("auth_pass") && server.arg("auth_pass").length() > 0) + strlcpy(authCfg.pass, server.arg("auth_pass").c_str(), sizeof(authCfg.pass)); + authCfg.enabled = strlen(authCfg.pass) > 0; + Serial.printf("[HTTP] auth enabled=%d\n", authCfg.enabled); + } else { + authCfg.enabled = false; + Serial.println("[HTTP] auth disabled"); + } + int newCount = meterCount; if (server.hasArg("meter_count")) newCount = constrain(server.arg("meter_count").toInt(), 1, MAX_METERS); @@ -536,6 +588,7 @@ static void handleConfig() { } static void handleSet() { + if (!requireAuth()) { Serial.println("[HTTP] GET /set -> 401"); return; } if (!server.hasArg("i") || !server.hasArg("v")) { server.send(400); Serial.println("[HTTP] GET /set missing args"); return; } int idx = server.arg("i").toInt(); float val = server.arg("v").toFloat();