mirror of
https://github.com/lumapu/ahoy.git
synced 2025-07-24 03:37:12 +02:00
0.8.70 - 2024-02-01
* prevent sending commands to inverter which isn't active #1387 * protect commands from popup in `/live` if password is set #1199
This commit is contained in:
parent
ae799b4c21
commit
4d7362fa6b
12 changed files with 156 additions and 58 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
## 0.8.70 - 2024-02-01
|
||||
* prevent sending commands to inverter which isn't active #1387
|
||||
* protect commands from popup in `/live` if password is set #1199
|
||||
|
||||
## 0.8.69 - 2024-01-31
|
||||
* merge PR: Dynamic retries, pendular first rx chan #1394
|
||||
|
|
|
@ -97,9 +97,8 @@ void app::setup() {
|
|||
esp_task_wdt_reset();
|
||||
|
||||
mWeb.setup(this, &mSys, mConfig);
|
||||
mWeb.setProtection(strlen(mConfig->sys.adminPwd) != 0);
|
||||
|
||||
mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig);
|
||||
mProtection = Protection::getInstance(mConfig->sys.adminPwd);
|
||||
|
||||
#ifdef ENABLE_SYSLOG
|
||||
mDbgSyslog.setup(mConfig); // be sure to init after mWeb.setup (webSerial uses also debug callback)
|
||||
|
@ -182,6 +181,8 @@ void app::onNetwork(bool gotIp) {
|
|||
void app::regularTickers(void) {
|
||||
DPRINTLN(DBG_DEBUG, F("regularTickers"));
|
||||
everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc");
|
||||
everySec([this]() { mProtection->tickSecond(); }, "prot");
|
||||
|
||||
// Plugins
|
||||
#if defined(PLUGIN_DISPLAY)
|
||||
if (DISP_TYPE_T0_NONE != mConfig->plugin.display.type)
|
||||
|
|
22
src/app.h
22
src/app.h
|
@ -30,6 +30,7 @@
|
|||
#include "utils/scheduler.h"
|
||||
#include "utils/syslog.h"
|
||||
#include "web/RestApi.h"
|
||||
#include "web/Protection.h"
|
||||
#if defined(ENABLE_HISTORY)
|
||||
#include "plugins/history.h"
|
||||
#endif /*ENABLE_HISTORY*/
|
||||
|
@ -246,8 +247,24 @@ class app : public IApp, public ah::Scheduler {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool getProtection(AsyncWebServerRequest *request) {
|
||||
return mWeb.isProtected(request);
|
||||
void lock(void) override {
|
||||
mProtection->lock();
|
||||
}
|
||||
|
||||
void unlock(const char *clientIp) override {
|
||||
mProtection->unlock(clientIp);
|
||||
}
|
||||
|
||||
void resetLockTimeout(void) override {
|
||||
mProtection->resetLockTimeout();
|
||||
}
|
||||
|
||||
bool isProtected(void) const override {
|
||||
return mProtection->isProtected();
|
||||
}
|
||||
|
||||
bool isProtected(const char *clientIp) const override {
|
||||
return mProtection->isProtected(clientIp);
|
||||
}
|
||||
|
||||
bool getNrfEnabled(void) {
|
||||
|
@ -387,6 +404,7 @@ class app : public IApp, public ah::Scheduler {
|
|||
#endif /* defined(ETHERNET) */
|
||||
WebType mWeb;
|
||||
RestApiType mApi;
|
||||
Protection *mProtection;
|
||||
#ifdef ENABLE_SYSLOG
|
||||
DbgSyslog mDbgSyslog;
|
||||
#endif
|
||||
|
|
|
@ -61,7 +61,11 @@ class IApp {
|
|||
virtual uint32_t getMqttRxCnt() = 0;
|
||||
virtual uint32_t getMqttTxCnt() = 0;
|
||||
|
||||
virtual bool getProtection(AsyncWebServerRequest *request) = 0;
|
||||
virtual void lock(void) = 0;
|
||||
virtual void unlock(const char *clientIp) = 0;
|
||||
virtual void resetLockTimeout(void) = 0;
|
||||
virtual bool isProtected(void) const = 0;
|
||||
virtual bool isProtected(const char *clientIp) const = 0;
|
||||
|
||||
virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0;
|
||||
virtual uint16_t getHistoryMaxDay() = 0;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//-------------------------------------
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 8
|
||||
#define VERSION_PATCH 69
|
||||
#define VERSION_PATCH 70
|
||||
|
||||
//-------------------------------------
|
||||
typedef struct {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if defined(ESP32)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
|
||||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __SPI_PATCHER_H__
|
||||
|
|
7
src/web/Protection.cpp
Normal file
7
src/web/Protection.cpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "Protection.h"
|
||||
Protection *Protection::mInstance = nullptr;
|
93
src/web/Protection.h
Normal file
93
src/web/Protection.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 2024 Ahoy, https://github.com/lumpapu/ahoy
|
||||
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __PROTECTION_H__
|
||||
#define __PROTECTION_H__
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../config/config.h"
|
||||
#include "../utils/helper.h"
|
||||
|
||||
class Protection {
|
||||
protected:
|
||||
Protection(const char *pwd) {
|
||||
mPwd = pwd;
|
||||
mLogoutTimeout = 0;
|
||||
mLoginIp.fill(0);
|
||||
|
||||
// no password set - unlock
|
||||
if(pwd[0] == '\0')
|
||||
mProtected = false;
|
||||
}
|
||||
|
||||
public:
|
||||
Protection(Protection &other) = delete;
|
||||
void operator=(const Protection &) = delete;
|
||||
|
||||
static Protection* getInstance(const char *pwd) {
|
||||
if(nullptr == mInstance)
|
||||
mInstance = new Protection(pwd);
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
void tickSecond() {
|
||||
// auto logout
|
||||
if(0 != mLogoutTimeout) {
|
||||
if (0 == --mLogoutTimeout) {
|
||||
if(mPwd[0] == '\0')
|
||||
mProtected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void lock(void) {
|
||||
mProtected = true;
|
||||
mLoginIp.fill(0);
|
||||
}
|
||||
|
||||
void unlock(const char *clientIp) {
|
||||
mProtected = false;
|
||||
ah::ip2Arr(static_cast<uint8_t*>(mLoginIp.data()), clientIp);
|
||||
}
|
||||
|
||||
void resetLockTimeout(void) {
|
||||
mLogoutTimeout = LOGOUT_TIMEOUT;
|
||||
}
|
||||
|
||||
bool isProtected(void) const {
|
||||
return mProtected;
|
||||
}
|
||||
|
||||
bool isProtected(const char *clientIp) const {
|
||||
if(mProtected)
|
||||
return true;
|
||||
|
||||
if(mPwd[0] == '\0')
|
||||
return false;
|
||||
|
||||
uint8_t ip[4];
|
||||
ah::ip2Arr(ip, clientIp);
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
if(mLoginIp[i] != ip[i])
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
static Protection *mInstance;
|
||||
|
||||
private:
|
||||
const char *mPwd;
|
||||
bool mProtected = true;
|
||||
uint16_t mLogoutTimeout = LOGOUT_TIMEOUT;
|
||||
std::array<uint8_t, 4> mLoginIp;
|
||||
};
|
||||
|
||||
#endif /*__PROTECTION_H__*/
|
|
@ -68,7 +68,7 @@ class RestApi {
|
|||
DynamicJsonDocument json(128);
|
||||
JsonObject dummy = json.as<JsonObject>();
|
||||
if(obj[F("path")] == "ctrl")
|
||||
setCtrl(obj, dummy);
|
||||
setCtrl(obj, dummy, "api");
|
||||
else if(obj[F("path")] == "setup")
|
||||
setSetup(obj, dummy);
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ class RestApi {
|
|||
if(!err) {
|
||||
String path = request->url().substring(5);
|
||||
if(path == "ctrl")
|
||||
root[F("success")] = setCtrl(obj, root);
|
||||
root[F("success")] = setCtrl(obj, root, request->client()->remoteIP().toString().c_str());
|
||||
else if(path == "setup")
|
||||
root[F("success")] = setSetup(obj, root);
|
||||
else {
|
||||
|
@ -263,7 +263,7 @@ class RestApi {
|
|||
obj[F("modules")] = String(mApp->getVersionModules());
|
||||
obj[F("build")] = String(AUTO_GIT_HASH);
|
||||
obj[F("env")] = String(ENV_NAME);
|
||||
obj[F("menu_prot")] = mApp->getProtection(request);
|
||||
obj[F("menu_prot")] = mApp->isProtected(request->client()->remoteIP().toString().c_str());
|
||||
obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask );
|
||||
obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0);
|
||||
obj[F("cst_lnk")] = String(mConfig->plugin.customLink);
|
||||
|
@ -847,7 +847,7 @@ class RestApi {
|
|||
}
|
||||
|
||||
|
||||
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) {
|
||||
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut, const char *clientIP) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]);
|
||||
bool accepted = true;
|
||||
if(NULL == iv) {
|
||||
|
@ -856,6 +856,13 @@ class RestApi {
|
|||
}
|
||||
jsonOut[F("id")] = jsonIn[F("id")];
|
||||
|
||||
if(strncmp("api", clientIP, 3) != 0) {
|
||||
if(mApp->isProtected(clientIP)) {
|
||||
jsonOut[F("error")] = F(INV_IS_PROTECTED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(F("power") == jsonIn[F("cmd")])
|
||||
accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff);
|
||||
else if(F("restart") == jsonIn[F("cmd")])
|
||||
|
|
|
@ -36,6 +36,12 @@
|
|||
#define UNKNOWN_CMD "unknown cmd: '"
|
||||
#endif
|
||||
|
||||
#ifdef LANG_DE
|
||||
#define INV_IS_PROTECTED "nicht angemeldet, Kommando nicht möglich!"
|
||||
#else /*LANG_EN*/
|
||||
#define INV_IS_PROTECTED "not logged in, command not possible!"
|
||||
#endif
|
||||
|
||||
#ifdef LANG_DE
|
||||
#define INV_DOES_NOT_ACCEPT_LIMIT_AT_MOMENT "Leistungsbegrenzung / Ansteuerung aktuell nicht möglich"
|
||||
#else /*LANG_EN*/
|
||||
|
|
|
@ -47,9 +47,6 @@ template <class HMSYSTEM>
|
|||
class Web {
|
||||
public:
|
||||
Web(void) : mWeb(80), mEvts("/events") {
|
||||
mProtected = true;
|
||||
mLogoutTimeout = 0;
|
||||
|
||||
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||
mSerialBufFill = 0;
|
||||
mSerialAddTime = true;
|
||||
|
@ -110,16 +107,6 @@ class Web {
|
|||
}
|
||||
|
||||
void tickSecond() {
|
||||
if (0 != mLogoutTimeout) {
|
||||
mLogoutTimeout -= 1;
|
||||
if (0 == mLogoutTimeout) {
|
||||
if (strlen(mConfig->sys.adminPwd) > 0)
|
||||
mProtected = true;
|
||||
}
|
||||
|
||||
DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout));
|
||||
}
|
||||
|
||||
if (mSerialClientConnnected) {
|
||||
if (mSerialBufFill > 0) {
|
||||
mEvts.send(mSerialBuf, "serial", millis());
|
||||
|
@ -133,27 +120,6 @@ class Web {
|
|||
return &mWeb;
|
||||
}
|
||||
|
||||
void setProtection(bool protect) {
|
||||
mProtected = protect;
|
||||
}
|
||||
|
||||
bool isProtected(AsyncWebServerRequest *request) {
|
||||
bool prot;
|
||||
prot = mProtected;
|
||||
if(!prot) {
|
||||
if(strlen(mConfig->sys.adminPwd) > 0) {
|
||||
uint8_t ip[4];
|
||||
ah::ip2Arr(ip, request->client()->remoteIP().toString().c_str());
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
if(mLoginIp[i] != ip[i])
|
||||
prot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prot;
|
||||
}
|
||||
|
||||
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
if (!index) {
|
||||
Serial.printf("Update Start: %s\n", filename.c_str());
|
||||
|
@ -264,7 +230,7 @@ class Web {
|
|||
}
|
||||
|
||||
void checkProtection(AsyncWebServerRequest *request) {
|
||||
if(isProtected(request)) {
|
||||
if(mApp->isProtected(request->client()->remoteIP().toString().c_str())) {
|
||||
checkRedirect(request);
|
||||
return;
|
||||
}
|
||||
|
@ -351,8 +317,7 @@ class Web {
|
|||
|
||||
if (request->args() > 0) {
|
||||
if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) {
|
||||
mProtected = false;
|
||||
ah::ip2Arr(mLoginIp, request->client()->remoteIP().toString().c_str());
|
||||
mApp->unlock(request->client()->remoteIP().toString().c_str());
|
||||
request->redirect("/");
|
||||
}
|
||||
}
|
||||
|
@ -366,8 +331,7 @@ class Web {
|
|||
DPRINTLN(DBG_VERBOSE, F("onLogout"));
|
||||
|
||||
checkProtection(request);
|
||||
|
||||
mProtected = true;
|
||||
mApp->lock();
|
||||
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len);
|
||||
response->addHeader(F("Content-Encoding"), "gzip");
|
||||
|
@ -390,7 +354,6 @@ class Web {
|
|||
|
||||
void onCss(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("onCss"));
|
||||
mLogoutTimeout = LOGOUT_TIMEOUT;
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len);
|
||||
response->addHeader(F("Content-Encoding"), "gzip");
|
||||
if(request->hasParam("v")) {
|
||||
|
@ -424,6 +387,7 @@ class Web {
|
|||
AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len);
|
||||
response->addHeader(F("Content-Encoding"), "gzip");
|
||||
request->send(response);
|
||||
mApp->resetLockTimeout();
|
||||
}
|
||||
|
||||
void showNotFound(AsyncWebServerRequest *request) {
|
||||
|
@ -495,7 +459,7 @@ class Web {
|
|||
// protection
|
||||
if (request->arg("adminpwd") != "{PWD}") {
|
||||
request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN);
|
||||
mProtected = (strlen(mConfig->sys.adminPwd) > 0);
|
||||
mApp->lock();
|
||||
}
|
||||
mConfig->sys.protectionMask = 0x0000;
|
||||
for (uint8_t i = 0; i < 7; i++) {
|
||||
|
@ -945,9 +909,6 @@ class Web {
|
|||
#endif
|
||||
AsyncWebServer mWeb;
|
||||
AsyncEventSource mEvts;
|
||||
bool mProtected;
|
||||
uint32_t mLogoutTimeout;
|
||||
uint8_t mLoginIp[4];
|
||||
IApp *mApp;
|
||||
HMSYSTEM *mSys;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue