Merge pull request #612 from iranl/http-form-auth

HTTP Form auth
This commit is contained in:
iranl
2025-01-17 23:28:38 +01:00
committed by GitHub
13 changed files with 418 additions and 160 deletions

View File

@@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1]
build: [release, debug]
build: [release]
env:
BOARD: ${{ matrix.board }}
VARIANT: ${{ matrix.name || matrix.board }}
@@ -107,24 +107,16 @@ jobs:
with:
path: release
pattern: '*-release-assets'
- name: Download debug assets
uses: actions/download-artifact@v4
with:
path: debug
pattern: '*-debug-assets'
- name: Copy binaries to ota/beta
env:
Version: ${{ github.run_id }}.${{ github.run_number }}.${{ github.run_attempt }}
run: |
mkdir -p ota/beta/
mkdir -p ota/debug/beta/
mkdir -p ota/master/
mkdir -p ota/debug/master/
mkdir -p webflash/
mkdir -p resources/
mkdir -p src/
cp -vf release/*/nuki_hub_*.bin ota/beta/
cp -vf debug/*/nuki_hub_*.bin ota/debug/beta/
cp -vf master/resources/ota_manifest.py resources/ota_manifest.py
cp -vf master/src/Config.h src/Config.h
python3 resources/ota_manifest.py beta $Version
@@ -132,14 +124,12 @@ jobs:
rm -rf .github .gitignore .gitmodules
touch ota/beta/empty
touch ota/master/empty
touch ota/debug/beta/empty
touch ota/debug/master/empty
touch webflash/empty
- name: Commit binaries to binary
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Update binaries"
file_pattern: 'ota/* ota/debug/* ota/master/* ota/debug/master/* ota/beta/* ota/debug/beta/* webflash/*'
file_pattern: 'ota/* ota/master/* ota/beta/* webflash/*'
branch: binary
skip_dirty_check: true
skip_fetch: true

View File

@@ -19,7 +19,7 @@ jobs:
fail-fast: false
matrix:
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1]
build: [release, debug]
build: [release]
env:
BOARD: ${{ matrix.board }}
VARIANT: ${{ matrix.name || matrix.board }}

View File

@@ -35,7 +35,7 @@ jobs:
fail-fast: false
matrix:
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1]
build: [release, debug]
build: [release]
env:
BOARD: ${{ matrix.board }}
VARIANT: ${{ matrix.name || matrix.board }}
@@ -130,19 +130,12 @@ jobs:
with:
path: release
pattern: '*-release-assets'
- name: Download debug assets
uses: actions/download-artifact@v4
with:
path: debug
pattern: '*-debug-assets'
- name: Copy binaries to ota/master
env:
Version: ${{ github.run_id }}.${{ github.run_number }}.${{ github.run_attempt }}
run: |
mkdir -p ota/beta/
mkdir -p ota/debug/beta/
mkdir -p ota/master/
mkdir -p ota/debug/master/
mkdir -p webflash/
mkdir -p resources/
mkdir -p src/
@@ -155,14 +148,12 @@ jobs:
rm -rf .github .gitignore .gitmodules
touch ota/beta/empty
touch ota/master/empty
touch ota/debug/beta/empty
touch ota/debug/master/empty
touch webflash/empty
- name: Commit binaries to binary
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Update binaries"
file_pattern: 'ota/* ota/debug/* ota/master/* ota/debug/master/* ota/beta/* ota/debug/beta/* webflash/*'
file_pattern: 'ota/* ota/master/* ota/beta/* webflash/*'
branch: binary
skip_dirty_check: true
skip_fetch: true

View File

