Bypass MFA

This commit is contained in:
iranl
2025-02-11 22:37:00 +01:00
parent 747688c4cc
commit b2fd9d9349
10 changed files with 254 additions and 46 deletions

View File

@@ -527,7 +527,7 @@ Note that the following options can break Nuki Hub and cause bootloops that will
Consider this when deciding if you want to enable the following functionality:
- Any application/actor that has read access to `nukihub/configuration/action` and `nukihub/configuration/json` can view your changes and exports.
- Any application/actor that has read access to `nukihub/configuration/action` and `nukihub/configuration/json` can view your changes and exports.
- If you have not enabled the setting to require MFA when changing settings any application/actor that has write access to `nukihub/configuration/action` can change Nuki Hub settings (including pairing data and credentials)
### Export Nuki Hub settings over MQTT
@@ -562,7 +562,7 @@ After the import is complete the ESP32 will reboot.
If you have enabled `Require MFA (Duo/TOTP) authentication for all sensitive Nuki Hub operations (changing/exporting settings)` you will need to either provide a currently valid TOTP code as part of the sent JSON in the `totp` node or approve the Duo Push before the settings will be changed/imported.
Note: When importing settings using MQTT there are less/no checks on the values entered. These checks are only available when changing settings through the WebConfigurator.
Note: When importing settings using MQTT there are less/no checks on the values entered. These checks are only available when changing settings through the WebConfigurator.
Consider testing your configuration values by changing them in the Web Configurator before trying to use MQTT to change configuration.
A general explanation of the values that can be imported can be found in the [PreferencesKeys.h](/src/PreferencesKeys.h) file

View File

@@ -5,7 +5,7 @@
#define NUKI_HUB_VERSION "9.09"
#define NUKI_HUB_VERSION_INT (uint32_t)909
#define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2025-02-11"
#define NUKI_HUB_DATE "2025-02-13"
#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

