mirror of
https://github.com/mfactory-osaka/ESPTimeCast.git
synced 2026-02-19 11:54:56 -05:00
Safer SSID and Password handling
Safer SSID and Password handling
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 || "";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 || "";
|
||||
|
||||
Reference in New Issue
Block a user