diff --git a/README.md b/README.md index db67387a..7bb876ab 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ Users report that [ESP_Flasher](https://github.com/Jason2866/ESP_Flasher/release ## First configuration * After the initial flashing of the microcontroller, an Access Point called "OpenDTU-*" is opened. The default password is "openDTU42". * Use a web browser to open the address [http://192.168.4.1](http://192.168.4.1) -* Navigate to Settings --> Network Settings and enter your WiFi credentials +* Navigate to Settings --> Network Settings and enter your WiFi credentials. The username to access the config menu is "admin" and the password the same as for accessing the Access Point (default: "openDTU42"). * OpenDTU then simultaneously connects to your WiFi AP with this credentials. Navigate to Info --> Network and look into section "Network Interface (Station)" for the IP address received via DHCP. * When OpenDTU is connected to a configured WiFI AP, the "OpenDTU-*" Access Point is closed after 3 minutes. * OpenDTU needs access to a working NTP server to get the current date & time. Both are sent to the inverter with each request. Default NTP server is pool.ntp.org. If your network has different requirements please change accordingly (Settings --> NTP Settings). diff --git a/include/WebApi.h b/include/WebApi.h index cf7805a7..699b9cd1 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -24,6 +24,8 @@ public: void init(); void loop(); + static bool checkCredentials(AsyncWebServerRequest* request); + private: AsyncWebServer _server; AsyncEventSource _events; diff --git a/include/WebApi_security.h b/include/WebApi_security.h index d94a7eeb..ea9dd75c 100644 --- a/include/WebApi_security.h +++ b/include/WebApi_security.h @@ -12,5 +12,7 @@ private: void onPasswordGet(AsyncWebServerRequest* request); void onPasswordPost(AsyncWebServerRequest* request); + void onAuthenticateGet(AsyncWebServerRequest* request); + AsyncWebServer* _server; }; \ No newline at end of file diff --git a/include/defaults.h b/include/defaults.h index 298a87ff..9656901d 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -9,6 +9,7 @@ #define ACCESS_POINT_NAME "OpenDTU-" #define ACCESS_POINT_PASSWORD "openDTU42" +#define AUTH_USERNAME "admin" #define ADMIN_TIMEOUT 180 #define WIFI_RECONNECT_TIMEOUT 15 diff --git a/src/WebApi.cpp b/src/WebApi.cpp index be8203db..f6a6355d 100644 --- a/src/WebApi.cpp +++ b/src/WebApi.cpp @@ -5,6 +5,7 @@ #include "WebApi.h" #include "ArduinoJson.h" #include "AsyncJson.h" +#include "Configuration.h" #include "defaults.h" WebApiClass::WebApiClass() @@ -55,4 +56,22 @@ void WebApiClass::loop() _webApiWsLive.loop(); } +bool WebApiClass::checkCredentials(AsyncWebServerRequest* request) +{ + CONFIG_T& config = Configuration.get(); + if (request->authenticate(AUTH_USERNAME, config.Security_Password)) { + return true; + } + + AsyncWebServerResponse* r = request->beginResponse(401); + + // WebAPI should set the X-Requested-With to prevent browser internal auth dialogs + if (!request->hasHeader("X-Requested-With")) { + r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\"")); + } + request->send(r); + + return false; +} + WebApiClass WebApi; \ No newline at end of file diff --git a/src/WebApi_security.cpp b/src/WebApi_security.cpp index 2009be96..96802fbc 100644 --- a/src/WebApi_security.cpp +++ b/src/WebApi_security.cpp @@ -6,6 +6,7 @@ #include "ArduinoJson.h" #include "AsyncJson.h" #include "Configuration.h" +#include "WebApi.h" #include "helper.h" void WebApiSecurityClass::init(AsyncWebServer* server) @@ -16,6 +17,7 @@ void WebApiSecurityClass::init(AsyncWebServer* server) _server->on("/api/security/password", HTTP_GET, std::bind(&WebApiSecurityClass::onPasswordGet, this, _1)); _server->on("/api/security/password", HTTP_POST, std::bind(&WebApiSecurityClass::onPasswordPost, this, _1)); + _server->on("/api/security/authenticate", HTTP_GET, std::bind(&WebApiSecurityClass::onAuthenticateGet, this, _1)); } void WebApiSecurityClass::loop() @@ -24,6 +26,10 @@ void WebApiSecurityClass::loop() void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request) { + if (!WebApi.checkCredentials(request)) { + return; + } + AsyncJsonResponse* response = new AsyncJsonResponse(); JsonObject root = response->getRoot(); const CONFIG_T& config = Configuration.get(); @@ -36,6 +42,10 @@ void WebApiSecurityClass::onPasswordGet(AsyncWebServerRequest* request) void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request) { + if (!WebApi.checkCredentials(request)) { + return; + } + AsyncJsonResponse* response = new AsyncJsonResponse(); JsonObject retMsg = response->getRoot(); retMsg[F("type")] = F("warning"); @@ -87,6 +97,21 @@ void WebApiSecurityClass::onPasswordPost(AsyncWebServerRequest* request) retMsg[F("type")] = F("success"); retMsg[F("message")] = F("Settings saved!"); + response->setLength(); + request->send(response); +} + +void WebApiSecurityClass::onAuthenticateGet(AsyncWebServerRequest* request) +{ + if (!WebApi.checkCredentials(request)) { + return; + } + + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject retMsg = response->getRoot(); + retMsg[F("type")] = F("success"); + retMsg[F("message")] = F("Authentication successfull!"); + response->setLength(); request->send(response); } \ No newline at end of file diff --git a/webapp/package.json b/webapp/package.json index 4cc42ebb..b57fad83 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -14,6 +14,7 @@ "@popperjs/core": "^2.11.6", "bootstrap": "^5.2.2", "bootstrap-icons-vue": "^1.8.1", + "mitt": "^3.0.0", "spark-md5": "^3.0.2", "vue": "^3.2.41", "vue-router": "^4.1.6" diff --git a/webapp/src/components/NavBar.vue b/webapp/src/components/NavBar.vue index ee4fa255..e66760ad 100644 --- a/webapp/src/components/NavBar.vue +++ b/webapp/src/components/NavBar.vue @@ -73,19 +73,41 @@ + \ No newline at end of file diff --git a/webapp/src/views/SecurityAdminView.vue b/webapp/src/views/SecurityAdminView.vue index f6878172..d557bd8b 100644 --- a/webapp/src/views/SecurityAdminView.vue +++ b/webapp/src/views/SecurityAdminView.vue @@ -26,8 +26,8 @@ @@ -41,6 +41,7 @@ import { defineComponent } from 'vue'; import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; +import { handleResponse, authHeader } from '@/utils/authentication'; import type { SecurityConfig } from '@/types/SecurityConfig'; export default defineComponent({ @@ -65,8 +66,8 @@ export default defineComponent({ methods: { getPasswordConfig() { this.dataLoading = true; - fetch("/api/security/password") - .then((response) => response.json()) + fetch("/api/security/password", { headers: authHeader() }) + .then(handleResponse) .then( (data) => { this.securityConfigList = data; @@ -90,15 +91,10 @@ export default defineComponent({ fetch("/api/security/password", { method: "POST", + headers: authHeader(), body: formData, }) - .then(function (response) { - if (response.status != 200) { - throw response.status; - } else { - return response.json(); - } - }) + .then(handleResponse) .then( (response) => { this.alertMessage = response.message; diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 78e37b1b..f6ef1d2a 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -1474,6 +1474,11 @@ minimatch@^5.1.0: dependencies: brace-expansion "^2.0.1" +mitt@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd" + integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"