From 9074ff702d4dfcddcde49d38eb57eb4ad9ac3870 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Tue, 20 Feb 2024 00:53:49 +0000 Subject: [PATCH] core/input: add feedback for password verification --- src/core/Password.cpp | 55 +++++++++++---- src/core/Password.hpp | 8 ++- src/core/hyprlock.cpp | 30 +++++--- src/core/hyprlock.hpp | 9 ++- src/renderer/widgets/PasswordInputField.cpp | 77 +++++++++++++++++---- src/renderer/widgets/PasswordInputField.hpp | 5 ++ 6 files changed, 144 insertions(+), 40 deletions(-) diff --git a/src/core/Password.cpp b/src/core/Password.cpp index ee749be..a777fdf 100644 --- a/src/core/Password.cpp +++ b/src/core/Password.cpp @@ -1,9 +1,12 @@ #include "Password.hpp" +#include "hyprlock.hpp" #include #include #include +#include + struct pam_response* reply; // @@ -12,25 +15,49 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp return PAM_SUCCESS; } -CPassword::SVerificationResult CPassword::verify(const std::string& pass) { - const pam_conv localConv = {conv, NULL}; - pam_handle_t* handle = NULL; +static void passwordCheckTimerCallback(std::shared_ptr self, void* data) { + g_pHyprlock->onPasswordCheckTimer(); +} - int ret = pam_start("su", getlogin(), &localConv, &handle); +std::shared_ptr CPassword::verify(const std::string& pass) { - if (ret != PAM_SUCCESS) - return {false, "pam_start failed"}; + std::shared_ptr result = std::make_shared(false); - reply = (struct pam_response*)malloc(sizeof(struct pam_response)); + std::thread([this, result, pass]() { + const pam_conv localConv = {conv, NULL}; + pam_handle_t* handle = NULL; - reply->resp = strdup(pass.c_str()); - reply->resp_retcode = 0; - ret = pam_authenticate(handle, 0); + int ret = pam_start("su", getlogin(), &localConv, &handle); - if (ret != PAM_SUCCESS) - return {false, ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"}; + if (ret != PAM_SUCCESS) { + result->success = false; + result->failReason = "pam_start failed"; + result->realized = true; + g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); + return; + } - ret = pam_end(handle, ret); + reply = (struct pam_response*)malloc(sizeof(struct pam_response)); - return {true, "Successfully authenticated"}; + reply->resp = strdup(pass.c_str()); + reply->resp_retcode = 0; + ret = pam_authenticate(handle, 0); + + if (ret != PAM_SUCCESS) { + result->success = false; + result->failReason = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; + result->realized = true; + g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); + return; + } + + ret = pam_end(handle, ret); + + result->success = true; + result->failReason = "Successfully authenticated"; + result->realized = true; + g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); + }).detach(); + + return result; } \ No newline at end of file diff --git a/src/core/Password.hpp b/src/core/Password.hpp index 951c180..74fcbac 100644 --- a/src/core/Password.hpp +++ b/src/core/Password.hpp @@ -2,15 +2,17 @@ #include #include +#include class CPassword { public: struct SVerificationResult { - bool success = false; - std::string failReason = ""; + std::atomic realized = false; + bool success = false; + std::string failReason = ""; }; - SVerificationResult verify(const std::string& pass); + std::shared_ptr verify(const std::string& pass); }; inline std::unique_ptr g_pPassword = std::make_unique(); \ No newline at end of file diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 2aaf297..c4ffd85 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -405,6 +405,27 @@ static const ext_session_lock_v1_listener sessionLockListener = { // end session_lock +void CHyprlock::onPasswordCheckTimer() { + // check result + if (m_sPasswordState.result->success) { + unlockSession(); + } else { + Debug::log(LOG, "Authentication failed: {}", m_sPasswordState.result->failReason); + m_sPasswordState.lastFailReason = m_sPasswordState.result->failReason; + m_sPasswordState.passBuffer = ""; + } + + m_sPasswordState.result.reset(); +} + +bool CHyprlock::passwordCheckWaiting() { + return m_sPasswordState.result.get(); +} + +std::optional CHyprlock::passwordLastFailReason() { + return m_sPasswordState.lastFailReason; +} + void CHyprlock::onKey(uint32_t key) { const auto SYM = xkb_state_key_get_one_sym(m_pXKBState, key + 8); @@ -414,14 +435,7 @@ void CHyprlock::onKey(uint32_t key) { } else if (SYM == XKB_KEY_Return) { Debug::log(LOG, "Authenticating"); - const auto RESULT = g_pPassword->verify(m_sPasswordState.passBuffer); - - Debug::log(LOG, "Password auth result: {}", RESULT.failReason); - - if (RESULT.success) - unlockSession(); - - m_sPasswordState.passBuffer = ""; + m_sPasswordState.result = g_pPassword->verify(m_sPasswordState.passBuffer); } else { char buf[16] = {0}; int len = xkb_keysym_to_utf8(SYM, buf, 16); diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 3295260..88a53ec 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -7,10 +7,12 @@ #include "Output.hpp" #include "CursorShape.hpp" #include "Timer.hpp" +#include "Password.hpp" #include #include #include +#include #include @@ -32,6 +34,9 @@ class CHyprlock { void unlockSession(); void onKey(uint32_t key); + void onPasswordCheckTimer(); + bool passwordCheckWaiting(); + std::optional passwordLastFailReason(); size_t getPasswordBufferLen(); @@ -69,7 +74,9 @@ class CHyprlock { } m_sLockState; struct { - std::string passBuffer = ""; + std::string passBuffer = ""; + std::shared_ptr result; + std::optional lastFailReason; } m_sPasswordState; struct { diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 63c4e27..ab78d06 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -23,7 +23,7 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport, const std::un request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = std::string{"Sans"}; request.props["color"] = CColor{1.0 - font.r, 1.0 - font.g, 1.0 - font.b, 0.5}; - request.props["font_size"] = 12; + request.props["font_size"] = (int)size.y / 4; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); } } @@ -86,13 +86,20 @@ bool CPasswordInputField::draw(const SRenderData& data) { CBox inputFieldBox = {pos, size}; CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}}; + bool forceReload = false; + updateFade(); updateDots(); + updateFailTex(); + + float passAlpha = g_pHyprlock->passwordCheckWaiting() ? 0.5 : 1.0; CColor outerCol = outer; outer.a = fade.a * data.opacity; CColor innerCol = inner; innerCol.a = fade.a * data.opacity; + CColor fontCol = font; + fontCol.a *= fade.a * data.opacity * passAlpha; g_pRenderer->renderRect(outerBox, outerCol, outerBox.h / 2.0); g_pRenderer->renderRect(inputFieldBox, innerCol, inputFieldBox.h / 2.0); @@ -103,32 +110,74 @@ bool CPasswordInputField::draw(const SRenderData& data) { for (size_t i = 0; i < std::floor(dots.currentAmount); ++i) { Vector2D currentPos = inputFieldBox.pos() + Vector2D{PASS_SPACING * 2, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * i, 0}; CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}}; - CColor fontCol = font; g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0); } if (dots.currentAmount != std::floor(dots.currentAmount)) { Vector2D currentPos = inputFieldBox.pos() + Vector2D{PASS_SPACING * 2, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * std::floor(dots.currentAmount), 0}; - CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}}; - CColor fontCol = font; - fontCol.a = (dots.currentAmount - std::floor(dots.currentAmount)) * data.opacity; + CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}}; + fontCol.a = (dots.currentAmount - std::floor(dots.currentAmount)) * data.opacity; g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0); } const auto PASSLEN = g_pHyprlock->getPasswordBufferLen(); if (PASSLEN == 0 && !placeholder.resourceID.empty()) { - if (!placeholder.asset) - placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID); + SPreloadedAsset* currAsset = nullptr; - if (placeholder.asset) { - Vector2D pos = outerBox.pos() + outerBox.size() / 2.f; - pos = pos - placeholder.asset->texture.m_vSize / 2.f; - CBox textbox{pos, placeholder.asset->texture.m_vSize}; - g_pRenderer->renderTexture(textbox, placeholder.asset->texture, data.opacity * fade.a, 0); + if (!placeholder.failID.empty()) { + if (!placeholder.failAsset) + placeholder.failAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.failID); + + currAsset = placeholder.failAsset; + } else { + if (!placeholder.asset) + placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID); + + currAsset = placeholder.asset; } + + if (currAsset) { + Vector2D pos = outerBox.pos() + outerBox.size() / 2.f; + pos = pos - currAsset->texture.m_vSize / 2.f; + CBox textbox{pos, currAsset->texture.m_vSize}; + g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a, 0); + } else + forceReload = true; } - return dots.currentAmount != PASSLEN || data.opacity < 1.0 || fade.a < 1.0; -} \ No newline at end of file + return dots.currentAmount != PASSLEN || data.opacity < 1.0 || fade.a < 1.0 || forceReload; +} + +void CPasswordInputField::updateFailTex() { + const auto FAIL = g_pHyprlock->passwordLastFailReason(); + + if (g_pHyprlock->passwordCheckWaiting()) + placeholder.canGetNewFail = true; + + if (g_pHyprlock->getPasswordBufferLen() != 0) { + if (placeholder.failAsset) { + g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.failAsset); + placeholder.failAsset = nullptr; + placeholder.failID = ""; + } + return; + } + + if (!FAIL.has_value() || !placeholder.canGetNewFail) + return; + + // query + CAsyncResourceGatherer::SPreloadRequest request; + request.id = "input-error:" + std::to_string((uintptr_t)this) + ",time:" + std::to_string(time(nullptr)); + placeholder.failID = request.id; + request.asset = "" + FAIL.value() + ""; + request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; + request.props["font_family"] = std::string{"Sans"}; + request.props["color"] = CColor{1.0 - font.r, 1.0 - font.g, 1.0 - font.b, 0.5}; + request.props["font_size"] = (int)size.y / 4; + g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); + + placeholder.canGetNewFail = false; +} diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index 77d1ff2..2fa51ee 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -19,6 +19,7 @@ class CPasswordInputField : public IWidget { private: void updateDots(); void updateFade(); + void updateFailTex(); Vector2D size; Vector2D pos; @@ -43,6 +44,10 @@ class CPasswordInputField : public IWidget { struct { std::string resourceID = ""; SPreloadedAsset* asset = nullptr; + + std::string failID = ""; + SPreloadedAsset* failAsset = nullptr; + bool canGetNewFail = true; } placeholder; bool fadeOnEmpty;