HTTP Form auth
This commit is contained in:
@@ -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
|
||||
|
||||
Submodule lib/nuki_ble updated: a579d58a1c...cf762e7dbd
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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 =
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
@@ -376,6 +505,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 +520,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 +545,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 +628,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 +653,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 +723,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 +1484,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 +3054,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 +4180,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 +4273,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 +4293,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 +5239,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: ");
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
33
src/main.cpp
33
src/main.cpp
@@ -10,6 +10,10 @@
|
||||
#include "esp32-hal-log.h"
|
||||
#include "hal/wdt_hal.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include <time.h>
|
||||
#include <esp_sntp.h>
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_sntp.h"
|
||||
#ifdef CONFIG_SOC_SPIRAM_SUPPORTED
|
||||
#include "esp_psram.h"
|
||||
#include "FS.h"
|
||||
@@ -29,7 +33,6 @@
|
||||
#include "RestartReason.h"
|
||||
#include "EspMillis.h"
|
||||
#include "NimBLEDevice.h"
|
||||
#include "esp_netif_sntp.h"
|
||||
|
||||
/*
|
||||
#ifdef DEBUG_NUKIHUB
|
||||
@@ -38,8 +41,6 @@
|
||||
#endif
|
||||
*/
|
||||
|
||||
char log_print_buffer[1024];
|
||||
|
||||
NukiNetworkLock* networkLock = nullptr;
|
||||
NukiNetworkOpener* networkOpener = nullptr;
|
||||
BleScanner::Scanner* bleScanner = nullptr;
|
||||
@@ -71,6 +72,8 @@ int64_t restartTs = 10 * 60 * 1000;
|
||||
|
||||
#endif
|
||||
|
||||
char log_print_buffer[1024];
|
||||
|
||||
PsychicHttpServer* psychicServer = nullptr;
|
||||
PsychicHttpsServer* psychicSSLServer = nullptr;
|
||||
NukiNetwork* network = nullptr;
|
||||
@@ -97,7 +100,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 +159,6 @@ void setReroute()
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t checkPartition()
|
||||
{
|
||||
@@ -181,6 +182,10 @@ uint8_t checkPartition()
|
||||
}
|
||||
}
|
||||
|
||||
void cbSyncTime(struct timeval *tv) {
|
||||
Log->println("NTP time synched");
|
||||
}
|
||||
|
||||
void networkTask(void *pvParameters)
|
||||
{
|
||||
int64_t networkLoopTs = 0;
|
||||
@@ -204,13 +209,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 +510,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 +825,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;
|
||||
|
||||
Reference in New Issue
Block a user