@@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
board: [esp32, esp32-s3, esp32-s3-oct, esp32-c3, esp32-c6, esp32-h2, esp32-solo1]
build: [release, debug]
build: [release]
env:
BOARD: ${{ matrix.board }}
VARIANT: ${{ matrix.name || matrix.board }}
@@ -109,11 +109,6 @@ jobs:
with:
path: release
pattern: '*-release-assets'
- name: Download debug assets
uses: actions/download-artifact@v4
with:
path: debug
pattern: '*-debug-assets'
- name: Build zip archives
id: zip
env:
@@ -171,14 +166,11 @@ jobs:
Version: ${{ github.run_id }}.${{ github.run_number }}.${{ github.run_attempt }}
run: |
mkdir -p ota/beta/
mkdir -p ota/debug/beta/
mkdir -p ota/master/
mkdir -p ota/debug/master/
mkdir -p webflash/
mkdir -p resources/
mkdir -p src/
cp -vf release/*/nuki_hub_*.bin ota/
cp -vf debug/*/nuki_hub_*.bin ota/debug/
cp -vf release/*/webflash_nuki_hub_*.bin webflash/
cp -vf master/resources/ota_manifest.py resources/ota_manifest.py
cp -vf master/src/Config.h src/Config.h
@@ -186,18 +178,15 @@ jobs:
python3 resources/ota_manifest.py beta none
find * -not -path "ota*" -not -path "webflash*" -delete
rm -rf ota/beta/*.bin
rm -rf ota/debug/beta/*.bin
rm -rf .github .gitignore .gitmodules
touch ota/beta/empty
touch ota/master/empty
touch ota/debug/beta/empty
touch ota/debug/master/empty
touch webflash/empty
- name: Commit binaries to binary
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Update binaries"
file_pattern: 'ota/* ota/debug/* ota/master/* ota/debug/master/* ota/beta/* ota/debug/beta/* webflash/*'
file_pattern: 'ota/* ota/master/* ota/beta/* webflash/*'
branch: binary
skip_dirty_check: true
skip_fetch: true

View File

@@ -312,7 +312,7 @@ In a browser navigate to the IP address assigned to the ESP32.
- User: Pick a username to enable HTTP authentication for the Web Configuration, Set to "#" to disable authentication.
- Password/Retype password: Pick a password to enable HTTP authentication for the Web Configuration.
- Use Digest Authentication (more secure): Enable to use HTTP Digest Authentication instead of HTTP Basic Authentication. Digest authentication is more secure, especially over unencrypted (HTTP) connections.
- HTTP Authentication type: Select from Basic, Digest or Form based authentication. Digest authentication is more secure than Basic or Form based authentication, especially over unencrypted (HTTP) connections. Form based authentication works best with password managers. Note: Firefox seems to have issues with basic authentication.
#### Nuki Lock PIN / Nuki Opener PIN

View File

@@ -303,7 +303,7 @@ String PsychicRequest::getCookie(const char* key)
// how big is our cookie?
size_t size;
if (!hasCookie("counter", &size))
if (!hasCookie(key, &size))
return cookie;
// allocate cookie buffer... keep it on the stack

View File

@@ -120,4 +120,5 @@ CONFIG_ESP_WIFI_IRAM_OPT=n
CONFIG_ESP_WIFI_RX_IRAM_OPT=n
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
CONFIG_LWIP_DHCP_GET_NTP_SRV=y
CONFIG_LWIP_SNTP_UPDATE_DELAY=43200000
CONFIG_LWIP_SNTP_UPDATE_DELAY=43200000
CONFIG_LWIP_SNTP_MAX_SERVERS=3

View File

@@ -5,7 +5,7 @@
#define NUKI_HUB_VERSION "9.08"
#define NUKI_HUB_VERSION_INT (uint32_t)908
#define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2025-01-15"
#define NUKI_HUB_DATE "2025-01-17"
#define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest"
#define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"

View File

@@ -226,6 +226,7 @@ inline void initPreferences(Preferences* preferences)
preferences->putInt(preference_query_interval_configuration, 3600);
preferences->putInt(preference_query_interval_battery, 1800);
preferences->putInt(preference_query_interval_keypad, 1800);
preferences->putInt(preference_http_auth_type, 0);
preferences->putBool(preference_debug_connect, false);
preferences->putBool(preference_debug_communication, false);
@@ -233,7 +234,6 @@ inline void initPreferences(Preferences* preferences)
preferences->putBool(preference_debug_hex_data, false);
preferences->putBool(preference_debug_command, false);
preferences->putBool(preference_connect_mode, true);
preferences->putBool(preference_http_auth_type, false);
preferences->putBool(preference_retain_gpio, false);
preferences->putBool(preference_enable_debug_mode, false);
@@ -508,7 +508,7 @@ private:
preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_enabled, preference_mqtt_hass_enabled, preference_retain_gpio,
preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt,
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled, preference_hass_device_discovery,
preference_ntw_reconfigure, preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_find_best_rssi, preference_http_auth_type,
preference_ntw_reconfigure, preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_find_best_rssi,
preference_debug_connect, preference_debug_communication, preference_debug_readable_data, preference_debug_hex_data, preference_debug_command, preference_connect_mode,
preference_lock_force_id, preference_lock_force_doorsensor, preference_lock_force_keypad, preference_opener_force_id, preference_opener_force_keypad, preference_mqtt_ssl_enabled,
preference_hybrid_reboot_on_disconnect, preference_lock_gemini_enabled, preference_enable_debug_mode
@@ -528,7 +528,7 @@ private:
preference_task_size_network, preference_task_size_nuki, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries,
preference_ble_tx_power, preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr,
preference_network_custom_irq, preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso,
preference_network_custom_mosi, preference_network_custom_pwr, preference_network_custom_mdio
preference_network_custom_mosi, preference_network_custom_pwr, preference_network_custom_mdio, preference_http_auth_type
};
std::vector<char*> _uintPrefs =
{

View File

@@ -56,8 +56,12 @@ WebCfgServer::WebCfgServer(NukiNetwork* network, Preferences* preferences, bool
str = _preferences->getString(preference_cred_password, "");
const char *pass = str.c_str();
memcpy(&_credPassword, pass, str.length());
}
if (_preferences->getInt(preference_http_auth_type, 0) == 2)
{
loadSessions();
}
}
_confirmCode = generateConfirmCode();
#ifndef NUKI_HUB_UPDATER
@@ -76,41 +80,115 @@ WebCfgServer::WebCfgServer(NukiNetwork* network, Preferences* preferences, bool
#endif
}
void WebCfgServer::initialize()
bool WebCfgServer::isAuthenticated(PsychicRequest *request)
{
_psychicServer->onOpen([&](PsychicClient* client) { Log->printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); });
_psychicServer->onClose([&](PsychicClient* client) { Log->printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
if (request->hasCookie("sessionId")) {
String cookie = request->getCookie("sessionId");
HTTPAuthMethod auth_type = BASIC_AUTH;
if (_preferences->getBool(preference_http_auth_type, false))
if (_httpSessions[cookie].is<JsonVariant>())
{
struct timeval time;
gettimeofday(&time, NULL);
int64_t time_us = (int64_t)time.tv_sec * 1000000L + (int64_t)time.tv_usec;
if (_httpSessions[cookie].as<signed long long>() > time_us)
{
return true;
}
else
{
Log->println("Cookie found, but not valid anymore");
}
}
}
Log->println("Authentication Failed");
return false;
}
esp_err_t WebCfgServer::logoutSession(PsychicRequest *request, PsychicResponse* resp)
{
Log->print("Logging out");
resp->setCookie("sessionId", "", 0, "HttpOnly");
if (request->hasCookie("sessionId")) {
String cookie = request->getCookie("sessionId");
_httpSessions.remove(cookie);
saveSessions();
}
else
{
auth_type = DIGEST_AUTH;
Log->print("No session cookie found");
}
_psychicServer->on("/", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in.");
}
return buildConfirmHtml(request, resp, "Logging out", 3, true);
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
if(!_network->isApOpen())
{
#endif
#ifndef NUKI_HUB_UPDATER
return buildHtml(request, resp);
#else
return buildOtaHtml(request, resp);
#endif
#ifndef CONFIG_IDF_TARGET_ESP32H2
void WebCfgServer::saveSessions()
{
if(_preferences->getBool(preference_update_time, false))
{
if (!SPIFFS.begin(true)) {
Log->println("SPIFFS Mount Failed");
}
else
{
File file = SPIFFS.open("/sessions.json", "w");
serializeJson(_httpSessions, file);
file.close();
}
}
}
void WebCfgServer::loadSessions()
{
if(_preferences->getBool(preference_update_time, false))
{
if (!SPIFFS.begin(true)) {
Log->println("SPIFFS Mount Failed");
}
else
{
return buildWifiConnectHtml(request, resp);
File file = SPIFFS.open("/sessions.json", "r");
if (!file || file.isDirectory()) {
Log->println("sessions.json not found");
}
else
{
deserializeJson(_httpSessions, file);
}
file.close();
}
#endif
});
}
}
int WebCfgServer::doAuthentication(PsychicRequest *request)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0)
{
int savedAuthType = _preferences->getInt(preference_http_auth_type, 0);
if (savedAuthType == 2)
{
if (!isAuthenticated(request)) {
return savedAuthType;
}
}
else
{
if (!request->authenticate(_credUser, _credPassword))
{
return savedAuthType;
}
}
}
return 4;
}
void WebCfgServer::initialize()
{
//_psychicServer->onOpen([&](PsychicClient* client) { Log->printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); });
//_psychicServer->onClose([&](PsychicClient* client) { Log->printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
_psychicServer->on("/style.css", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp)
{
return sendCss(request, resp);
@@ -129,9 +207,23 @@ void WebCfgServer::initialize()
});
_psychicServer->on("/savewifi", HTTP_POST, [&](PsychicRequest *request, PsychicResponse* resp)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
int authReq = doAuthentication(request);
switch (authReq)
{
return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in.");
case 0:
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
break;
case 1:
return request->requestAuthentication(DIGEST_AUTH, "Nuki Hub", "You must log in.");
break;
case 2:
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/get?page=login");
case 4:
default:
break;
}
String message = "";
@@ -148,9 +240,23 @@ void WebCfgServer::initialize()
});
_psychicServer->on("/reboot", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
int authReq = doAuthentication(request);
switch (authReq)
{
return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in.");
case 0:
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
break;
case 1:
return request->requestAuthentication(DIGEST_AUTH, "Nuki Hub", "You must log in.");
break;
case 2:
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/get?page=login");
case 4:
default:
break;
}
String value = "";
@@ -169,6 +275,8 @@ void WebCfgServer::initialize()
if(value != _confirmCode)
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
esp_err_t res = buildConfirmHtml(request, resp, "Rebooting...", 2, true);
@@ -191,16 +299,44 @@ void WebCfgServer::initialize()
value = p->value();
}
}
if (value != "status")
int authReq = doAuthentication(request);
if (value != "status" && value != "login" && value != "logout")
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
switch (authReq)
{
return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in.");
case 0:
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
break;
case 1:
return request->requestAuthentication(DIGEST_AUTH, "Nuki Hub", "You must log in.");
break;
case 2:
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/get?page=login");
case 4:
default:
break;
}
}
else if (value == "status" && authReq != 4)
{
resp->setCode(200);
resp->setContentType("application/json");
resp->setContent("{}");
return resp->send();
}
if (value == "reboot")
if (value == "login")
{
return buildLoginHtml(request, resp);
}
else if (value == "logout")
{
return logoutSession(request, resp);
}
else if (value == "reboot")
{
String value = "";
if(request->hasParam("CONFIRMTOKEN"))
@@ -218,6 +354,8 @@ void WebCfgServer::initialize()
if(value != _confirmCode)
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
esp_err_t res = buildConfirmHtml(request, resp, "Rebooting...", 2, true);
@@ -250,18 +388,7 @@ void WebCfgServer::initialize()
}
else if (value == "status")
{
if(request->hasParam("token"))
{
const PsychicWebParameter* p2 = request->getParam("token");
if(p2->value().toInt() == _randomInt)
{
return buildStatusHtml(request, resp);
}
}
resp->setCode(200);
resp->setContentType("text/html");
resp->setContent("");
return resp->send();
return buildStatusHtml(request, resp);
}
else if (value == "acclvl")
{
@@ -337,6 +464,8 @@ void WebCfgServer::initialize()
}
if(value != _confirmCode)
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
if(!_allowRestartToPortal)
@@ -356,7 +485,8 @@ void WebCfgServer::initialize()
}
else if (value == "otadebug")
{
return buildOtaHtml(request, resp, true);
return buildOtaHtml(request, resp);
//return buildOtaHtml(request, resp, true);
}
else if (value == "reboottoota")
{
@@ -376,6 +506,8 @@ void WebCfgServer::initialize()
if(value != _confirmCode)
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
esp_err_t res = buildConfirmHtml(request, resp, "Rebooting to other partition...", 2, true);
@@ -389,36 +521,21 @@ void WebCfgServer::initialize()
#ifndef NUKI_HUB_UPDATER
return processUpdate(request, resp);
#else
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
#endif
}
else
{
#ifndef CONFIG_IDF_TARGET_ESP32H2
if(!_network->isApOpen())
{
#endif
#ifndef NUKI_HUB_UPDATER
return buildHtml(request, resp);
#else
return buildOtaHtml(request, resp);
#endif
#ifndef CONFIG_IDF_TARGET_ESP32H2
}
else
{
return buildWifiConnectHtml(request, resp);
}
#endif
Log->println("Page not found, loading index");
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
});
_psychicServer->on("/post", HTTP_POST, [&](PsychicRequest *request, PsychicResponse* resp)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in.");
}
String value = "";
if(request->hasParam("page"))
{
@@ -429,8 +546,46 @@ void WebCfgServer::initialize()
}
}
if (value != "login")
{
int authReq = doAuthentication(request);
switch (authReq)
{
case 0:
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
break;
case 1:
return request->requestAuthentication(DIGEST_AUTH, "Nuki Hub", "You must log in.");
break;
case 2:
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/get?page=login");
case 4:
default:
break;
}
}
if (value == "login")
{
bool loggedIn = processLogin(request, resp);
if (loggedIn)
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
else
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/get?page=login");
}
}
#ifndef NUKI_HUB_UPDATER
if (value == "savecfg")
else if (value == "savecfg")
{
String message = "";
bool restart = processArgs(request, resp, message);
@@ -474,10 +629,8 @@ void WebCfgServer::initialize()
bool restart = processImport(request, resp, message);
return buildConfirmHtml(request, resp, message, 3, true);
}
else
#else
if (1 == 1)
#endif
else
{
#ifndef CONFIG_IDF_TARGET_ESP32H2
if(!_network->isApOpen())
@@ -501,19 +654,44 @@ void WebCfgServer::initialize()
PsychicUploadHandler *updateHandler = new PsychicUploadHandler();
updateHandler->onUpload([&](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
{
return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in.");
}
int authReq = doAuthentication(request);
switch (authReq)
{
case 0:
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
break;
case 1:
return request->requestAuthentication(DIGEST_AUTH, "Nuki Hub", "You must log in.");
break;
case 2:
return ESP_FAIL;
case 4:
default:
break;
}
return handleOtaUpload(request, filename, index, data, len, last);
});
updateHandler->onRequest([&](PsychicRequest* request, PsychicResponse* resp)
{
if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword))
int authReq = doAuthentication(request);
switch (authReq)
{
return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in.");
case 0:
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
break;
case 1:
return request->requestAuthentication(DIGEST_AUTH, "Nuki Hub", "You must log in.");
break;
case 2:
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/get?page=login");
case 4:
default:
break;
}
String result;
@@ -546,6 +724,47 @@ void WebCfgServer::initialize()
_psychicServer->on("/uploadota", HTTP_POST, updateHandler);
//Update.onProgress(printProgress);
}
_psychicServer->on("/", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp)
{
int authReq = doAuthentication(request);
switch (authReq)
{
case 0:
return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in.");
break;
case 1:
return request->requestAuthentication(DIGEST_AUTH, "Nuki Hub", "You must log in.");
break;
case 2:
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/get?page=login");
break;
case 4:
default:
break;
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
if(!_network->isApOpen())
{
#endif
#ifndef NUKI_HUB_UPDATER
return buildHtml(request, resp);
#else
return buildOtaHtml(request, resp);
#endif
#ifndef CONFIG_IDF_TARGET_ESP32H2
}
else
{
return buildWifiConnectHtml(request, resp);
}
#endif
});
}
void WebCfgServer::printCheckBox(PsychicStreamResponse *response, const char *token, const char *description, const bool value, const char *htmlClass)
@@ -1266,6 +1485,54 @@ void WebCfgServer::printInputField(PsychicStreamResponse *response,
printInputField(response, token, description, valueStr, maxLength, args);
}
esp_err_t WebCfgServer::buildLoginHtml(PsychicRequest *request, PsychicResponse* resp)
{
PsychicStreamResponse response(resp, "text/html");
response.beginSend();
response.print("<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
response.print("<style>form{border:3px solid #f1f1f1; max-width: 400px;}input[type=password],input[type=text]{width:100%;padding:12px 20px;margin:8px 0;display:inline-block;border:1px solid #ccc;box-sizing:border-box}button{background-color:#04aa6d;color:#fff;padding:14px 20px;margin:8px 0;border:none;cursor:pointer;width:100%}button:hover{opacity:.8}.container{padding:16px}span.password{float:right;padding-top:16px}@media screen and (max-width:300px){span.psw{display:block;float:none}}</style>");
response.print("<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
response.print("</head><body><center><h2>NukiHub login</h2><form action=\"/post?page=login\" method=\"post\">");
response.print("<div class=\"container\"><label for=\"username\"><b>Username</b></label><input type=\"text\" placeholder=\"Enter Username\" name=\"username\" required>");
response.print("<label for=\"password\"><b>Password</b></label><input type=\"password\" placeholder=\"Enter Password\" name=\"password\" required>");
response.print("<button type=\"submit\">Login</button><label><input type=\"checkbox\" checked=\"checked\" name=\"remember\"> Remember me</label></div>");
response.print("</form></center></body></html>");
return response.endSend();
}
bool WebCfgServer::processLogin(PsychicRequest *request, PsychicResponse* resp)
{
if(request->hasParam("username") && request->hasParam("password"))
{
const PsychicWebParameter* user = request->getParam("username");
const PsychicWebParameter* pass = request->getParam("password");
if(user->value() != "" && pass->value() != "")
{
if (user->value() == _preferences->getString(preference_cred_user, "") && pass->value() == _preferences->getString(preference_cred_password, ""))
{
char buffer[33];
int i;
int64_t durationLength = 60*60*24*30;
for (i = 0; i < 4; i++) {
sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random());
}
if(!request->hasParam("remember")) {
durationLength = 60*60;
}
resp->setCookie("sessionId", buffer, durationLength, "HttpOnly");
struct timeval time;
gettimeofday(&time, NULL);
int64_t time_us = (int64_t)time.tv_sec * 1000000L + (int64_t)time.tv_usec;
_httpSessions[buffer] = time_us + (durationLength*1000000L);
saveSessions();
return true;
}
}
}
return false;
}
#ifndef NUKI_HUB_UPDATER
esp_err_t WebCfgServer::sendSettings(PsychicRequest *request, PsychicResponse* resp)
{
@@ -2788,9 +3055,9 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
}
else if(key == "CREDDIGEST")
{
if(_preferences->getBool(preference_http_auth_type, false) != (value == "1"))
if(_preferences->getInt(preference_http_auth_type, 0) != value.toInt())
{
_preferences->putBool(preference_http_auth_type, (value == "1"));
_preferences->putInt(preference_http_auth_type, value.toInt());
Log->print(("Setting changed: "));
Log->println(key);
configChanged = true;
@@ -3914,8 +4181,7 @@ esp_err_t WebCfgServer::buildCustomNetworkConfigHtml(PsychicRequest *request, Ps
esp_err_t WebCfgServer::buildHtml(PsychicRequest *request, PsychicResponse* resp)
{
_randomInt = esp_random();
String header = (String)"<script>let intervalId; window.onload = function() { updateInfo(); intervalId = setInterval(updateInfo, 3000); }; function updateInfo() { var request = new XMLHttpRequest(); request.open('GET', '/get?page=status&token=" + _randomInt + "', true); request.onload = () => { const obj = JSON.parse(request.responseText); if (obj.stop == 1) { clearInterval(intervalId); } for (var key of Object.keys(obj)) { if(key=='ota' && document.getElementById(key) !== null) { document.getElementById(key).innerText = \"<a href='/ota'>\" + obj[key] + \"</a>\"; } else if(document.getElementById(key) !== null) { document.getElementById(key).innerText = obj[key]; } } }; request.send(); }</script>";
String header = (String)"<script>let intervalId; window.onload = function() { updateInfo(); intervalId = setInterval(updateInfo, 3000); }; function updateInfo() { var request = new XMLHttpRequest(); request.open('GET', '/get?page=status', true); request.onload = () => { const obj = JSON.parse(request.responseText); if (obj.stop == 1) { clearInterval(intervalId); } for (var key of Object.keys(obj)) { if(key=='ota' && document.getElementById(key) !== null) { document.getElementById(key).innerText = \"<a href='/ota'>\" + obj[key] + \"</a>\"; } else if(document.getElementById(key) !== null) { document.getElementById(key).innerText = obj[key]; } } }; request.send(); }</script>";
PsychicStreamResponse response(resp, "text/html");
response.beginSend();
buildHtmlHeader(&response, header);
@@ -4008,6 +4274,10 @@ esp_err_t WebCfgServer::buildHtml(PsychicRequest *request, PsychicResponse* resp
#endif
String rebooturl = "/get?page=reboot&CONFIRMTOKEN=" + _confirmCode;
buildNavigationMenuEntry(&response, "Reboot Nuki Hub", rebooturl.c_str());
if (_preferences->getInt(preference_http_auth_type, 0) == 2)
{
buildNavigationMenuEntry(&response, "Logout", "/get?page=logout");
}
response.print("</ul></body></html>");
return response.endSend();
}
@@ -4024,7 +4294,13 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
printInputField(&response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "id=\"inputuser\"", false, true);
printInputField(&response, "CREDPASS", "Password", "*", 30, "id=\"inputpass\"", true, true);
printInputField(&response, "CREDPASSRE", "Retype password", "*", 30, "id=\"inputpass2\"", true);
printCheckBox(&response, "CREDDIGEST", "Use Digest Authentication (more secure)", _preferences->getBool(preference_http_auth_type, false), "");
std::vector<std::pair<String, String>> httpAuthOptions;
httpAuthOptions.push_back(std::make_pair("0", "Basic"));
httpAuthOptions.push_back(std::make_pair("1", "Digest"));
httpAuthOptions.push_back(std::make_pair("2", "Form"));
printDropDown(&response, "CREDDIGEST", "HTTP Authentication type", String(_preferences->getInt(preference_http_auth_type, 0)), httpAuthOptions, "");
response.print("</table>");
response.print("<br><input type=\"submit\" name=\"submit\" value=\"Save\">");
response.print("</form><script>function testcreds() { var input_user = document.getElementById(\"inputuser\").value; var input_pass = document.getElementById(\"inputpass\").value; var input_pass2 = document.getElementById(\"inputpass2\").value; var pattern = /^[ -~]*$/; if(input_user == '#' || input_user == '') { return true; } if (input_pass != input_pass2) { alert('Passwords do not match'); return false;} if(!pattern.test(input_user) || !pattern.test(input_pass)) { alert('Only non unicode characters are allowed in username and password'); return false;} else { return true; } }</script>");
@@ -4964,7 +5240,7 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
response.print("\nWeb configurator password: ");
response.print(_preferences->getString(preference_cred_password, "").length() > 0 ? "***" : "Not set");
response.print("\nWeb configurator authentication: ");
response.print(_preferences->getBool(preference_http_auth_type, false) ? "Digest" : "Basic");
response.print(_preferences->getInt(preference_http_auth_type, 0) == 0 ? "Basic" : _preferences->getInt(preference_http_auth_type, 0) == 1 ? "Digest" : "Form");
response.print("\nWeb configurator enabled: ");
response.print(_preferences->getBool(preference_webserver_enabled, true) ? "Yes" : "No");
response.print("\nHTTP SSL: ");
@@ -5652,6 +5928,7 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request, PsychicResponse*
if(request->hasParam("beta"))
{
/*
if(request->hasParam("debug"))
{
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest DEBUG BETA version", 2, true);
@@ -5660,13 +5937,15 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request, PsychicResponse*
}
else
{
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest BETA version", 2, true);
_preferences->putString(preference_ota_updater_url, GITHUB_BETA_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_BETA_RELEASE_BINARY_URL);
}
*/
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest BETA version", 2, true);
_preferences->putString(preference_ota_updater_url, GITHUB_BETA_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_BETA_RELEASE_BINARY_URL);
//}
}
else if(request->hasParam("master"))
{
/*
if(request->hasParam("debug"))
{
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest DEBUG DEVELOPMENT version", 2, true);
@@ -5675,10 +5954,11 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request, PsychicResponse*
}
else
{
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest DEVELOPMENT version", 2, true);
_preferences->putString(preference_ota_updater_url, GITHUB_MASTER_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL);
}
*/
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest DEVELOPMENT version", 2, true);
_preferences->putString(preference_ota_updater_url, GITHUB_MASTER_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL);
//}
}
#if defined(CONFIG_IDF_TARGET_ESP32S3)
else if(request->hasParam("other"))
@@ -5690,6 +5970,7 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request, PsychicResponse*
#endif
else
{
/*
if(request->hasParam("debug"))
{
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest DEBUG RELEASE version", 2, true);
@@ -5698,10 +5979,11 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request, PsychicResponse*
}
else
{
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest RELEASE version", 2, true);
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL);
}
*/
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest RELEASE version", 2, true);
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL);
//}
}
waitAndProcess(true, 1000);
restartEsp(RestartReason::OTAReboot);

