mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
fix: stop sending stored passwords and PIN back to clients
The settings routes returned the WiFi passphrase, MQTT password and the login password and PIN in plain text, and the debug log printed them and the API key. Responses now carry hasPassphrase/hasPassword/hasPin flags, logs mask the values, and an omitted secret keeps the stored one. The UI loads secret fields empty and treats a blank field as "keep" while the SSID or username is unchanged. Clearing a secret without changing the SSID or username is listed in docs/KNOWN_ISSUES.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e963a5adb3
commit
1fb4c4cfcd
8 changed files with 189 additions and 29 deletions
|
|
@ -5,6 +5,7 @@
|
|||
#include <Preferences.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "Utils.h"
|
||||
#include "Secrets.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
|
|
@ -301,7 +302,7 @@ bool MQTTSettings::toJSON(JsonObject &obj) {
|
|||
obj["hostname"] = this->hostname;
|
||||
obj["port"] = this->port;
|
||||
obj["username"] = this->username;
|
||||
obj["password"] = this->password;
|
||||
obj["hasPassword"] = hasSecret(this->password);
|
||||
obj["rootTopic"] = this->rootTopic;
|
||||
obj["discoTopic"] = this->discoTopic;
|
||||
return true;
|
||||
|
|
@ -312,7 +313,7 @@ bool MQTTSettings::fromJSON(JsonObject &obj) {
|
|||
this->parseValueString(obj, "protocol", this->protocol, sizeof(this->protocol));
|
||||
this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname));
|
||||
this->parseValueString(obj, "username", this->username, sizeof(this->username));
|
||||
this->parseValueString(obj, "password", this->password, sizeof(this->password));
|
||||
applySecret(obj["password"].as<const char *>(), this->password, sizeof(this->password));
|
||||
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"];
|
||||
|
|
@ -478,16 +479,16 @@ bool SecuritySettings::begin() {
|
|||
bool SecuritySettings::fromJSON(JsonObject &obj) {
|
||||
if(!obj["type"].isNull()) this->type = static_cast<security_types>(obj["type"].as<uint8_t>());
|
||||
this->parseValueString(obj, "username", this->username, sizeof(this->username));
|
||||
this->parseValueString(obj, "password", this->password, sizeof(this->password));
|
||||
this->parseValueString(obj, "pin", this->pin, sizeof(this->pin));
|
||||
applySecret(obj["password"].as<const char *>(), this->password, sizeof(this->password));
|
||||
applySecret(obj["pin"].as<const char *>(), this->pin, sizeof(this->pin));
|
||||
if(!obj["permissions"].isNull()) this->permissions = obj["permissions"];
|
||||
return true;
|
||||
}
|
||||
bool SecuritySettings::toJSON(JsonObject &obj) {
|
||||
obj["type"] = static_cast<uint8_t>(this->type);
|
||||
obj["username"] = this->username;
|
||||
obj["password"] = this->password;
|
||||
obj["pin"] = this->pin;
|
||||
obj["hasPassword"] = hasSecret(this->password);
|
||||
obj["hasPin"] = hasSecret(this->pin);
|
||||
obj["permissions"] = this->permissions;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -516,7 +517,7 @@ bool SecuritySettings::load() {
|
|||
}
|
||||
void SecuritySettings::print() {
|
||||
ESP_LOGD(TAG, "SECURITY Type:%u Username:[%s] Password:[%s] Pin:[%s] Permissions:%u",
|
||||
static_cast<uint8_t>(this->type), this->username, this->password, this->pin, this->permissions);
|
||||
static_cast<uint8_t>(this->type), this->username, maskSecret(this->password), maskSecret(this->pin), this->permissions);
|
||||
}
|
||||
|
||||
WifiSettings::WifiSettings() {}
|
||||
|
|
@ -526,14 +527,14 @@ bool WifiSettings::begin() {
|
|||
}
|
||||
bool WifiSettings::fromJSON(JsonObject &obj) {
|
||||
this->parseValueString(obj, "ssid", this->ssid, sizeof(this->ssid));
|
||||
this->parseValueString(obj, "passphrase", this->passphrase, sizeof(this->passphrase));
|
||||
applySecret(obj["passphrase"].as<const char *>(), this->passphrase, sizeof(this->passphrase));
|
||||
if(!obj["roaming"].isNull()) this->roaming = obj["roaming"];
|
||||
if(!obj["hidden"].isNull()) this->hidden = obj["hidden"];
|
||||
return true;
|
||||
}
|
||||
bool WifiSettings::toJSON(JsonObject &obj) {
|
||||
obj["ssid"] = this->ssid;
|
||||
obj["passphrase"] = this->passphrase;
|
||||
obj["hasPassphrase"] = hasSecret(this->passphrase);
|
||||
obj["roaming"] = this->roaming;
|
||||
obj["hidden"] = this->hidden;
|
||||
return true;
|
||||
|
|
@ -579,7 +580,7 @@ String WifiSettings::mapEncryptionType(int type) {
|
|||
return "Unknown";
|
||||
}
|
||||
void WifiSettings::print() {
|
||||
ESP_LOGD(TAG, "WIFI Settings SSID: [%s] PassPhrase: [%s]", this->ssid, this->passphrase);
|
||||
ESP_LOGD(TAG, "WIFI Settings SSID: [%s] PassPhrase: [%s]", this->ssid, maskSecret(this->passphrase));
|
||||
}
|
||||
void WifiSettings::printNetworks() {
|
||||
int n = WiFi.scanNetworks(false, false);
|
||||
|
|
|
|||
25
src/Secrets.h
Normal file
25
src/Secrets.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef secrets_h
|
||||
#define secrets_h
|
||||
#include <string.h>
|
||||
|
||||
// Stored secrets (WiFi passphrase, MQTT password, login password/PIN) are
|
||||
// write-only: responses carry hasSecret() flags and logs carry maskSecret().
|
||||
|
||||
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.
|
||||
// Returns true when dest changed.
|
||||
inline bool applySecret(const char *incoming, char *dest, size_t size) {
|
||||
if(!incoming || size == 0) return false;
|
||||
size_t len = strlen(incoming);
|
||||
if(len >= size) len = size - 1;
|
||||
if(strncmp(dest, incoming, len) == 0 && dest[len] == '\0') return false;
|
||||
memcpy(dest, incoming, len);
|
||||
dest[len] = '\0';
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -125,7 +125,6 @@ bool Web::createAPIToken(const char *payload, char *token) {
|
|||
sprintf(str, "%02x", (int)hmacResult[i]);
|
||||
strcat(token, str);
|
||||
}
|
||||
ESP_LOGD(TAG, "Hash: %s", token);
|
||||
return true;
|
||||
}
|
||||
bool Web::createAPIToken(const IPAddress ipAddress, char *token) {
|
||||
|
|
|
|||
|
|
@ -621,7 +621,6 @@ void registerWebUiRoutes(AsyncWebServer &server) {
|
|||
resp.beginObject();
|
||||
resp.beginObject("connected");
|
||||
resp.addElem("name", settings.WIFI.ssid);
|
||||
resp.addElem("passphrase", settings.WIFI.passphrase);
|
||||
resp.addElem("strength", (int32_t)WiFi.RSSI());
|
||||
resp.addElem("channel", (int32_t)WiFi.channel());
|
||||
resp.endObject();
|
||||
|
|
@ -834,7 +833,8 @@ void registerWebUiRoutes(AsyncWebServer &server) {
|
|||
JsonObject obj = json.as<JsonObject>();
|
||||
ESP_LOGD(TAG, "Settings WIFI connection...");
|
||||
String ssid = "";
|
||||
String passphrase = "";
|
||||
// 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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue