Bypass MFA

This commit is contained in:
iranl
2025-02-11 22:37:00 +01:00
parent 5d7b22448e
commit 6d14a1dcdd
10 changed files with 254 additions and 46 deletions

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);