diff --git a/README.md b/README.md index 2c02abfa..6951dce4 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ This project is **not affiliated with Xteink**; it's built as a community projec - [ ] Sleep wallpaper picker - [x] Adjustable sleep timer - [x] Set default folder +- [x] Custom credentials for Hotspot and FTP/HTTP access See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint. diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 02415eff..5ba05946 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -12,7 +12,7 @@ CrossPointSettings CrossPointSettings::instance; namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 22; +constexpr uint8_t SETTINGS_COUNT = 28; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -49,6 +49,12 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, scheduleNetworkMode); serialization::writePod(outputFile, scheduleHour); serialization::writePod(outputFile, scheduleAutoShutdown); + serialization::writeString(outputFile, ftpUsername); + serialization::writeString(outputFile, ftpPassword); + serialization::writeString(outputFile, httpUsername); + serialization::writeString(outputFile, httpPassword); + serialization::writeString(outputFile, apSsid); + serialization::writeString(outputFile, apPassword); outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -119,6 +125,18 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, scheduleAutoShutdown); if (++settingsRead >= fileSettingsCount) break; + serialization::readString(inputFile, ftpUsername); + if (++settingsRead >= fileSettingsCount) break; + serialization::readString(inputFile, ftpPassword); + if (++settingsRead >= fileSettingsCount) break; + serialization::readString(inputFile, httpUsername); + if (++settingsRead >= fileSettingsCount) break; + serialization::readString(inputFile, httpPassword); + if (++settingsRead >= fileSettingsCount) break; + serialization::readString(inputFile, apSsid); + if (++settingsRead >= fileSettingsCount) break; + serialization::readString(inputFile, apPassword); + if (++settingsRead >= fileSettingsCount) break; } while (false); inputFile.close(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 1b910eae..16bc82ae 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -88,6 +88,14 @@ class CrossPointSettings { // Custom default folder path (used when defaultFolder == FOLDER_CUSTOM) std::string customDefaultFolder = "/books"; + // Network credentials + std::string ftpUsername = "crosspoint"; + std::string ftpPassword = "reader"; + std::string httpUsername = "crosspoint"; + std::string httpPassword = "reader"; + std::string apSsid = "CrossPoint-Reader"; + std::string apPassword = ""; // Empty = open network + ~CrossPointSettings() = default; // Get singleton instance diff --git a/src/activities/network/FileTransferActivity.cpp b/src/activities/network/FileTransferActivity.cpp index b5f534a7..b04defca 100644 --- a/src/activities/network/FileTransferActivity.cpp +++ b/src/activities/network/FileTransferActivity.cpp @@ -18,8 +18,6 @@ namespace { // AP Mode configuration -constexpr const char* AP_SSID = "CrossPoint-Reader"; -constexpr const char* AP_PASSWORD = nullptr; // Open network for ease of use constexpr const char* AP_HOSTNAME = "crosspoint"; constexpr uint8_t AP_CHANNEL = 1; constexpr uint8_t AP_MAX_CONNECTIONS = 4; @@ -219,11 +217,11 @@ void FileTransferActivity::startAccessPoint() { // Start soft AP bool apStarted; - if (AP_PASSWORD && strlen(AP_PASSWORD) >= 8) { - apStarted = WiFi.softAP(AP_SSID, AP_PASSWORD, AP_CHANNEL, false, AP_MAX_CONNECTIONS); + if (!SETTINGS.apPassword.empty() && SETTINGS.apPassword.length() >= 8) { + apStarted = WiFi.softAP(SETTINGS.apSsid.c_str(), SETTINGS.apPassword.c_str(), AP_CHANNEL, false, AP_MAX_CONNECTIONS); } else { - // Open network (no password) - apStarted = WiFi.softAP(AP_SSID, nullptr, AP_CHANNEL, false, AP_MAX_CONNECTIONS); + // Open network (no password or password too short) + apStarted = WiFi.softAP(SETTINGS.apSsid.c_str(), nullptr, AP_CHANNEL, false, AP_MAX_CONNECTIONS); } if (!apStarted) { @@ -239,10 +237,10 @@ void FileTransferActivity::startAccessPoint() { char ipStr[16]; snprintf(ipStr, sizeof(ipStr), "%d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]); connectedIP = ipStr; - connectedSSID = AP_SSID; + connectedSSID = SETTINGS.apSsid; Serial.printf("[%lu] [WEBACT] Access Point started!\n", millis()); - Serial.printf("[%lu] [WEBACT] SSID: %s\n", millis(), AP_SSID); + Serial.printf("[%lu] [WEBACT] SSID: %s\n", millis(), SETTINGS.apSsid.c_str()); Serial.printf("[%lu] [WEBACT] IP: %s\n", millis(), connectedIP.c_str()); // Start mDNS for hostname resolution @@ -492,6 +490,13 @@ void FileTransferActivity::renderHttpServerRunning() const { // Show QR code for URL renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "or scan QR code with your phone:"); drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl); + + // Show HTTP credentials at the bottom + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 14, "Browser will ask for credentials:"); + std::string httpUserStr = "Username: " + SETTINGS.httpUsername; + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 15, httpUserStr.c_str()); + std::string httpPassStr = "Password: " + SETTINGS.httpPassword; + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 16, httpPassStr.c_str()); } else { // STA mode display (original behavior) const int startY = 65; @@ -518,6 +523,13 @@ void FileTransferActivity::renderHttpServerRunning() const { // Show QR code for URL drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo); renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:"); + + // Show HTTP credentials + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 12, "Browser will ask for credentials:"); + std::string httpUserStr = "Username: " + SETTINGS.httpUsername; + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 13, httpUserStr.c_str()); + std::string httpPassStr = "Password: " + SETTINGS.httpPassword; + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 14, httpPassStr.c_str()); } const auto labels = mappedInput.mapLabels("« Exit", "", "", ""); @@ -542,7 +554,10 @@ void FileTransferActivity::renderFtpServerRunning() const { renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, "or scan QR code with your phone to connect to WiFi."); // Show QR code for WiFi - std::string wifiConfig = std::string("WIFI:T:WPA;S:") + connectedSSID + ";P:" + "" + ";;"; + std::string wifiConfig = std::string("WIFI:T:") + + (SETTINGS.apPassword.empty() ? "" : "WPA") + + ";S:" + connectedSSID + + ";P:" + SETTINGS.apPassword + ";;"; drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig); startY += 6 * 29 + 3 * LINE_SPACING; @@ -554,8 +569,10 @@ void FileTransferActivity::renderFtpServerRunning() const { renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 4, ftpInfo.c_str(), true, BOLD); renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Connect with FTP client:"); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "Username: crosspoint"); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 7, "Password: reader"); + std::string ftpUserStr = "Username: " + SETTINGS.ftpUsername; + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, ftpUserStr.c_str()); + std::string ftpPassStr = "Password: " + SETTINGS.ftpPassword; + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 7, ftpPassStr.c_str()); } else { // STA mode display const int startY = 65; @@ -576,8 +593,10 @@ void FileTransferActivity::renderFtpServerRunning() const { renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, ftpInfo.c_str(), true, BOLD); renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Use FTP client to connect:"); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Username: crosspoint"); - renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "Password: reader"); + std::string ftpUserStr = "Username: " + SETTINGS.ftpUsername; + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, ftpUserStr.c_str()); + std::string ftpPassStr = "Password: " + SETTINGS.ftpPassword; + renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, ftpPassStr.c_str()); // Show QR code for FTP URL drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 8, ftpInfo); diff --git a/src/activities/settings/CredentialSettingsActivity.cpp b/src/activities/settings/CredentialSettingsActivity.cpp new file mode 100644 index 00000000..65f9c400 --- /dev/null +++ b/src/activities/settings/CredentialSettingsActivity.cpp @@ -0,0 +1,198 @@ +#include "CredentialSettingsActivity.h" + +#include + +#include "CrossPointSettings.h" +#include "MappedInputManager.h" +#include "activities/util/KeyboardEntryActivity.h" +#include "fontIds.h" + +namespace { +constexpr int FIELD_COUNT = 6; +const char* FIELD_NAMES[FIELD_COUNT] = { + "FTP Username", + "FTP Password", + "HTTP Username", + "HTTP Password", + "Hotspot SSID", + "Hotspot Password" +}; +} // namespace + +void CredentialSettingsActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void CredentialSettingsActivity::onEnter() { + Activity::onEnter(); + + renderingMutex = xSemaphoreCreateMutex(); + selectedIndex = 0; + updateRequired = true; + + xTaskCreate(&CredentialSettingsActivity::taskTrampoline, "CredentialSettingsTask", + 2048, // Stack size + this, // Parameters + 1, // Priority + &displayTaskHandle // Task handle + ); +} + +void CredentialSettingsActivity::onExit() { + ActivityWithSubactivity::onExit(); + + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void CredentialSettingsActivity::loop() { + if (subActivity) { + subActivity->loop(); + return; + } + + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { + SETTINGS.saveToFile(); + onGoBack(); + return; + } + + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { + selectCurrentField(); + return; + } + + // Handle navigation + if (mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left)) { + selectedIndex = (selectedIndex > 0) ? (selectedIndex - 1) : (FIELD_COUNT - 1); + updateRequired = true; + } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right)) { + selectedIndex = (selectedIndex + 1) % FIELD_COUNT; + updateRequired = true; + } +} + +void CredentialSettingsActivity::selectCurrentField() { + std::string* targetField = nullptr; + const char* promptText = ""; + + switch (selectedIndex) { + case 0: // FTP Username + targetField = &SETTINGS.ftpUsername; + promptText = "Enter FTP username:"; + break; + case 1: // FTP Password + targetField = &SETTINGS.ftpPassword; + promptText = "Enter FTP password:"; + break; + case 2: // HTTP Username + targetField = &SETTINGS.httpUsername; + promptText = "Enter HTTP username:"; + break; + case 3: // HTTP Password + targetField = &SETTINGS.httpPassword; + promptText = "Enter HTTP password:"; + break; + case 4: // Hotspot SSID + targetField = &SETTINGS.apSsid; + promptText = "Enter hotspot SSID:"; + break; + case 5: // Hotspot Password + targetField = &SETTINGS.apPassword; + promptText = "Enter hotspot password (leave empty for open network):"; + break; + } + + if (targetField) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + bool isPassword = (selectedIndex == 1 || selectedIndex == 3 || selectedIndex == 5); // Password fields + enterNewActivity(new KeyboardEntryActivity( + renderer, mappedInput, + promptText, // title + *targetField, // initialText + 10, // startY + 0, // maxLength (0 = unlimited) + isPassword, // isPassword + [this, targetField](const std::string& newValue) { + *targetField = newValue; + SETTINGS.saveToFile(); + exitActivity(); + updateRequired = true; + }, + [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + } +} + +void CredentialSettingsActivity::displayTaskLoop() { + while (true) { + if (updateRequired && !subActivity) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + render(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void CredentialSettingsActivity::render() const { + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + const auto pageHeight = renderer.getScreenHeight(); + + // Draw header + renderer.drawCenteredText(UI_12_FONT_ID, 15, "Network Credentials", true, BOLD); + renderer.drawCenteredText(SMALL_FONT_ID, 40, "Configure server and hotspot credentials"); + + // Draw selection + renderer.fillRect(0, 70 + selectedIndex * 35 - 2, pageWidth - 1, 35); + + // Draw fields + for (int i = 0; i < FIELD_COUNT; i++) { + const int fieldY = 70 + i * 35; + const bool isSelected = (i == selectedIndex); + + // Draw field name + renderer.drawText(UI_10_FONT_ID, 20, fieldY, FIELD_NAMES[i], !isSelected); + + // Draw current value (masked for passwords) + std::string displayValue; + switch (i) { + case 0: // FTP Username + displayValue = SETTINGS.ftpUsername; + break; + case 1: // FTP Password + displayValue = SETTINGS.ftpPassword.empty() ? "" : std::string(SETTINGS.ftpPassword.length(), '*'); + break; + case 2: // Hotspot SSID + displayValue = SETTINGS.apSsid; + break; + case 3: // Hotspot Password + displayValue = SETTINGS.apPassword.empty() ? "(open)" : std::string(SETTINGS.apPassword.length(), '*'); + break; + } + + const auto width = renderer.getTextWidth(UI_10_FONT_ID, displayValue.c_str()); + renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, fieldY, displayValue.c_str(), !isSelected); + } + + // Draw help text + const auto labels = mappedInput.mapLabels("« Save", "Edit", "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(); +} diff --git a/src/activities/settings/CredentialSettingsActivity.h b/src/activities/settings/CredentialSettingsActivity.h new file mode 100644 index 00000000..344aca75 --- /dev/null +++ b/src/activities/settings/CredentialSettingsActivity.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include + +#include + +#include "activities/ActivityWithSubactivity.h" + +/** + * CredentialSettingsActivity allows users to configure credentials for: + * - FTP server (username and password) + * - HTTP server (username and password) + * - WiFi Hotspot (SSID and password) + */ +class CredentialSettingsActivity final : public ActivityWithSubactivity { + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + bool updateRequired = false; + int selectedIndex = 0; // Currently selected credential field + const std::function onGoBack; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void render() const; + void selectCurrentField(); + + public: + explicit CredentialSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& onGoBack) + : ActivityWithSubactivity("CredentialSettings", renderer, mappedInput), onGoBack(onGoBack) {} + void onEnter() override; + void onExit() override; + void loop() override; +}; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index ad89ecda..56bb329d 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -3,6 +3,7 @@ #include #include "CrossPointSettings.h" +#include "CredentialSettingsActivity.h" #include "FolderPickerActivity.h" #include "MappedInputManager.h" #include "OtaUpdateActivity.h" @@ -11,7 +12,7 @@ // Define the static settings list namespace { -constexpr int settingsCount = 18; +constexpr int settingsCount = 19; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}}, @@ -51,6 +52,7 @@ const SettingInfo settingsList[settingsCount] = { {"Root", "Custom", "Last Used"}}, {"Choose Custom Folder", SettingType::ACTION, nullptr, {}}, {"Bluetooth", SettingType::TOGGLE, &CrossPointSettings::bluetoothEnabled, {}}, + {"Network Credentials", SettingType::ACTION, nullptr, {}}, {"File Transfer Schedule", SettingType::ACTION, nullptr, {}}, {"Check for updates", SettingType::ACTION, nullptr, {}}, }; @@ -169,6 +171,14 @@ void SettingsActivity::toggleCurrentSetting() { }, "/")); // Start from root directory xSemaphoreGive(renderingMutex); + } else if (std::string(setting.name) == "Network Credentials") { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new CredentialSettingsActivity(renderer, mappedInput, [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); } else if (std::string(setting.name) == "File Transfer Schedule") { xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); diff --git a/src/network/CrossPointFtpServer.cpp b/src/network/CrossPointFtpServer.cpp index 75f0527d..5c6a3c0b 100644 --- a/src/network/CrossPointFtpServer.cpp +++ b/src/network/CrossPointFtpServer.cpp @@ -2,11 +2,7 @@ #include -namespace { -// FTP server credentials -constexpr const char* FTP_USERNAME = "crosspoint"; -constexpr const char* FTP_PASSWORD = "reader"; -} // namespace +#include "CrossPointSettings.h" CrossPointFtpServer::CrossPointFtpServer() {} @@ -52,15 +48,15 @@ void CrossPointFtpServer::begin() { } // Initialize FTP server with credentials - ftpServer->begin(FTP_USERNAME, FTP_PASSWORD); + ftpServer->begin(SETTINGS.ftpUsername.c_str(), SETTINGS.ftpPassword.c_str()); running = true; Serial.printf("[%lu] [FTP] FTP server started on port 21\n", millis()); // Show the correct IP based on network mode const String ipAddr = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString(); Serial.printf("[%lu] [FTP] Access at ftp://%s/\n", millis(), ipAddr.c_str()); - Serial.printf("[%lu] [FTP] Username: %s\n", millis(), FTP_USERNAME); - Serial.printf("[%lu] [FTP] Password: %s\n", millis(), FTP_PASSWORD); + Serial.printf("[%lu] [FTP] Username: %s\n", millis(), SETTINGS.ftpUsername.c_str()); + Serial.printf("[%lu] [FTP] Password: %s\n", millis(), SETTINGS.ftpPassword.c_str()); Serial.printf("[%lu] [FTP] [MEM] Free heap after server.begin(): %d bytes\n", millis(), ESP.getFreeHeap()); } diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 3a26a736..b1b9ae37 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -7,6 +7,7 @@ #include +#include "CrossPointSettings.h" #include "html/FilesPageHtml.generated.h" #include "html/HomePageHtml.generated.h" @@ -150,7 +151,17 @@ void CrossPointWebServer::handleClient() const { server->handleClient(); } +bool CrossPointWebServer::authenticate() const { + if (!server->authenticate(SETTINGS.httpUsername.c_str(), SETTINGS.httpPassword.c_str())) { + server->requestAuthentication(); + Serial.printf("[%lu] [WEB] Authentication failed\n", millis()); + return false; + } + return true; +} + void CrossPointWebServer::handleRoot() const { + if (!authenticate()) return; server->send(200, "text/html", HomePageHtml); Serial.printf("[%lu] [WEB] Served root page\n", millis()); } @@ -162,6 +173,7 @@ void CrossPointWebServer::handleNotFound() const { } void CrossPointWebServer::handleStatus() const { + if (!authenticate()) return; // Get correct IP based on AP vs STA mode const String ipAddr = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString(); @@ -241,9 +253,13 @@ bool CrossPointWebServer::isEpubFile(const String& filename) const { return lower.endsWith(".epub"); } -void CrossPointWebServer::handleFileList() const { server->send(200, "text/html", FilesPageHtml); } +void CrossPointWebServer::handleFileList() const { + if (!authenticate()) return; + server->send(200, "text/html", FilesPageHtml); +} void CrossPointWebServer::handleFileListData() const { + if (!authenticate()) return; // Get current path from query string (default to root) String currentPath = "/"; if (server->hasArg("path")) { @@ -302,6 +318,9 @@ static bool uploadSuccess = false; static String uploadError = ""; void CrossPointWebServer::handleUpload() const { + // Check authentication + if (!authenticate()) return; + static unsigned long lastWriteTime = 0; static unsigned long uploadStartTime = 0; static size_t lastLoggedSize = 0; @@ -416,6 +435,7 @@ void CrossPointWebServer::handleUpload() const { } void CrossPointWebServer::handleUploadPost() const { + if (!authenticate()) return; if (uploadSuccess) { server->send(200, "text/plain", "File uploaded successfully: " + uploadFileName); } else { @@ -425,6 +445,7 @@ void CrossPointWebServer::handleUploadPost() const { } void CrossPointWebServer::handleCreateFolder() const { + if (!authenticate()) return; // Get folder name from form data if (!server->hasArg("name")) { server->send(400, "text/plain", "Missing folder name"); @@ -475,6 +496,7 @@ void CrossPointWebServer::handleCreateFolder() const { } void CrossPointWebServer::handleDelete() const { + if (!authenticate()) return; // Get path from form data if (!server->hasArg("path")) { server->send(400, "text/plain", "Missing path"); diff --git a/src/network/CrossPointWebServer.h b/src/network/CrossPointWebServer.h index 1be07b4a..835d67d6 100644 --- a/src/network/CrossPointWebServer.h +++ b/src/network/CrossPointWebServer.h @@ -38,6 +38,9 @@ class CrossPointWebServer { bool apMode = false; // true when running in AP mode, false for STA mode uint16_t port = 80; + // Authentication helper + bool authenticate() const; + // File scanning void scanFiles(const char* path, const std::function& callback) const; String formatFileSize(size_t bytes) const;