@@ -39,6 +39,8 @@ void ImportExport::readSettings()
_totpKey = _preferences->getString(preference_totp_secret, "");
_totpEnabled = _totpKey.length() > 0;
_bypassKey = _preferences->getString(preference_bypass_secret, "");
_bypassEnabled = _bypassKey.length() > 0;
}
bool ImportExport::getDuoEnabled()
@@ -51,6 +53,11 @@ bool ImportExport::getTOTPEnabled()
return _totpEnabled;
}
bool ImportExport::getBypassEnabled()
{
return _bypassEnabled;
}
bool ImportExport::getBypassGPIOEnabled()
{
return _bypassGPIO;
@@ -132,7 +139,7 @@ int ImportExport::checkDuoAuth(PsychicRequest *request)
const char* duo_ikey = _duoIkey.c_str();
const char* duo_skey = _duoSkey.c_str();
const char* duo_user = _duoUser.c_str();
int type = 0;
if(request->hasParam("type"))
{
@@ -168,7 +175,7 @@ int ImportExport::checkDuoAuth(PsychicRequest *request)
_duoTransactionId = "";
_duoCheckIP = "";
_duoCheckId = "";
if(type==0)
{
int64_t durationLength = 60*60*_preferences->getInt(preference_cred_session_lifetime_duo_remember, 720);
@@ -200,7 +207,7 @@ int ImportExport::checkDuoAuth(PsychicRequest *request)
_duoTransactionId = "";
_duoCheckIP = "";
_duoCheckId = "";
if(type==0)
{
if (_preferences->getBool(preference_mfa_reconfigure, false))
@@ -278,9 +285,9 @@ bool ImportExport::checkTOTP(String* totpKey)
}
_lastCodeCheck = espMillis();
String key(totpKey->c_str());
String key(totpKey->c_str());
time_t now;
time(&now);
int totpTime = -60;
@@ -288,7 +295,7 @@ bool ImportExport::checkTOTP(String* totpKey)
while (totpTime <= 60)
{
String key2(TOTP::currentOTP(now, _totpKey, 30, 6, totpTime)->c_str());
if(key.toInt() == key2.toInt())
{
_invalidCount = 0;
@@ -303,6 +310,30 @@ bool ImportExport::checkTOTP(String* totpKey)
return false;
}
bool ImportExport::checkBypass(String bypass)
{
if(_bypassEnabled)
{
if((pow(_invalidCount2, 5) + _lastCodeCheck2) > espMillis())
{
_lastCodeCheck2 = espMillis();
return false;
}
_lastCodeCheck2 = espMillis();
if(bypass == _bypassKey)
{
_invalidCount2 = 0;
Log->println("Successful Bypass MFA Auth");
return true;
}
_invalidCount2++;
Log->println("Failed Bypass MFA Auth");
}
return false;
}
void ImportExport::exportHttpsJson(JsonDocument &json)
{
if (!SPIFFS.begin(true)) {
@@ -442,6 +473,10 @@ void ImportExport::exportNukiHubJson(JsonDocument &json, bool redacted, bool pai
{
continue;
}
if(strcmp(key, preference_bypass_secret) == 0)
{
continue;
}
if(!redacted) if(std::find(redactedPrefs.begin(), redactedPrefs.end(), key) != redactedPrefs.end())
{
continue;

View File

@@ -16,7 +16,9 @@ public:
int checkDuoApprove();
bool startDuoAuth(char* pushType = (char*)"");
bool getTOTPEnabled();
bool getBypassEnabled();
bool checkTOTP(String* totpKey);
bool checkBypass(String bypass);
bool getDuoEnabled();
bool getBypassGPIOEnabled();
int getBypassGPIOHigh();
@@ -27,13 +29,17 @@ public:
JsonDocument _duoSessions;
JsonDocument _totpSessions;
JsonDocument _sessionsOpts;
JsonDocument _bypassSessions;
int64_t _lastCodeCheck = 0;
int64_t _lastCodeCheck2 = 0;
int _invalidCount = 0;
int _invalidCount2 = 0;
private:
void saveSessions();
Preferences* _preferences;
struct tm timeinfo;
bool _totpEnabled = false;
bool _bypassEnabled = false;
bool _duoActiveRequest;
bool _duoEnabled = false;
bool _bypassGPIO = false;
@@ -47,6 +53,7 @@ private:
String _duoUser;
String _duoCheckId;
String _duoCheckIP;
String _totpKey;
String _totpKey;
String _bypassKey;
};

View File

@@ -384,12 +384,17 @@ bool NukiNetwork::update()
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
int64_t ts = espMillis();
_device->update();
if(_importExport->getTOTPEnabled() && _importExport->_invalidCount > 0 && (ts - (120000 * _importExport->_invalidCount)) > _importExport->_lastCodeCheck)
{
_importExport->_invalidCount--;
}
if(_importExport->getBypassEnabled() && _importExport->_invalidCount2 > 0 && (ts - (120000 * _importExport->_invalidCount2)) > _importExport->_lastCodeCheck2)
{
_importExport->_invalidCount2--;
}
if(disableNetwork || !_mqttEnabled || _device->isApOpen())
{
return false;
@@ -477,7 +482,7 @@ bool NukiNetwork::update()
_lastRssi = rssi;
}
}
if(_overwriteNukiHubConfigTS > 0 && espMillis() > _overwriteNukiHubConfigTS)
{
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_json, "--", true);
@@ -1101,7 +1106,7 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
if(timeSynced && _importExport->getTOTPEnabled() && !doc["totp"].isNull())
{
String jsonTotp = doc["totp"];
if (!_importExport->checkTOTP(&jsonTotp)) {
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action_command_result, "{\"error\": \"totpIncorrect\"}", false);
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true);
@@ -1114,15 +1119,19 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true);
return;
}
else if (_importExport->startDuoAuth((char*)"Approve Nuki Hub setting change"))
else
{
bool duoRes = _importExport->startDuoAuth((char*)"Approve Nuki Hub setting change");
int duoResult = 2;
while (duoResult == 2)
if (duoRes)
{
duoResult = _importExport->checkDuoApprove();
delay(2000);
esp_task_wdt_reset();
while (duoResult == 2)
{
duoResult = _importExport->checkDuoApprove();
delay(2000);
esp_task_wdt_reset();
}
}
if (duoResult != 1)
@@ -1133,18 +1142,18 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
}
}
}
if(!doc["exportHTTPS"].isNull() && _device->isEncrypted())
{
if(_preferences->getBool(preference_publish_config, false))
{
if(_device->isEncrypted())
if(_device->isEncrypted())
{
JsonDocument json;
_importExport->exportHttpsJson(json);
serializeJson(json, _buffer, _bufferSize);
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_json, _buffer, false);
if (doc["exportHTTPS"].as<int>() > 0)
{
_overwriteNukiHubConfigTS = espMillis() + (doc["exportHTTPS"].as<int>() * 1000);
@@ -1164,7 +1173,7 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
{
if(_preferences->getBool(preference_publish_config, false))
{
if(_device->isEncrypted())
if(_device->isEncrypted())
{
JsonDocument json;
_importExport->exportMqttsJson(json);
@@ -1193,7 +1202,7 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
bool redacted = false;
if(!doc["redacted"].isNull())
{
if(_device->isEncrypted())
if(_device->isEncrypted())
{
redacted = true;
}
@@ -1205,7 +1214,7 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
bool pairing = false;
if(!doc["pairing"].isNull())
{
if(_device->isEncrypted())
if(_device->isEncrypted())
{
pairing = true;
}

View File

@@ -62,7 +62,7 @@ void NukiWrapper::initialize()
_nukiLock.setDebugHexData(_preferences->getBool(preference_debug_hex_data, false));
_nukiLock.setDebugCommand(_preferences->getBool(preference_debug_command, false));
_nukiLock.registerLogger(Log);
if (_preferences->getInt(preference_lock_gemini_pin, 0) > 0 && _preferences->getBool(preference_lock_gemini_enabled, false))
{
_nukiLock.saveUltraPincode(_preferences->getInt(preference_lock_gemini_pin, 0), false);
@@ -90,7 +90,7 @@ void NukiWrapper::readSettings()
#else
if(pwrLvl >= 20)
{
powerLevel = ESP_PWR_LVL_P20;
powerLevel = ESP_PWR_LVL_P20;
}
else if(pwrLvl >= 18)
{
@@ -486,7 +486,7 @@ void NukiWrapper::unpair()
nukiBlePref.clear();
nukiBlePref.end();
_deviceId->assignNewId();
if (!_forceId)
if (!_forceId)
{
_preferences->remove(preference_nuki_id_lock);
}
@@ -4302,18 +4302,18 @@ void NukiWrapper::updateTime()
Log->println("No valid PIN set");
return;
}
time_t now;
tm tm;
time(&now);
localtime_r(&now, &tm);
if (int(tm.tm_year + 1900) < int(2025))
{
Log->println("NTP Time not valid, not updating Nuki device");
return;
}
Nuki::TimeValue nukiTime;
nukiTime.year = tm.tm_year + 1900;
nukiTime.month = tm.tm_mon + 1;

View File

@@ -96,6 +96,7 @@
#define preference_publish_config (char*)"nhPubConfig"
#define preference_config_from_mqtt (char*)"nhCntrlEnabled"
#define preference_totp_secret (char*)"totpsecret"
#define preference_bypass_secret (char*)"bypassecret"
// CHANGE DOES NOT REQUIRE REBOOT TO TAKE EFFECT
#define preference_find_best_rssi (char*)"nwbestrssi"
@@ -535,13 +536,13 @@ private:
preference_cred_duo_host, preference_cred_duo_ikey, preference_cred_duo_skey, preference_cred_duo_user, preference_cred_duo_enabled, preference_https_fqdn, preference_bypass_proxy,
preference_cred_session_lifetime, preference_cred_session_lifetime_remember, preference_cred_session_lifetime_duo, preference_cred_session_lifetime_duo_remember,
preference_cred_duo_approval, preference_cred_bypass_boot_btn_enabled, preference_cred_bypass_gpio_high, preference_cred_bypass_gpio_low, preference_publish_config,
preference_config_from_mqtt, preference_totp_secret, preference_cred_session_lifetime_totp, preference_cred_session_lifetime_totp_remember
preference_config_from_mqtt, preference_totp_secret, preference_cred_session_lifetime_totp, preference_cred_session_lifetime_totp_remember, preference_bypass_secret
};
std::vector<char*> _redact =
{
preference_mqtt_user, preference_mqtt_password, preference_cred_user, preference_cred_password, preference_nuki_id_lock, preference_nuki_id_opener, preference_wifi_pass,
preference_lock_gemini_pin, preference_cred_duo_host, preference_cred_duo_ikey, preference_cred_duo_skey, preference_cred_duo_user, preference_bypass_proxy,
preference_totp_secret
preference_totp_secret, preference_bypass_secret
};
std::vector<char*> _boolPrefs =
{

View File

@@ -102,18 +102,22 @@ bool WebCfgServer::isAuthenticated(PsychicRequest *request, int type)
{
cookieKey = "totpId";
}
else if (type == 3)
{
cookieKey = "bypassId";
}
if (request->hasCookie(cookieKey.c_str()))
{
String cookie = request->getCookie(cookieKey.c_str());
if ((type == 0 && _httpSessions[cookie].is<JsonVariant>()) || (type == 1 && _importExport->_duoSessions[cookie].is<JsonVariant>()) || (type == 2 && _importExport->_totpSessions[cookie].is<JsonVariant>()))
if ((type == 0 && _httpSessions[cookie].is<JsonVariant>()) || (type == 1 && _importExport->_duoSessions[cookie].is<JsonVariant>()) || (type == 2 && _importExport->_totpSessions[cookie].is<JsonVariant>()) || (type == 3 && _importExport->_bypassSessions[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 ((type == 0 && _httpSessions[cookie].as<signed long long>() > time_us) || (type == 1 && _importExport->_duoSessions[cookie].as<signed long long>() > time_us) || (type == 2 && _importExport->_totpSessions[cookie].as<signed long long>() > time_us))
if ((type == 0 && _httpSessions[cookie].as<signed long long>() > time_us) || (type == 1 && _importExport->_duoSessions[cookie].as<signed long long>() > time_us) || (type == 2 && _importExport->_totpSessions[cookie].as<signed long long>() > time_us) || (type == 3 && _importExport->_bypassSessions[cookie].as<signed long long>() > time_us))
{
return true;
}
@@ -194,6 +198,23 @@ esp_err_t WebCfgServer::logoutSession(PsychicRequest *request, PsychicResponse*
}
}
if (_importExport->getBypassEnabled())
{
if (!_isSSL)
{
resp->setCookie("bypassId", "", 0, "HttpOnly");
}
else
{
resp->setCookie("bypassId", "", 0, "Secure; HttpOnly");
}
if (request->hasCookie("bypassId")) {
String cookie2 = request->getCookie("bypassId");
_importExport->_bypassSessions.remove(cookie2);
}
}
return buildConfirmHtml(request, resp, "Logging out", 3, true);
}
@@ -369,6 +390,11 @@ int WebCfgServer::doAuthentication(PsychicRequest *request)
_importExport->_sessionsOpts[request->client()->localIP().toString() + "totp"] = true;
return 4;
}
else if(!timeSynced && _importExport->getBypassEnabled() && isAuthenticated(request, 3))
{
_importExport->_sessionsOpts[request->client()->localIP().toString() + "totp"] = false;
return 4;
}
Log->println("Authentication Failed");
@@ -507,7 +533,7 @@ void WebCfgServer::initialize()
}
int authReq = doAuthentication(request);
if (value != "status" && value != "login" && value != "duocheck")
if (value != "status" && value != "login" && value != "duocheck" && value != "bypass")
{
switch (authReq)
{
@@ -559,6 +585,15 @@ void WebCfgServer::initialize()
{
return buildTOTPHtml(request, resp, 0);
}
else if (value == "bypass")
{
return buildBypassHtml(request, resp);
}
else if (value == "newbypass" && _newBypass)
{
_newBypass = false;
return buildConfirmHtml(request, resp, "Logged in using Bypass. New bypass: " + _preferences->getString(preference_bypass_secret, ""), 3, false);
}
else if (value == "logout")
{
return logoutSession(request, resp);
@@ -816,7 +851,7 @@ void WebCfgServer::initialize()
}
}
if (value != "login" && value != "totp")
if (value != "login" && value != "totp" && value != "bypass")
{
int authReq = doAuthentication(request);
@@ -866,6 +901,11 @@ void WebCfgServer::initialize()
}
}
}
else if(!timeSynced && _importExport->getBypassEnabled() && isAuthenticated(request, 3))
{
_importExport->_sessionsOpts[request->client()->localIP().toString() + "approve"] = false;
approved = true;
}
if (!approved)
{
@@ -926,6 +966,23 @@ void WebCfgServer::initialize()
return resp->redirect("/get?page=totp");
}
}
else if (value == "bypass")
{
bool loggedIn = processBypass(request, resp);
if (loggedIn)
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
_newBypass = true;
return resp->redirect("/get?page=newbypass");
}
else
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
}
#ifndef NUKI_HUB_UPDATER
else if (value == "savecfg")
{
@@ -1882,14 +1939,14 @@ esp_err_t WebCfgServer::buildTOTPHtml(PsychicRequest *request, PsychicResponse*
{
if (!timeSynced)
{
return buildConfirmHtml(request, resp, "NTP time not synced yet, TOTP not available, please wait for NTP to sync", 3, true);
return buildConfirmHtml(request, resp, "NTP time not synced yet, TOTP not available, please wait for NTP to sync or use <a href=\"/get?page=bypass\">one-time bypass</a>", 3, true);
}
if((pow(_importExport->_invalidCount, 5) + _importExport->_lastCodeCheck) > espMillis())
{
return buildConfirmHtml(request, resp, "Too many invalid TOTP tries, please wait before retrying", 3, true);
return buildConfirmHtml(request, resp, "Too many invalid TOTP tries, please wait before retrying or use <a href=\"/get?page=bypass\">one-time bypass</a>", 3, true);
}
PsychicStreamResponse response(resp, "text/html");
response.beginSend();
response.print("<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
@@ -1943,6 +2000,32 @@ esp_err_t WebCfgServer::buildTOTPHtml(PsychicRequest *request, PsychicResponse*
return response.endSend();
}
esp_err_t WebCfgServer::buildBypassHtml(PsychicRequest *request, PsychicResponse* resp)
{
if (timeSynced)
{
return buildConfirmHtml(request, resp, "One-time bypass is only available if NTP time is not synced</a>", 3, true);
}
if((pow(_importExport->_invalidCount2, 5) + _importExport->_lastCodeCheck2) > espMillis())
{
return buildConfirmHtml(request, resp, "Too many invalid bypass tries, please wait before retrying", 3, true);
}
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("</head><body><center><h2>Nuki Hub One-time Bypass</h2>");
response.print("<form action=\"/post?page=bypass\" method=\"post\">");
response.print("<div class=\"container\">");
response.print("<label for=\"bypass\"><b>Bypass code</b></label><input type=\"text\" placeholder=\"Enter bypass code\" name=\"bypass\">");
response.print("<button type=\"submit\" ");
response.print(">Login</button></div>");
response.print("</form></center></body></html>");
return response.endSend();
}
esp_err_t WebCfgServer::buildDuoCheckHtml(PsychicRequest *request, PsychicResponse* resp)
{
char valueStr[2];
@@ -1986,7 +2069,7 @@ esp_err_t WebCfgServer::buildDuoHtml(PsychicRequest *request, PsychicResponse* r
{
if (!timeSynced)
{
return buildConfirmHtml(request, resp, "NTP time not synced yet, Duo not available, please wait for NTP to sync", 3, true);
return buildConfirmHtml(request, resp, "NTP time not synced yet, Duo not available, please wait for NTP to sync or use <a href=\"/get?page=bypass\">one-time bypass</a>", 3, true);
}
String duoText;
@@ -2130,6 +2213,52 @@ bool WebCfgServer::processLogin(PsychicRequest *request, PsychicResponse* resp)
return false;
}
bool WebCfgServer::processBypass(PsychicRequest *request, PsychicResponse* resp)
{
if(!timeSynced && request->hasParam("bypass"))
{
const PsychicWebParameter* pass = request->getParam("bypass");
if(pass->value() != "")
{
String bypass = pass->value();
if (_importExport->checkBypass(bypass))
{
char buffer[33];
int i;
for (i = 0; i < 4; i++) {
sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random());
}
if (!_isSSL)
{
resp->setCookie("bypassId", buffer, 3600, "HttpOnly");
}
else
{
resp->setCookie("bypassId", buffer, 3600, "Secure; HttpOnly");
}
struct timeval time;
gettimeofday(&time, NULL);
int64_t time_us = (int64_t)time.tv_sec * 1000000L + (int64_t)time.tv_usec;
_importExport->_bypassSessions[buffer] = time_us + ((int64_t)3600*1000000L);
char randomstr2[33];
randomSeed(analogRead(0));
char chars[] = {'1', '2', '3','4', '5', '6','7', '8', '9', '0', 'A', 'B', 'C', 'D','E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O','P', 'Q','R', 'S', 'T','U', 'V', 'W','X', 'Y', 'Z'};
for(int i = 0;i < 32; i++){
randomstr2[i] = chars[random(36)];
}
randomstr2[32] = '\0';
_preferences->putString(preference_bypass_secret, randomstr2);
return true;
}
}
}
return false;
}
bool WebCfgServer::processTOTP(PsychicRequest *request, PsychicResponse* resp)
{
if(timeSynced && request->hasParam("totpkey"))
@@ -4151,6 +4280,19 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
}
}
}
else if(key == "CREDBYPASS")
{
if(value != "*")
{
if(_preferences->getString(preference_bypass_secret, "") != value)
{
_preferences->putString(preference_bypass_secret, value);
Log->print("Setting changed: ");
Log->println(key);
configChanged = true;
}
}
}
else if(key == "NUKIPIN" && _nuki != nullptr)
{
if(value == "#")
@@ -4708,6 +4850,13 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
randomstr[i] = chars[random(32)];
}
randomstr[16] = '\0';
char randomstr2[33];
randomSeed(analogRead(0));
char chars2[] = {'1', '2', '3','4', '5', '6','7', '8', '9', '0', 'A', 'B', 'C', 'D','E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O','P', 'Q','R', 'S', 'T','U', 'V', 'W','X', 'Y', 'Z'};
for(int i = 0;i < 32; i++){
randomstr2[i] = chars2[random(36)];
}
randomstr2[32] = '\0';
PsychicStreamResponse response(resp, "text/html");
response.beginSend();
@@ -4738,6 +4887,10 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
response.print("<tr id=\"totpgentr\" ><td><input type=\"button\" id=\"totpgen\" onclick=\"document.getElementsByName('CREDTOTP')[0].type='text'; document.getElementsByName('CREDTOTP')[0].value='");
response.print(randomstr);
response.print("'; document.getElementById('totpgentr').style.display='none';\" value=\"Generate new TOTP key\"></td></tr>");
printInputField(&response, "CREDBYPASS", "One-time MFA Bypass", "*", 32, "", true, false);
response.print("<tr id=\"bypassgentr\" ><td><input type=\"button\" id=\"bypassgen\" onclick=\"document.getElementsByName('CREDBYPASS')[0].type='text'; document.getElementsByName('CREDBYPASS')[0].value='");
response.print(randomstr2);
response.print("'; document.getElementById('bypassgentr').style.display='none';\" value=\"Generate new Bypass\"></td></tr>");
printInputField(&response, "CREDLFTM", "Session validity (in seconds)", _preferences->getInt(preference_cred_session_lifetime, 3600), 12, "");
printInputField(&response, "CREDLFTMRMBR", "Session validity remember (in hours)", _preferences->getInt(preference_cred_session_lifetime_remember, 720), 12, "");
printInputField(&response, "CREDDUOLFTM", "Duo Session validity (in seconds)", _preferences->getInt(preference_cred_session_lifetime_duo, 3600), 12, "");

View File

@@ -111,9 +111,11 @@ private:
bool isAuthenticated(PsychicRequest *request, int type = 0);
bool processLogin(PsychicRequest *request, PsychicResponse* resp);
bool processTOTP(PsychicRequest *request, PsychicResponse* resp);
bool processBypass(PsychicRequest *request, PsychicResponse* resp);
int doAuthentication(PsychicRequest *request);
esp_err_t buildCoredumpHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildLoginHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildBypassHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildTOTPHtml(PsychicRequest *request, PsychicResponse* resp, int type);
esp_err_t buildDuoHtml(PsychicRequest *request, PsychicResponse* resp, int type);
esp_err_t buildDuoCheckHtml(PsychicRequest *request, PsychicResponse* resp);
@@ -150,6 +152,7 @@ private:
JsonDocument _httpSessions;
bool _duoEnabled = false;
bool _bypassGPIO = false;
bool _newBypass = false;
int _bypassGPIOHigh = -1;
int _bypassGPIOLow = -1;
};

View File

@@ -546,7 +546,7 @@ void logCoreDump()
file.printf("%s\r\n", NUKI_HUB_HW);
file.printf("%s\r\n", NUKI_HUB_BUILD);
}
Serial.printf("%s\r\n", NUKI_HUB_HW);
Serial.printf("%s\r\n", NUKI_HUB_BUILD);
@@ -581,7 +581,7 @@ void logCoreDump()
file.printf("%s", str_dst);
}
}
Serial.println("");
if (file) {
@@ -669,7 +669,7 @@ void setup()
{
preferences->putString(preference_updater_date, NUKI_HUB_DATE);
}
importExport = new ImportExport(preferences);
network = new NukiNetwork(preferences);
@@ -796,7 +796,7 @@ void setup()
Log->print(gpioDesc.c_str());
const String mqttLockPath = preferences->getString(preference_mqtt_lock_path);
importExport = new ImportExport(preferences);
network = new NukiNetwork(preferences, gpio, mqttLockPath, CharBuffer::get(), buffer_size, importExport);