diff --git a/ESPTimeCast_ESP32/ESPTimeCast_ESP32.ino b/ESPTimeCast_ESP32/ESPTimeCast_ESP32.ino
index e659a49..82829f8 100644
--- a/ESPTimeCast_ESP32/ESPTimeCast_ESP32.ino
+++ b/ESPTimeCast_ESP32/ESPTimeCast_ESP32.ino
@@ -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);
diff --git a/ESPTimeCast_ESP32/data/index.html b/ESPTimeCast_ESP32/data/index.html
index 0b2977b..d25eca5 100644
--- a/ESPTimeCast_ESP32/data/index.html
+++ b/ESPTimeCast_ESP32/data/index.html
@@ -585,7 +585,7 @@ textarea::placeholder {
+ oninput="dimmingBrightnessValue.textContent = (this.value == -1 ? 'Off' : this.value);">