mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 13:52:15 +01:00
fix: keep stored secrets when an older UI sends them empty
Firmware and UI files update separately, so an old index.js can run against firmware that no longer returns secrets. It sends "" for the field it can't fill in, which cleared the WiFi passphrase (reboot into AP mode) or the MQTT password. The firmware now decides: "" keeps the stored secret, unless the same request changes the SSID or MQTT username, then it clears it (open network). /setNetwork compares the settings after applying them, so a kept passphrase no longer triggers a reboot. The UI cache-buster moves to 3.0.14d. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1fb4c4cfcd
commit
9383ebec2e
6 changed files with 77 additions and 34 deletions
|
|
@ -312,8 +312,11 @@ bool MQTTSettings::fromJSON(JsonObject &obj) {
|
|||
if(!obj["pubDisco"].isNull()) this->pubDisco = obj["pubDisco"];
|
||||
this->parseValueString(obj, "protocol", this->protocol, sizeof(this->protocol));
|
||||
this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname));
|
||||
// An empty password clears the stored one only alongside a new username.
|
||||
const char *username = obj["username"].as<const char *>();
|
||||
bool newUsername = username && strcmp(username, this->username) != 0;
|
||||
this->parseValueString(obj, "username", this->username, sizeof(this->username));
|
||||
applySecret(obj["password"].as<const char *>(), this->password, sizeof(this->password));
|
||||
applySecret(obj["password"].as<const char *>(), this->password, sizeof(this->password), newUsername);
|
||||
this->parseValueString(obj, "rootTopic", this->rootTopic, sizeof(this->rootTopic));
|
||||
this->parseValueString(obj, "discoTopic", this->discoTopic, sizeof(this->discoTopic));
|
||||
if(!obj["port"].isNull()) this->port = obj["port"];
|
||||
|
|
@ -526,8 +529,11 @@ bool WifiSettings::begin() {
|
|||
return true;
|
||||
}
|
||||
bool WifiSettings::fromJSON(JsonObject &obj) {
|
||||
// An empty passphrase clears the stored one only alongside a new SSID (an open network).
|
||||
const char *ssid = obj["ssid"].as<const char *>();
|
||||
bool newSsid = ssid && strcmp(ssid, this->ssid) != 0;
|
||||
this->parseValueString(obj, "ssid", this->ssid, sizeof(this->ssid));
|
||||
applySecret(obj["passphrase"].as<const char *>(), this->passphrase, sizeof(this->passphrase));
|
||||
applySecret(obj["passphrase"].as<const char *>(), this->passphrase, sizeof(this->passphrase), newSsid);
|
||||
if(!obj["roaming"].isNull()) this->roaming = obj["roaming"];
|
||||
if(!obj["hidden"].isNull()) this->hidden = obj["hidden"];
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -9,12 +9,17 @@ inline bool hasSecret(const char *s) { return s && s[0] != '\0'; }
|
|||
|
||||
inline const char *maskSecret(const char *s) { return hasSecret(s) ? "<set>" : "<empty>"; }
|
||||
|
||||
// Updates dest from an incoming value, truncating to size. A null incoming
|
||||
// value means the client left the field out, so the stored secret is kept.
|
||||
// Pass obj["key"].as<const char *>(), which is null when the key is absent.
|
||||
// Updates dest from an incoming value, truncating to size. Pass
|
||||
// obj["key"].as<const char *>(), which is null when the key is absent.
|
||||
// absent -> keep the stored secret.
|
||||
// "" -> keep it too, unless identityChanged (a new SSID or username in
|
||||
// the same request), then clear it. UIs older than write-only
|
||||
// secrets send "" for a field the device no longer fills in.
|
||||
// value -> replace it.
|
||||
// Returns true when dest changed.
|
||||
inline bool applySecret(const char *incoming, char *dest, size_t size) {
|
||||
inline bool applySecret(const char *incoming, char *dest, size_t size, bool identityChanged = false) {
|
||||
if(!incoming || size == 0) return false;
|
||||
if(incoming[0] == '\0' && !identityChanged) return false;
|
||||
size_t len = strlen(incoming);
|
||||
if(len >= size) len = size - 1;
|
||||
if(strncmp(dest, incoming, len) == 0 && dest[len] == '\0') return false;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "ConfigSettings.h"
|
||||
#include "ConfigFile.h"
|
||||
#include "Utils.h"
|
||||
#include "Secrets.h"
|
||||
#include "SSDP.h"
|
||||
#include "Somfy.h"
|
||||
#include "WResp.h"
|
||||
|
|
@ -781,16 +782,13 @@ void registerWebUiRoutes(AsyncWebServer &server) {
|
|||
}
|
||||
if(!obj["wifi"].isNull()) {
|
||||
JsonObject objWifi = obj["wifi"];
|
||||
if(settings.connType == conn_types_t::wifi) {
|
||||
if(!objWifi["ssid"].isNull() && objWifi["ssid"].as<String>().compareTo(settings.WIFI.ssid) != 0) {
|
||||
if(WiFi.softAPgetStationNum() == 0) reboot = true;
|
||||
}
|
||||
if(!objWifi["passphrase"].isNull() && objWifi["passphrase"].as<String>().compareTo(settings.WIFI.passphrase) != 0) {
|
||||
if(WiFi.softAPgetStationNum() == 0) reboot = true;
|
||||
}
|
||||
}
|
||||
// Compared after fromJSON, which decides whether an empty passphrase keeps the stored one.
|
||||
WifiSettings before = settings.WIFI;
|
||||
settings.WIFI.fromJSON(objWifi);
|
||||
settings.WIFI.save();
|
||||
if(settings.connType == conn_types_t::wifi && WiFi.softAPgetStationNum() == 0 &&
|
||||
(strcmp(before.ssid, settings.WIFI.ssid) != 0 || strcmp(before.passphrase, settings.WIFI.passphrase) != 0))
|
||||
reboot = true;
|
||||
}
|
||||
if(!obj["ethernet"].isNull()) {
|
||||
JsonObject objEth = obj["ethernet"];
|
||||
|
|
@ -833,19 +831,19 @@ void registerWebUiRoutes(AsyncWebServer &server) {
|
|||
JsonObject obj = json.as<JsonObject>();
|
||||
ESP_LOGD(TAG, "Settings WIFI connection...");
|
||||
String ssid = "";
|
||||
// Clients never receive the stored passphrase, so an omitted one keeps it.
|
||||
String passphrase = settings.WIFI.passphrase;
|
||||
if(!obj["ssid"].isNull()) ssid = obj["ssid"].as<String>();
|
||||
if(!obj["passphrase"].isNull()) passphrase = obj["passphrase"].as<String>();
|
||||
bool reboot = false;
|
||||
if(ssid.compareTo(settings.WIFI.ssid) != 0) reboot = true;
|
||||
if(passphrase.compareTo(settings.WIFI.passphrase) != 0) reboot = true;
|
||||
bool newSsid = ssid.compareTo(settings.WIFI.ssid) != 0;
|
||||
// Clients never receive the stored passphrase: an omitted or empty one keeps it unless the SSID changes.
|
||||
char passphrase[sizeof(settings.WIFI.passphrase)];
|
||||
SETCHARPROP(passphrase, settings.WIFI.passphrase, sizeof(passphrase));
|
||||
bool newPassphrase = applySecret(obj["passphrase"].as<const char *>(), passphrase, sizeof(passphrase), newSsid);
|
||||
bool reboot = newSsid || newPassphrase;
|
||||
if(!settings.WIFI.ssidExists(ssid.c_str()) && ssid.length() > 0) {
|
||||
request->send(400, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"WiFi Network Does not exist\"}");
|
||||
}
|
||||
else {
|
||||
SETCHARPROP(settings.WIFI.ssid, ssid.c_str(), sizeof(settings.WIFI.ssid));
|
||||
SETCHARPROP(settings.WIFI.passphrase, passphrase.c_str(), sizeof(settings.WIFI.passphrase));
|
||||
SETCHARPROP(settings.WIFI.passphrase, passphrase, sizeof(settings.WIFI.passphrase));
|
||||
settings.WIFI.save();
|
||||
settings.WIFI.print();
|
||||
request->send(201, _encoding_json, "{\"status\":\"OK\",\"desc\":\"Successfully set server connection\"}");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue