Bypass MFA
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
#define NUKI_HUB_VERSION "9.09"
|
#define NUKI_HUB_VERSION "9.09"
|
||||||
#define NUKI_HUB_VERSION_INT (uint32_t)909
|
#define NUKI_HUB_VERSION_INT (uint32_t)909
|
||||||
#define NUKI_HUB_BUILD "unknownbuildnr"
|
#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_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"
|
#define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ void ImportExport::readSettings()
|
|||||||
|
|
||||||
_totpKey = _preferences->getString(preference_totp_secret, "");
|
_totpKey = _preferences->getString(preference_totp_secret, "");
|
||||||
_totpEnabled = _totpKey.length() > 0;
|
_totpEnabled = _totpKey.length() > 0;
|
||||||
|
_bypassKey = _preferences->getString(preference_bypass_secret, "");
|
||||||
|
_bypassEnabled = _bypassKey.length() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ImportExport::getDuoEnabled()
|
bool ImportExport::getDuoEnabled()
|
||||||
@@ -51,6 +53,11 @@ bool ImportExport::getTOTPEnabled()
|
|||||||
return _totpEnabled;
|
return _totpEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ImportExport::getBypassEnabled()
|
||||||
|
{
|
||||||
|
return _bypassEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool ImportExport::getBypassGPIOEnabled()
|
bool ImportExport::getBypassGPIOEnabled()
|
||||||
{
|
{
|
||||||
return _bypassGPIO;
|
return _bypassGPIO;
|
||||||
@@ -303,6 +310,30 @@ bool ImportExport::checkTOTP(String* totpKey)
|
|||||||
return false;
|
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)
|
void ImportExport::exportHttpsJson(JsonDocument &json)
|
||||||
{
|
{
|
||||||
if (!SPIFFS.begin(true)) {
|
if (!SPIFFS.begin(true)) {
|
||||||
@@ -442,6 +473,10 @@ void ImportExport::exportNukiHubJson(JsonDocument &json, bool redacted, bool pai
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if(strcmp(key, preference_bypass_secret) == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if(!redacted) if(std::find(redactedPrefs.begin(), redactedPrefs.end(), key) != redactedPrefs.end())
|
if(!redacted) if(std::find(redactedPrefs.begin(), redactedPrefs.end(), key) != redactedPrefs.end())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ public:
|
|||||||
int checkDuoApprove();
|
int checkDuoApprove();
|
||||||
bool startDuoAuth(char* pushType = (char*)"");
|
bool startDuoAuth(char* pushType = (char*)"");
|
||||||
bool getTOTPEnabled();
|
bool getTOTPEnabled();
|
||||||
|
bool getBypassEnabled();
|
||||||
bool checkTOTP(String* totpKey);
|
bool checkTOTP(String* totpKey);
|
||||||
|
bool checkBypass(String bypass);
|
||||||
bool getDuoEnabled();
|
bool getDuoEnabled();
|
||||||
bool getBypassGPIOEnabled();
|
bool getBypassGPIOEnabled();
|
||||||
int getBypassGPIOHigh();
|
int getBypassGPIOHigh();
|
||||||
@@ -27,13 +29,17 @@ public:
|
|||||||
JsonDocument _duoSessions;
|
JsonDocument _duoSessions;
|
||||||
JsonDocument _totpSessions;
|
JsonDocument _totpSessions;
|
||||||
JsonDocument _sessionsOpts;
|
JsonDocument _sessionsOpts;
|
||||||
|
JsonDocument _bypassSessions;
|
||||||
int64_t _lastCodeCheck = 0;
|
int64_t _lastCodeCheck = 0;
|
||||||
|
int64_t _lastCodeCheck2 = 0;
|
||||||
int _invalidCount = 0;
|
int _invalidCount = 0;
|
||||||
|
int _invalidCount2 = 0;
|
||||||
private:
|
private:
|
||||||
void saveSessions();
|
void saveSessions();
|
||||||
Preferences* _preferences;
|
Preferences* _preferences;
|
||||||
struct tm timeinfo;
|
struct tm timeinfo;
|
||||||
bool _totpEnabled = false;
|
bool _totpEnabled = false;
|
||||||
|
bool _bypassEnabled = false;
|
||||||
bool _duoActiveRequest;
|
bool _duoActiveRequest;
|
||||||
bool _duoEnabled = false;
|
bool _duoEnabled = false;
|
||||||
bool _bypassGPIO = false;
|
bool _bypassGPIO = false;
|
||||||
@@ -48,5 +54,6 @@ private:
|
|||||||
String _duoCheckId;
|
String _duoCheckId;
|
||||||
String _duoCheckIP;
|
String _duoCheckIP;
|
||||||
String _totpKey;
|
String _totpKey;
|
||||||
|
String _bypassKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -390,6 +390,11 @@ bool NukiNetwork::update()
|
|||||||
_importExport->_invalidCount--;
|
_importExport->_invalidCount--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(_importExport->getBypassEnabled() && _importExport->_invalidCount2 > 0 && (ts - (120000 * _importExport->_invalidCount2)) > _importExport->_lastCodeCheck2)
|
||||||
|
{
|
||||||
|
_importExport->_invalidCount2--;
|
||||||
|
}
|
||||||
|
|
||||||
if(disableNetwork || !_mqttEnabled || _device->isApOpen())
|
if(disableNetwork || !_mqttEnabled || _device->isApOpen())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -1114,15 +1119,19 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
|
|||||||
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true);
|
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true);
|
||||||
return;
|
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;
|
int duoResult = 2;
|
||||||
|
|
||||||
while (duoResult == 2)
|
if (duoRes)
|
||||||
{
|
{
|
||||||
duoResult = _importExport->checkDuoApprove();
|
while (duoResult == 2)
|
||||||
delay(2000);
|
{
|
||||||
esp_task_wdt_reset();
|
duoResult = _importExport->checkDuoApprove();
|
||||||
|
delay(2000);
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duoResult != 1)
|
if (duoResult != 1)
|
||||||
|
|||||||
@@ -96,6 +96,7 @@
|
|||||||
#define preference_publish_config (char*)"nhPubConfig"
|
#define preference_publish_config (char*)"nhPubConfig"
|
||||||
#define preference_config_from_mqtt (char*)"nhCntrlEnabled"
|
#define preference_config_from_mqtt (char*)"nhCntrlEnabled"
|
||||||
#define preference_totp_secret (char*)"totpsecret"
|
#define preference_totp_secret (char*)"totpsecret"
|
||||||
|
#define preference_bypass_secret (char*)"bypassecret"
|
||||||
|
|
||||||
// CHANGE DOES NOT REQUIRE REBOOT TO TAKE EFFECT
|
// CHANGE DOES NOT REQUIRE REBOOT TO TAKE EFFECT
|
||||||
#define preference_find_best_rssi (char*)"nwbestrssi"
|
#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_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_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_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 =
|
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_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_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 =
|
std::vector<char*> _boolPrefs =
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,18 +102,22 @@ bool WebCfgServer::isAuthenticated(PsychicRequest *request, int type)
|
|||||||
{
|
{
|
||||||
cookieKey = "totpId";
|
cookieKey = "totpId";
|
||||||
}
|
}
|
||||||
|
else if (type == 3)
|
||||||
|
{
|
||||||
|
cookieKey = "bypassId";
|
||||||
|
}
|
||||||
|
|
||||||
if (request->hasCookie(cookieKey.c_str()))
|
if (request->hasCookie(cookieKey.c_str()))
|
||||||
{
|
{
|
||||||
String cookie = request->getCookie(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;
|
struct timeval time;
|
||||||
gettimeofday(&time, NULL);
|
gettimeofday(&time, NULL);
|
||||||
int64_t time_us = (int64_t)time.tv_sec * 1000000L + (int64_t)time.tv_usec;
|
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;
|
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);
|
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;
|
_importExport->_sessionsOpts[request->client()->localIP().toString() + "totp"] = true;
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
else if(!timeSynced && _importExport->getBypassEnabled() && isAuthenticated(request, 3))
|
||||||
|
{
|
||||||
|
_importExport->_sessionsOpts[request->client()->localIP().toString() + "totp"] = false;
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
Log->println("Authentication Failed");
|
Log->println("Authentication Failed");
|
||||||
|
|
||||||
@@ -507,7 +533,7 @@ void WebCfgServer::initialize()
|
|||||||
}
|
}
|
||||||
int authReq = doAuthentication(request);
|
int authReq = doAuthentication(request);
|
||||||
|
|
||||||
if (value != "status" && value != "login" && value != "duocheck")
|
if (value != "status" && value != "login" && value != "duocheck" && value != "bypass")
|
||||||
{
|
{
|
||||||
switch (authReq)
|
switch (authReq)
|
||||||
{
|
{
|
||||||
@@ -559,6 +585,15 @@ void WebCfgServer::initialize()
|
|||||||
{
|
{
|
||||||
return buildTOTPHtml(request, resp, 0);
|
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")
|
else if (value == "logout")
|
||||||
{
|
{
|
||||||
return logoutSession(request, resp);
|
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);
|
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)
|
if (!approved)
|
||||||
{
|
{
|
||||||
@@ -926,6 +966,23 @@ void WebCfgServer::initialize()
|
|||||||
return resp->redirect("/get?page=totp");
|
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
|
#ifndef NUKI_HUB_UPDATER
|
||||||
else if (value == "savecfg")
|
else if (value == "savecfg")
|
||||||
{
|
{
|
||||||
@@ -1882,12 +1939,12 @@ esp_err_t WebCfgServer::buildTOTPHtml(PsychicRequest *request, PsychicResponse*
|
|||||||
{
|
{
|
||||||
if (!timeSynced)
|
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())
|
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");
|
PsychicStreamResponse response(resp, "text/html");
|
||||||
@@ -1943,6 +2000,32 @@ esp_err_t WebCfgServer::buildTOTPHtml(PsychicRequest *request, PsychicResponse*
|
|||||||
return response.endSend();
|
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)
|
esp_err_t WebCfgServer::buildDuoCheckHtml(PsychicRequest *request, PsychicResponse* resp)
|
||||||
{
|
{
|
||||||
char valueStr[2];
|
char valueStr[2];
|
||||||
@@ -1986,7 +2069,7 @@ esp_err_t WebCfgServer::buildDuoHtml(PsychicRequest *request, PsychicResponse* r
|
|||||||
{
|
{
|
||||||
if (!timeSynced)
|
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;
|
String duoText;
|
||||||
@@ -2130,6 +2213,52 @@ bool WebCfgServer::processLogin(PsychicRequest *request, PsychicResponse* resp)
|
|||||||
return false;
|
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)
|
bool WebCfgServer::processTOTP(PsychicRequest *request, PsychicResponse* resp)
|
||||||
{
|
{
|
||||||
if(timeSynced && request->hasParam("totpkey"))
|
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)
|
else if(key == "NUKIPIN" && _nuki != nullptr)
|
||||||
{
|
{
|
||||||
if(value == "#")
|
if(value == "#")
|
||||||
@@ -4708,6 +4850,13 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
|
|||||||
randomstr[i] = chars[random(32)];
|
randomstr[i] = chars[random(32)];
|
||||||
}
|
}
|
||||||
randomstr[16] = '\0';
|
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");
|
PsychicStreamResponse response(resp, "text/html");
|
||||||
response.beginSend();
|
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("<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(randomstr);
|
||||||
response.print("'; document.getElementById('totpgentr').style.display='none';\" value=\"Generate new TOTP key\"></td></tr>");
|
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, "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, "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, "");
|
printInputField(&response, "CREDDUOLFTM", "Duo Session validity (in seconds)", _preferences->getInt(preference_cred_session_lifetime_duo, 3600), 12, "");
|
||||||
|
|||||||
@@ -111,9 +111,11 @@ private:
|
|||||||
bool isAuthenticated(PsychicRequest *request, int type = 0);
|
bool isAuthenticated(PsychicRequest *request, int type = 0);
|
||||||
bool processLogin(PsychicRequest *request, PsychicResponse* resp);
|
bool processLogin(PsychicRequest *request, PsychicResponse* resp);
|
||||||
bool processTOTP(PsychicRequest *request, PsychicResponse* resp);
|
bool processTOTP(PsychicRequest *request, PsychicResponse* resp);
|
||||||
|
bool processBypass(PsychicRequest *request, PsychicResponse* resp);
|
||||||
int doAuthentication(PsychicRequest *request);
|
int doAuthentication(PsychicRequest *request);
|
||||||
esp_err_t buildCoredumpHtml(PsychicRequest *request, PsychicResponse* resp);
|
esp_err_t buildCoredumpHtml(PsychicRequest *request, PsychicResponse* resp);
|
||||||
esp_err_t buildLoginHtml(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 buildTOTPHtml(PsychicRequest *request, PsychicResponse* resp, int type);
|
||||||
esp_err_t buildDuoHtml(PsychicRequest *request, PsychicResponse* resp, int type);
|
esp_err_t buildDuoHtml(PsychicRequest *request, PsychicResponse* resp, int type);
|
||||||
esp_err_t buildDuoCheckHtml(PsychicRequest *request, PsychicResponse* resp);
|
esp_err_t buildDuoCheckHtml(PsychicRequest *request, PsychicResponse* resp);
|
||||||
@@ -150,6 +152,7 @@ private:
|
|||||||
JsonDocument _httpSessions;
|
JsonDocument _httpSessions;
|
||||||
bool _duoEnabled = false;
|
bool _duoEnabled = false;
|
||||||
bool _bypassGPIO = false;
|
bool _bypassGPIO = false;
|
||||||
|
bool _newBypass = false;
|
||||||
int _bypassGPIOHigh = -1;
|
int _bypassGPIOHigh = -1;
|
||||||
int _bypassGPIOLow = -1;
|
int _bypassGPIOLow = -1;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user