Authentication added

This commit is contained in:
2026-06-23 23:39:43 +02:00
parent aed0d1a13e
commit a3c7d9aa28
+57 -4
View File
@@ -12,6 +12,7 @@
#define PWM_FREQ 5000 #define PWM_FREQ 5000
#define PWM_RES 10 #define PWM_RES 10
#define MQTT_MANUFACTURER "Baumann Enkataleiptics" #define MQTT_MANUFACTURER "Baumann Enkataleiptics"
#define AUTH_USERNAME "admin"
struct MeterConfig { struct MeterConfig {
int pin; int pin;
@@ -32,10 +33,16 @@ struct MqttConfig {
char prefix[32] = "m1730"; char prefix[32] = "m1730";
}; };
struct AuthConfig {
bool enabled = false;
char pass[32] = "";
};
static char hostname[32] = HOSTNAME_DEFAULT; static char hostname[32] = HOSTNAME_DEFAULT;
static int meterCount = 0; static int meterCount = 0;
static MeterConfig meters[MAX_METERS] = {}; static MeterConfig meters[MAX_METERS] = {};
static MqttConfig mqttCfg; static MqttConfig mqttCfg;
static AuthConfig authCfg;
static WiFiManagerParameter hostnameParam("hostname", "Device hostname", hostname, 32); static WiFiManagerParameter hostnameParam("hostname", "Device hostname", hostname, 32);
static WebServer server(80); static WebServer server(80);
static WiFiClient wifiClient; static WiFiClient wifiClient;
@@ -70,6 +77,12 @@ static void loadConfig() {
strlcpy(mqttCfg.prefix, mq["prefix"] | "m1730", sizeof(mqttCfg.prefix)); 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<JsonArray>(); JsonArray arr = doc["meters"].as<JsonArray>();
meterCount = min((int)arr.size(), MAX_METERS); meterCount = min((int)arr.size(), MAX_METERS);
for (int i = 0; i < meterCount; i++) { for (int i = 0; i < meterCount; i++) {
@@ -97,6 +110,10 @@ static void saveConfig() {
mq["pass"] = mqttCfg.pass; mq["pass"] = mqttCfg.pass;
mq["prefix"] = mqttCfg.prefix; mq["prefix"] = mqttCfg.prefix;
JsonObject auth = doc["auth"].to<JsonObject>();
auth["en"] = authCfg.enabled;
auth["pass"] = authCfg.pass;
JsonArray arr = doc["meters"].to<JsonArray>(); JsonArray arr = doc["meters"].to<JsonArray>();
for (int i = 0; i < meterCount; i++) { for (int i = 0; i < meterCount; i++) {
JsonObject m = arr.add<JsonObject>(); JsonObject m = arr.add<JsonObject>();
@@ -346,11 +363,19 @@ static String escHtml(const String& s) {
return out; 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 // Web handlers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
static void handleRoot() { static void handleRoot() {
if (!requireAuth()) { Serial.println("[HTTP] GET / -> 401"); return; }
Serial.println("[HTTP] GET /"); Serial.println("[HTTP] GET /");
String meterRows; String meterRows;
for (int i = 0; i < meterCount; i++) { for (int i = 0; i < meterCount; i++) {
@@ -385,6 +410,21 @@ static void handleRoot() {
countOpts += ">" + String(i) + "</option>"; countOpts += ">" + String(i) + "</option>";
} }
String authSection;
{
String checked = authCfg.enabled ? " checked" : "";
const char* passHint = strlen(authCfg.pass) > 0 ? "set &#8212; leave blank to keep" : "enter password";
authSection +=
"<fieldset><legend>Security</legend>"
"<div class=row>"
"<label class=tgl-lbl>Require password</label>"
"<label class=switch><input type=checkbox name=auth_en" + checked + "><span class=slid></span></label>"
"</div>"
"<div class=row><label>Password</label><input name=auth_pass type=password placeholder='" + String(passHint) + "'></div>";
authSection += "</fieldset>";
}
String mqttSection; String mqttSection;
{ {
char portStr[8]; char portStr[8];
@@ -437,10 +477,10 @@ static void handleRoot() {
html += ".count-row label{margin-bottom:4px}"; 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{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 += ".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 += ".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:24px;transition:.3s}"; 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:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;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(20px)}"; html += ".switch input:checked+.slid{background:#4361ee}.switch input:checked+.slid::before{transform:translateX(16px)}";
html += "</style></head><body><div class=card>"; html += "</style></head><body><div class=card>";
html += "<h1>M1730</h1><div class=sub>Configuration</div><div class=info>"; html += "<h1>M1730</h1><div class=sub>Configuration</div><div class=info>";
html += "<div><span>Hostname</span><span>" + String(hostname) + "</span></div>"; html += "<div><span>Hostname</span><span>" + String(hostname) + "</span></div>";
@@ -450,6 +490,7 @@ static void handleRoot() {
html += "<div class=count-row><div><label>Meters</label>"; html += "<div class=count-row><div><label>Meters</label>";
html += "<select name=meter_count onchange='this.form.submit()'>" + countOpts + "</select></div></div>"; html += "<select name=meter_count onchange='this.form.submit()'>" + countOpts + "</select></div></div>";
html += meterRows; html += meterRows;
html += authSection;
html += mqttSection; html += mqttSection;
html += "<button class=btn type=submit>Save</button></form>"; html += "<button class=btn type=submit>Save</button></form>";
html += "<div class=mac>" + WiFi.macAddress() + "</div></div>"; html += "<div class=mac>" + WiFi.macAddress() + "</div></div>";
@@ -468,6 +509,7 @@ static void handleRoot() {
} }
static void handleConfig() { static void handleConfig() {
if (!requireAuth()) { Serial.println("[HTTP] POST /config -> 401"); return; }
Serial.println("[HTTP] POST /config"); Serial.println("[HTTP] POST /config");
if (server.hasArg("hostname")) { if (server.hasArg("hostname")) {
String val = server.arg("hostname"); String val = server.arg("hostname");
@@ -495,6 +537,16 @@ static void handleConfig() {
Serial.println("[HTTP] mqtt disabled"); 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; int newCount = meterCount;
if (server.hasArg("meter_count")) if (server.hasArg("meter_count"))
newCount = constrain(server.arg("meter_count").toInt(), 1, MAX_METERS); newCount = constrain(server.arg("meter_count").toInt(), 1, MAX_METERS);
@@ -536,6 +588,7 @@ static void handleConfig() {
} }
static void handleSet() { 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; } if (!server.hasArg("i") || !server.hasArg("v")) { server.send(400); Serial.println("[HTTP] GET /set missing args"); return; }
int idx = server.arg("i").toInt(); int idx = server.arg("i").toInt();
float val = server.arg("v").toFloat(); float val = server.arg("v").toFloat();