Safer SSID and Password handling

Safer SSID and Password handling
This commit is contained in:
M-Factory
2025-09-27 23:09:11 +09:00
parent 44167c20f5
commit 51aa139eef
4 changed files with 136 additions and 25 deletions

View File

@@ -137,6 +137,19 @@ const unsigned long descriptionDuration = 3000; // 3s for short text
static unsigned long descScrollEndTime = 0; // for post-scroll delay (re-used for scroll timing)
const unsigned long descriptionScrollPause = 300; // 300ms pause after scroll
// --- Safe WiFi credential getters ---
const char *getSafeSsid() {
return isAPMode ? "" : ssid;
}
const char *getSafePassword() {
if (strlen(password) == 0) { // No password set yet — return empty string for fresh install
return "";
} else { // Password exists — mask it in the web UI
return "********";
}
}
// Scroll flipped
textEffect_t getEffectiveScrollDirection(textEffect_t desiredDirection, bool isFlipped) {
if (isFlipped) {
@@ -329,10 +342,6 @@ void connectWiFi() {
Serial.println(WiFi.softAPIP());
isAPMode = true;
clearWiFiCredentialsInConfig();
strlcpy(ssid, "", sizeof(ssid));
strlcpy(password, "", sizeof(password));
WiFiMode_t mode = WiFi.getMode();
Serial.printf("[WIFI] WiFi mode after setting AP: %s\n",
mode == WIFI_OFF ? "OFF" : mode == WIFI_STA ? "STA ONLY"
@@ -391,10 +400,6 @@ void connectWiFi() {
dnsServer.start(DNS_PORT, "*", WiFi.softAPIP());
isAPMode = true;
clearWiFiCredentialsInConfig();
strlcpy(ssid, "", sizeof(ssid));
strlcpy(password, "", sizeof(password));
auto mode = WiFi.getMode();
Serial.printf("[WIFI] WiFi mode after STA failure and setting AP: %s\n",
mode == WIFI_OFF ? "OFF" : mode == WIFI_STA ? "STA ONLY"
@@ -589,7 +594,12 @@ void setupWebServer() {
request->send(500, "application/json", "{\"error\":\"Failed to parse config.json\"}");
return;
}
// Always sanitize before sending to browser
doc[F("ssid")] = getSafeSsid();
doc[F("password")] = getSafePassword();
doc[F("mode")] = isAPMode ? "ap" : "sta";
String response;
serializeJson(doc, response);
request->send(200, "application/json", response);
@@ -634,7 +644,16 @@ void setupWebServer() {
else if (n == "showWeatherDescription") doc[n] = (v == "true" || v == "on" || v == "1");
else if (n == "dimmingEnabled") doc[n] = (v == "true" || v == "on" || v == "1");
else if (n == "weatherUnits") doc[n] = v;
else {
else if (n == "password") {
if (v != "********" && v.length() > 0) {
doc[n] = v; // user entered a new password
} else {
Serial.println(F("[SAVE] Password unchanged."));
// do nothing, keep the one already in doc
}
} else {
doc[n] = v;
}
}
@@ -797,6 +816,22 @@ void setupWebServer() {
}
});
server.on("/clear_wifi", HTTP_POST, [](AsyncWebServerRequest *request) {
Serial.println(F("[WEBSERVER] Request: /clear_wifi"));
clearWiFiCredentialsInConfig();
DynamicJsonDocument okDoc(128);
okDoc[F("message")] = "✅ WiFi credentials cleared! Rebooting...";
String response;
serializeJson(okDoc, response);
request->send(200, "application/json", response);
request->onDisconnect([]() {
Serial.println(F("[WEBSERVER] Rebooting after clearing WiFi..."));
ESP.restart();
});
});
server.on("/ap_status", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.print(F("[WEBSERVER] Request: /ap_status. isAPMode = "));
Serial.println(isAPMode);

View File

@@ -585,7 +585,7 @@ textarea::placeholder {
<label style="margin-top: 1.75rem;" for="dimBrightness">Dimming Brightness: <span id="dimmingBrightnessValue">2</span></label>
<input style="width: 100%;" type="range" min="-1" max="15" name="dimming_brightness" id="dimBrightness" value="2"
oninput="dimmingBrightnessValue.textContent = (this.value == -1 ? 'off' : this.value);">
oninput="dimmingBrightnessValue.textContent = (this.value == -1 ? 'Off' : this.value);">
<br><br><br>
<div class="form-group">
<label style="display: flex; align-items: center; justify-content: space-between;">
@@ -642,16 +642,35 @@ document.addEventListener('DOMContentLoaded', function() {
brightnessValue.textContent = brightnessSlider.value;
});
// Show/hide password toggle
// Show/Hide Password toggle
document.addEventListener("DOMContentLoaded", function () {
const passwordInput = document.getElementById("password");
const toggleCheckbox = document.getElementById("togglePassword");
toggleCheckbox.addEventListener("change", function () {
passwordInput.type = this.checked ? "text" : "password";
if (this.checked) {
// Show password as text
passwordInput.type = "text";
// Only clear if it's the masked placeholder
if (passwordInput.value === "********") {
passwordInput.value = "";
passwordInput.placeholder = "Enter new password";
}
} else {
// Hide password as dots
passwordInput.type = "password";
// Remove placeholder only if it was set by show-password toggle
if (passwordInput.placeholder === "Enter new password") {
passwordInput.placeholder = "";
}
}
});
});
window.onbeforeunload = function () {
if (isSaving) {
return "Settings are being saved. Leaving now may interrupt the process.";
@@ -680,7 +699,7 @@ window.onload = function () {
document.getElementById('language').value = data.language || '';
// Advanced:
document.getElementById('brightnessSlider').value = typeof data.brightness !== "undefined" ? data.brightness : 10;
document.getElementById('brightnessValue').textContent = (document.getElementById('brightnessSlider').value == -1 ? 'off' : document.getElementById('brightnessSlider').value);
document.getElementById('brightnessValue').textContent = (document.getElementById('brightnessSlider').value == -1 ? 'Off' : document.getElementById('brightnessSlider').value);
document.getElementById('flipDisplay').checked = !!data.flipDisplay;
document.getElementById('ntpServer1').value = data.ntpServer1 || "";
document.getElementById('ntpServer2').value = data.ntpServer2 || "";

View File

@@ -137,6 +137,20 @@ const unsigned long descriptionDuration = 3000; // 3s for short text
static unsigned long descScrollEndTime = 0; // for post-scroll delay (re-used for scroll timing)
const unsigned long descriptionScrollPause = 300; // 300ms pause after scroll
// --- Safe WiFi credential getters ---
const char *getSafeSsid() {
return isAPMode ? "" : ssid;
}
const char *getSafePassword() {
if (strlen(password) == 0) { // No password set yet — return empty string for fresh install
return "";
} else { // Password exists — mask it in the web UI
return "********";
}
}
// Scroll flipped
textEffect_t getEffectiveScrollDirection(textEffect_t desiredDirection, bool isFlipped) {
if (isFlipped) {
@@ -330,10 +344,6 @@ void connectWiFi() {
Serial.println(WiFi.softAPIP());
isAPMode = true;
clearWiFiCredentialsInConfig();
strlcpy(ssid, "", sizeof(ssid));
strlcpy(password, "", sizeof(password));
WiFiMode_t mode = WiFi.getMode();
Serial.printf("[WIFI] WiFi mode after setting AP: %s\n",
mode == WIFI_OFF ? "OFF" : mode == WIFI_STA ? "STA ONLY"
@@ -392,10 +402,6 @@ void connectWiFi() {
dnsServer.start(DNS_PORT, "*", WiFi.softAPIP());
isAPMode = true;
clearWiFiCredentialsInConfig();
strlcpy(ssid, "", sizeof(ssid));
strlcpy(password, "", sizeof(password));
WiFiMode_t mode = WiFi.getMode();
Serial.printf("[WIFI] WiFi mode after STA failure and setting AP: %s\n",
mode == WIFI_OFF ? "OFF" : mode == WIFI_STA ? "STA ONLY"
@@ -590,7 +596,12 @@ void setupWebServer() {
request->send(500, "application/json", "{\"error\":\"Failed to parse config.json\"}");
return;
}
// Always sanitize before sending to browser
doc[F("ssid")] = getSafeSsid();
doc[F("password")] = getSafePassword();
doc[F("mode")] = isAPMode ? "ap" : "sta";
String response;
serializeJson(doc, response);
request->send(200, "application/json", response);
@@ -635,6 +646,16 @@ void setupWebServer() {
else if (n == "showWeatherDescription") doc[n] = (v == "true" || v == "on" || v == "1");
else if (n == "dimmingEnabled") doc[n] = (v == "true" || v == "on" || v == "1");
else if (n == "weatherUnits") doc[n] = v;
else if (n == "password") {
if (v != "********" && v.length() > 0) {
doc[n] = v; // user entered a new password
} else {
Serial.println(F("[SAVE] Password unchanged."));
// do nothing, keep the one already in doc
}
}
else {
doc[n] = v;
}
@@ -798,6 +819,23 @@ void setupWebServer() {
}
});
server.on("/clear_wifi", HTTP_POST, [](AsyncWebServerRequest *request) {
Serial.println(F("[WEBSERVER] Request: /clear_wifi"));
clearWiFiCredentialsInConfig();
DynamicJsonDocument okDoc(128);
okDoc[F("message")] = "✅ WiFi credentials cleared! Rebooting...";
String response;
serializeJson(okDoc, response);
request->send(200, "application/json", response);
request->onDisconnect([]() {
Serial.println(F("[WEBSERVER] Rebooting after clearing WiFi..."));
ESP.restart();
});
});
server.on("/ap_status", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.print(F("[WEBSERVER] Request: /ap_status. isAPMode = "));
Serial.println(isAPMode);

View File

@@ -585,7 +585,7 @@ textarea::placeholder {
<label style="margin-top: 1.75rem;" for="dimBrightness">Dimming Brightness: <span id="dimmingBrightnessValue">2</span></label>
<input style="width: 100%;" type="range" min="-1" max="15" name="dimming_brightness" id="dimBrightness" value="2"
oninput="dimmingBrightnessValue.textContent = (this.value == -1 ? 'off' : this.value);">
oninput="dimmingBrightnessValue.textContent = (this.value == -1 ? 'Off' : this.value);">
<br><br><br>
<div class="form-group">
<label style="display: flex; align-items: center; justify-content: space-between;">
@@ -642,16 +642,35 @@ document.addEventListener('DOMContentLoaded', function() {
brightnessValue.textContent = brightnessSlider.value;
});
// Show/hide password toggle
// Show/Hide Password toggle
document.addEventListener("DOMContentLoaded", function () {
const passwordInput = document.getElementById("password");
const toggleCheckbox = document.getElementById("togglePassword");
toggleCheckbox.addEventListener("change", function () {
passwordInput.type = this.checked ? "text" : "password";
if (this.checked) {
// Show password as text
passwordInput.type = "text";
// Only clear if it's the masked placeholder
if (passwordInput.value === "********") {
passwordInput.value = "";
passwordInput.placeholder = "Enter new password";
}
} else {
// Hide password as dots
passwordInput.type = "password";
// Remove placeholder only if it was set by show-password toggle
if (passwordInput.placeholder === "Enter new password") {
passwordInput.placeholder = "";
}
}
});
});
window.onbeforeunload = function () {
if (isSaving) {
return "Settings are being saved. Leaving now may interrupt the process.";
@@ -680,7 +699,7 @@ window.onload = function () {
document.getElementById('language').value = data.language || '';
// Advanced:
document.getElementById('brightnessSlider').value = typeof data.brightness !== "undefined" ? data.brightness : 10;
document.getElementById('brightnessValue').textContent = (document.getElementById('brightnessSlider').value == -1 ? 'off' : document.getElementById('brightnessSlider').value);
document.getElementById('brightnessValue').textContent = (document.getElementById('brightnessSlider').value == -1 ? 'Off' : document.getElementById('brightnessSlider').value);
document.getElementById('flipDisplay').checked = !!data.flipDisplay;
document.getElementById('ntpServer1').value = data.ntpServer1 || "";
document.getElementById('ntpServer2').value = data.ntpServer2 || "";