View File

@@ -100,7 +100,14 @@ private:
std::vector<int> _rssiList;
String generateConfirmCode();
String _confirmCode = "----";
void saveSessions();
void loadSessions();
esp_err_t logoutSession(PsychicRequest *request, PsychicResponse* resp);
bool isAuthenticated(PsychicRequest *request);
bool processLogin(PsychicRequest *request, PsychicResponse* resp);
int doAuthentication(PsychicRequest *request);
esp_err_t buildLoginHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildSSIDListHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildConfirmHtml(PsychicRequest *request, PsychicResponse* resp, const String &message, uint32_t redirectDelay = 5, bool redirect = false, String redirectTo = "/");
esp_err_t buildOtaHtml(PsychicRequest *request, PsychicResponse* resp, bool debug = false);
@@ -128,6 +135,6 @@ private:
bool _allowRestartToPortal = false;
uint8_t _partitionType = 0;
size_t _otaContentLen = 0;
uint32_t _randomInt = 0;
String _hostname;
JsonDocument _httpSessions;
};

View File

@@ -10,6 +10,7 @@
#include "esp32-hal-log.h"
#include "hal/wdt_hal.h"
#include "esp_chip_info.h"
#include "esp_netif_sntp.h"
#ifdef CONFIG_SOC_SPIRAM_SUPPORTED
#include "esp_psram.h"
#include "FS.h"
@@ -29,7 +30,6 @@
#include "RestartReason.h"
#include "EspMillis.h"
#include "NimBLEDevice.h"
#include "esp_netif_sntp.h"
/*
#ifdef DEBUG_NUKIHUB
@@ -38,8 +38,6 @@
#endif
*/
char log_print_buffer[1024];
NukiNetworkLock* networkLock = nullptr;
NukiNetworkOpener* networkOpener = nullptr;
BleScanner::Scanner* bleScanner = nullptr;
@@ -71,6 +69,8 @@ int64_t restartTs = 10 * 60 * 1000;
#endif
char log_print_buffer[1024];
PsychicHttpServer* psychicServer = nullptr;
PsychicHttpsServer* psychicSSLServer = nullptr;
NukiNetwork* network = nullptr;
@@ -97,7 +97,6 @@ RestartReason currentRestartReason = RestartReason::NotApplicable;
TaskHandle_t otaTaskHandle = nullptr;
TaskHandle_t networkTaskHandle = nullptr;
#ifndef NUKI_HUB_UPDATER
ssize_t write_fn(void* cookie, const char* buf, ssize_t size)
{
Log->write((uint8_t *)buf, (size_t)size);
@@ -157,7 +156,6 @@ void setReroute()
}
}
#endif
uint8_t checkPartition()
{
@@ -181,6 +179,10 @@ uint8_t checkPartition()
}
}
void cbSyncTime(struct timeval *tv) {
Log->println("NTP time synched");
}
void networkTask(void *pvParameters)
{
int64_t networkLoopTs = 0;
@@ -204,13 +206,15 @@ void networkTask(void *pvParameters)
network->update();
bool connected = network->isConnected();
#ifndef NUKI_HUB_UPDATER
if(connected && reroute)
{
if(preferences->getBool(preference_update_time, false))
{
esp_netif_sntp_start();
}
reroute = false;
setReroute();
}
#endif
#ifndef NUKI_HUB_UPDATER
wifiConnected = network->wifiConnected();
@@ -503,10 +507,6 @@ void setupTasks(bool ota)
}
}
void cbSyncTime(struct timeval *tv) {
Log->println(("NTP time synched"));
}
void setup()
{
//Set Log level to error for all TAGS
@@ -822,16 +822,14 @@ void setup()
}
#endif
*/
}
if(preferences->getBool(preference_update_time, false))
{
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(preferences->getString(preference_time_server, "pool.ntp.org").c_str());
String timeserver = preferences->getString(preference_time_server, "pool.ntp.org");
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG(timeserver.c_str());
config.start = false;
config.server_from_dhcp = true;
config.renew_servers_after_new_IP = true;
config.index_of_first_server = 1;
if (network->networkDeviceType() == NetworkDeviceType::WiFi)
{
config.ip_event_to_renew = IP_EVENT_STA_GOT_IP;