From 8ed9e6dcf4d6f233136a758209fd9067e0d32265 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Mon, 18 Mar 2024 23:17:27 +0100 Subject: [PATCH 01/17] auth: implement a full pam conversation --- src/config/ConfigManager.cpp | 1 + src/core/Auth.cpp | 151 ++++++++++++++++++++ src/core/Auth.hpp | 52 +++++++ src/core/Password.cpp | 78 ---------- src/core/Password.hpp | 18 --- src/core/hyprlock.cpp | 34 ++--- src/core/hyprlock.hpp | 8 +- src/renderer/widgets/IWidget.cpp | 7 +- src/renderer/widgets/PasswordInputField.cpp | 112 ++++++++------- src/renderer/widgets/PasswordInputField.hpp | 20 +-- 10 files changed, 301 insertions(+), 180 deletions(-) create mode 100644 src/core/Auth.cpp create mode 100644 src/core/Auth.hpp delete mode 100644 src/core/Password.cpp delete mode 100644 src/core/Password.hpp diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 5fddf1cd..f444b760 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -49,6 +49,7 @@ void CConfigManager::init() { m_config.addConfigValue("general:no_fade_in", Hyprlang::INT{0}); m_config.addConfigValue("general:no_fade_out", Hyprlang::INT{0}); m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0}); + m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"su"}); m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""}); diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp new file mode 100644 index 00000000..e6b313fa --- /dev/null +++ b/src/core/Auth.cpp @@ -0,0 +1,151 @@ +#include "Auth.hpp" +#include "hyprlock.hpp" +#include "../helpers/Log.hpp" +#include "src/config/ConfigManager.hpp" + +#include +#include +#include +#if __has_include() +#include +#endif + +#include +#include + +int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { + const auto VERIFICATIONSTATE = (CAuth::SPamConversationState*)appdata_ptr; + struct pam_response* pam_reply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response)); + + for (int i = 0; i < num_msg; ++i) { + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: { + g_pAuth->setPrompt(msg[i]->msg); + // Some pam configurations ask for the password twice for whatever reason (Fedora su for example) + // When the prompt is the same as the last one, I guess our answer can be the same. + Debug::log(LOG, "PAM_PROMPT: {}", msg[i]->msg); + if (VERIFICATIONSTATE->prompt != VERIFICATIONSTATE->lastPrompt) + g_pAuth->waitForInput(); + + if (g_pHyprlock->m_bTerminate) + return PAM_CONV_ERR; + + pam_reply[i].resp = strdup(VERIFICATIONSTATE->input.c_str()); + break; + } + case PAM_ERROR_MSG: Debug::log(ERR, "PAM: {}", msg[i]->msg); break; + case PAM_TEXT_INFO: Debug::log(LOG, "PAM: {}", msg[i]->msg); break; + } + } + + VERIFICATIONSTATE->waitingForPamAuth = true; + + *resp = pam_reply; + return PAM_SUCCESS; +} + +static void passwordCheckTimerCallback(std::shared_ptr self, void* data) { + g_pHyprlock->onPasswordCheckTimer(); +} + +void CAuth::start() { + m_authThread = std::thread([this]() { + static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:pam_module")); + while (!g_pHyprlock->m_bTerminate) { + bool success = auth(*PPAMMODULE); + g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); + if (success) + break; + + std::this_thread::sleep_for(std::chrono::seconds(1)); // TODO: Make sure that the callback succeeded + resetConversation(); + } + }); + + m_authThread.detach(); +} + +bool CAuth::auth(std::string pam_module) { + const pam_conv localConv = {conv, (void*)&m_sConversationState}; + pam_handle_t* handle = NULL; + auto uidPassword = getpwuid(getuid()); + + int ret = pam_start(pam_module.c_str(), uidPassword->pw_name, &localConv, &handle); + + if (ret != PAM_SUCCESS) { + m_sConversationState.success = false; + m_sConversationState.failReason = "pam_start failed"; + Debug::log(ERR, "auth: pam_start failed for {}", pam_module); + return false; + } + + ret = pam_authenticate(handle, 0); + + m_sConversationState.waitingForPamAuth = false; + + if (ret != PAM_SUCCESS) { + m_sConversationState.success = false; + m_sConversationState.failReason = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; + Debug::log(ERR, "auth: {} for {}", m_sConversationState.failReason, pam_module); + return false; + } + + ret = pam_end(handle, ret); + + m_sConversationState.success = true; + m_sConversationState.failReason = "Successfully authenticated"; + Debug::log(LOG, "auth: authenticated for {}", pam_module); + + return true; +} + +bool CAuth::didAuthSucceed() { + return m_sConversationState.success; +} + +void CAuth::waitForInput() { + std::unique_lock lk(m_sConversationState.inputMutex); + m_sConversationState.inputSubmitted = false; + m_sConversationState.inputSubmittedCondition.wait(lk, [this] { return m_sConversationState.inputSubmitted || g_pHyprlock->m_bTerminate; }); +} + +void CAuth::submitInput(const std::string& input) { + std::unique_lock lock(m_sConversationState.inputMutex); + m_sConversationState.input = input; + m_sConversationState.inputSubmitted = true; + m_sConversationState.inputSubmittedCondition.notify_all(); +} + +std::optional CAuth::getFeedback() { + if (!m_sConversationState.inputSubmitted) { + return SFeedback{m_sConversationState.prompt, false}; + } else if (!m_sConversationState.failReason.empty()) { + return SFeedback{m_sConversationState.failReason, true}; + } + + return std::nullopt; +} + +void CAuth::setPrompt(const char* prompt) { + m_sConversationState.lastPrompt = m_sConversationState.prompt; + m_sConversationState.prompt = prompt; +} + +bool CAuth::checkWaiting() { + return m_sConversationState.waitingForPamAuth; +} + +void CAuth::terminate() { + m_sConversationState.inputSubmittedCondition.notify_all(); +} + +void CAuth::resetConversation() { + m_sConversationState.input = ""; + m_sConversationState.prompt = ""; + m_sConversationState.lastPrompt = ""; + m_sConversationState.failReason = ""; + m_sConversationState.inputSubmitted = false; + m_sConversationState.waitingForPamAuth = false; + m_sConversationState.success = false; +} diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp new file mode 100644 index 00000000..eaaf3324 --- /dev/null +++ b/src/core/Auth.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class CAuth { + public: + struct SPamConversationState { + std::string input = ""; + std::string prompt = ""; + std::string lastPrompt = ""; + std::string failReason = ""; + + std::mutex inputMutex; + std::condition_variable inputSubmittedCondition; + + bool waitingForPamAuth = false; + bool inputSubmitted = false; + bool success = false; + }; + + struct SFeedback { + std::string text; + bool isFail = false; + }; + + void start(); + bool auth(std::string pam_module); + bool didAuthSucceed(); + + void waitForInput(); + void submitInput(const std::string& input); + + void setPrompt(const char* prompt); + std::optional getFeedback(); + + bool checkWaiting(); + + void terminate(); + + private: + SPamConversationState m_sConversationState; + std::thread m_authThread; + + void resetConversation(); +}; + +inline std::unique_ptr g_pAuth = std::make_unique(); diff --git a/src/core/Password.cpp b/src/core/Password.cpp deleted file mode 100644 index f1c3653a..00000000 --- a/src/core/Password.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "Password.hpp" -#include "hyprlock.hpp" -#include "../helpers/Log.hpp" - -#include -#include -#include -#if __has_include() -#include -#endif - -#include -#include - -// -int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { - const char* pass = static_cast(appdata_ptr); - struct pam_response* pam_reply = static_cast(calloc(num_msg, sizeof(struct pam_response))); - - for (int i = 0; i < num_msg; ++i) { - switch (msg[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: - case PAM_PROMPT_ECHO_ON: pam_reply[i].resp = strdup(pass); break; - case PAM_ERROR_MSG: Debug::log(ERR, "PAM: {}", msg[i]->msg); break; - case PAM_TEXT_INFO: Debug::log(LOG, "PAM: {}", msg[i]->msg); break; - } - } - *resp = pam_reply; - return PAM_SUCCESS; -} - -static void passwordCheckTimerCallback(std::shared_ptr self, void* data) { - g_pHyprlock->onPasswordCheckTimer(); -} - -std::shared_ptr CPassword::verify(const std::string& pass) { - - std::shared_ptr result = std::make_shared(false); - - std::thread([this, result, pass]() { - auto auth = [&](std::string auth) -> bool { - const pam_conv localConv = {conv, (void*)pass.c_str()}; - pam_handle_t* handle = NULL; - auto uidPassword = getpwuid(getuid()); - - int ret = pam_start(auth.c_str(), uidPassword->pw_name, &localConv, &handle); - - if (ret != PAM_SUCCESS) { - result->success = false; - result->failReason = "pam_start failed"; - Debug::log(ERR, "auth: pam_start failed for {}", auth); - return false; - } - - ret = pam_authenticate(handle, 0); - - if (ret != PAM_SUCCESS) { - result->success = false; - result->failReason = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; - Debug::log(ERR, "auth: {} for {}", result->failReason, auth); - return false; - } - - ret = pam_end(handle, ret); - - result->success = true; - result->failReason = "Successfully authenticated"; - Debug::log(LOG, "auth: authenticated for {}", auth); - - return true; - }; - - result->realized = auth("hyprlock") || auth("su") || true; - g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); - }).detach(); - - return result; -} diff --git a/src/core/Password.hpp b/src/core/Password.hpp deleted file mode 100644 index 74fcbacb..00000000 --- a/src/core/Password.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include -#include - -class CPassword { - public: - struct SVerificationResult { - std::atomic realized = false; - bool success = false; - std::string failReason = ""; - }; - - 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 c029ddb8..494c806e 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -2,7 +2,7 @@ #include "../helpers/Log.hpp" #include "../config/ConfigManager.hpp" #include "../renderer/Renderer.hpp" -#include "Password.hpp" +#include "Auth.hpp" #include "Egl.hpp" #include @@ -452,6 +452,8 @@ void CHyprlock::run() { m_sLoopState.event = true; // let it process once + g_pAuth->start(); + while (1) { std::unique_lock lk(m_sLoopState.eventRequestMutex); if (m_sLoopState.event == false) @@ -527,6 +529,8 @@ void CHyprlock::run() { pthread_kill(pollThr.native_handle(), SIGRTMIN); + g_pAuth->terminate(); + // wait for threads to exit cleanly to avoid a coredump pollThr.join(); timersThr.join(); @@ -733,12 +737,11 @@ static const ext_session_lock_v1_listener sessionLockListener = { void CHyprlock::onPasswordCheckTimer() { // check result - if (m_sPasswordState.result->success) { + if (g_pAuth->didAuthSucceed()) { unlock(); } else { - Debug::log(LOG, "Authentication failed: {}", m_sPasswordState.result->failReason); - m_sPasswordState.lastFailReason = m_sPasswordState.result->failReason; - m_sPasswordState.passBuffer = ""; + Debug::log(LOG, "Authentication failed: {}", (g_pAuth->getFeedback().has_value()) ? g_pAuth->getFeedback().value().text : "unknown reason"); + m_sPasswordState.passBuffer = ""; m_sPasswordState.failedAttempts += 1; Debug::log(LOG, "Failed attempts: {}", m_sPasswordState.failedAttempts); forceUpdateTimers(); @@ -747,16 +750,6 @@ void CHyprlock::onPasswordCheckTimer() { o->sessionLockSurface->render(); } } - - m_sPasswordState.result.reset(); -} - -bool CHyprlock::passwordCheckWaiting() { - return m_sPasswordState.result.get(); -} - -std::optional CHyprlock::passwordLastFailReason() { - return m_sPasswordState.lastFailReason; } void CHyprlock::renderOutput(const std::string& stringPort) { @@ -792,7 +785,7 @@ void CHyprlock::onKey(uint32_t key, bool down) { else std::erase(m_vPressedKeys, key); - if (m_sPasswordState.result) { + if (g_pAuth->checkWaiting()) { for (auto& o : m_vOutputs) { o->sessionLockSurface->render(); } @@ -820,7 +813,7 @@ void CHyprlock::onKey(uint32_t key, bool down) { return; } - m_sPasswordState.result = g_pPassword->verify(m_sPasswordState.passBuffer); + g_pAuth->submitInput(m_sPasswordState.passBuffer); } else if (SYM == XKB_KEY_BackSpace) { if (m_sPasswordState.passBuffer.length() > 0) { // handle utf-8 @@ -828,6 +821,13 @@ void CHyprlock::onKey(uint32_t key, bool down) { m_sPasswordState.passBuffer.pop_back(); m_sPasswordState.passBuffer = m_sPasswordState.passBuffer.substr(0, m_sPasswordState.passBuffer.length() - 1); } + } else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) { + Debug::log(LOG, "Authenticating"); + g_pAuth->submitInput(m_sPasswordState.passBuffer); + } else if (SYM == XKB_KEY_Escape) { + Debug::log(LOG, "Clearing password buffer"); + + m_sPasswordState.passBuffer = ""; } else if (SYM == XKB_KEY_Caps_Lock) { m_bCapsLock = !m_bCapsLock; } else if (SYM == XKB_KEY_Num_Lock) { diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index cb7c92cf..293453ce 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -9,7 +9,7 @@ #include "Output.hpp" #include "CursorShape.hpp" #include "Timer.hpp" -#include "Password.hpp" +#include "Auth.hpp" #include #include @@ -129,10 +129,8 @@ class CHyprlock { } m_sLockState; struct { - std::string passBuffer = ""; - std::shared_ptr result; - std::optional lastFailReason; - size_t failedAttempts = 0; + std::string passBuffer = ""; + size_t failedAttempts = 0; } m_sPasswordState; struct { diff --git a/src/renderer/widgets/IWidget.cpp b/src/renderer/widgets/IWidget.cpp index 07bdea19..76f55228 100644 --- a/src/renderer/widgets/IWidget.cpp +++ b/src/renderer/widgets/IWidget.cpp @@ -119,9 +119,10 @@ IWidget::SFormatResult IWidget::formatString(std::string in) { result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000; } - if (in.contains("$FAIL")) { - const auto FAIL = g_pHyprlock->passwordLastFailReason(); - replaceAll(in, "$FAIL", FAIL.has_value() ? FAIL.value() : ""); + if (in.contains("$FAIL") || in.contains("$PROMPT")) { + const auto AUTHFEEDBACKOPT = g_pAuth->getFeedback(); + replaceAll(in, "$FAIL", (AUTHFEEDBACKOPT.has_value() && AUTHFEEDBACKOPT->isFail) ? AUTHFEEDBACKOPT->text : ""); + replaceAll(in, "$PROMPT", (AUTHFEEDBACKOPT.has_value() && !AUTHFEEDBACKOPT->isFail) ? AUTHFEEDBACKOPT->text : ""); result.allowForceUpdate = true; } diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index d15ba955..9b85306e 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -3,6 +3,16 @@ #include "../../core/hyprlock.hpp" #include +static void replaceAll(std::string& str, const std::string& from, const std::string& to) { + if (from.empty()) + return; + size_t pos = 0; + while ((pos = str.find(from, pos)) != std::string::npos) { + str.replace(pos, from.length(), to); + pos += to.length(); + } +} + CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::unordered_map& props, const std::string& output) : outputStringPort(output), shadow(this, props, viewport_) { size = std::any_cast(props.at("size")); @@ -15,6 +25,7 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u fadeTimeoutMs = std::any_cast(props.at("fade_timeout")); hiddenInputState.enabled = std::any_cast(props.at("hide_input")); rounding = std::any_cast(props.at("rounding")); + configPlaceholderText = std::any_cast(props.at("placeholder_text")); configFailText = std::any_cast(props.at("fail_text")); col.transitionMs = std::any_cast(props.at("fail_transition")); col.outer = std::any_cast(props.at("outer_color")); @@ -48,15 +59,17 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u g_pHyprlock->m_bNumLock = col.invertNum; - std::string placeholderText = std::any_cast(props.at("placeholder_text")); - // Render placeholder if either placeholder_text or fail_text are non-empty // as placeholder must be rendered to show fail_text - if (!placeholderText.empty() || !configFailText.empty()) { - placeholder.resourceID = "placeholder:" + std::to_string((uintptr_t)this); + if (!configPlaceholderText.empty() || !configFailText.empty()) { + placeholder.currentText = configPlaceholderText; + + replaceAll(placeholder.currentText, "$PROMPT", ""); + + placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this); CAsyncResourceGatherer::SPreloadRequest request; request.id = placeholder.resourceID; - request.asset = placeholderText; + request.asset = placeholder.currentText; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = std::string{"Sans"}; request.props["color"] = CColor{1.0 - col.font.r, 1.0 - col.font.g, 1.0 - col.font.b, 0.5}; @@ -65,16 +78,6 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u } } -static void replaceAllFail(std::string& str, const std::string& from, const std::string& to) { - if (from.empty()) - return; - size_t pos = 0; - while ((pos = str.find(from, pos)) != std::string::npos) { - str.replace(pos, from.length(), to); - pos += to.length(); - } -} - static void fadeOutCallback(std::shared_ptr self, void* data) { CPasswordInputField* p = (CPasswordInputField*)data; @@ -176,18 +179,18 @@ bool CPasswordInputField::draw(const SRenderData& data) { failedAttempts = g_pHyprlock->getPasswordFailedAttempts(); passwordLength = g_pHyprlock->getPasswordBufferDisplayLen(); - checkWaiting = g_pHyprlock->passwordCheckWaiting(); + checkWaiting = g_pAuth->checkWaiting(); updateFade(); updateDots(); - updateFailTex(); + updatePlaceholder(); updateColors(); updateHiddenInputState(); static auto TIMER = std::chrono::system_clock::now(); - if (placeholder.failAsset) { - const auto TARGETSIZEX = placeholder.failAsset->texture.m_vSize.x + inputFieldBox.h; + if (placeholder.asset) { + const auto TARGETSIZEX = placeholder.asset->texture.m_vSize.x + inputFieldBox.h; if (size.x < TARGETSIZEX) { const auto DELTA = std::clamp((int)std::chrono::duration_cast(std::chrono::system_clock::now() - TIMER).count(), 8000, 20000); @@ -285,17 +288,10 @@ bool CPasswordInputField::draw(const SRenderData& data) { if (passwordLength == 0 && !placeholder.resourceID.empty()) { SPreloadedAsset* currAsset = nullptr; - if (!placeholder.failID.empty()) { - if (!placeholder.failAsset) - placeholder.failAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.failID); + if (!placeholder.asset) + placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID); - currAsset = placeholder.failAsset; - } else { - if (!placeholder.asset) - placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID); - - currAsset = placeholder.asset; - } + currAsset = placeholder.asset; if (currAsset) { Vector2D pos = outerBox.pos() + outerBox.size() / 2.f; @@ -309,41 +305,55 @@ bool CPasswordInputField::draw(const SRenderData& data) { return dots.currentAmount != passwordLength || fade.animated || col.animated || redrawShadow || data.opacity < 1.0 || forceReload; } -void CPasswordInputField::updateFailTex() { - const auto FAIL = g_pHyprlock->passwordLastFailReason(); - - if (checkWaiting) - placeholder.canGetNewFail = true; - +void CPasswordInputField::updatePlaceholder() { if (passwordLength != 0 || (checkWaiting && passwordLength == 0)) { - if (placeholder.failAsset) { - g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.failAsset); - placeholder.failAsset = nullptr; - placeholder.failID = ""; - redrawShadow = true; + if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ placeholder.isFailText) { + std::erase(placeholder.registeredResourceIDs, placeholder.resourceID); + g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset); + placeholder.asset = nullptr; + placeholder.resourceID = ""; + redrawShadow = true; } return; } - if (!FAIL.has_value() || !placeholder.canGetNewFail) + const auto AUTHFEEDBACKOPT = g_pAuth->getFeedback(); + if (!AUTHFEEDBACKOPT.has_value()) + return; + + const auto AUTHFEEDBACK = AUTHFEEDBACKOPT.value(); + if (AUTHFEEDBACK.text.empty() || placeholder.lastAuthFeedback == AUTHFEEDBACK.text) + return; + + placeholder.isFailText = AUTHFEEDBACK.isFail; + placeholder.lastAuthFeedback = AUTHFEEDBACK.text; + + placeholder.asset = nullptr; + + if (placeholder.isFailText) { + placeholder.currentText = configFailText; + replaceAll(placeholder.currentText, "$FAIL", AUTHFEEDBACK.text); + replaceAll(placeholder.currentText, "$ATTEMPTS", std::to_string(g_pHyprlock->getPasswordFailedAttempts())); + } else { + placeholder.currentText = configPlaceholderText; + replaceAll(placeholder.currentText, "$PROMPT", AUTHFEEDBACK.text); + } + + placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this); + if (std::find(placeholder.registeredResourceIDs.begin(), placeholder.registeredResourceIDs.end(), placeholder.resourceID) != placeholder.registeredResourceIDs.end()) return; - placeholder.failText = configFailText; - replaceAllFail(placeholder.failText, "$FAIL", FAIL.value()); - replaceAllFail(placeholder.failText, "$ATTEMPTS", std::to_string(failedAttempts)); + placeholder.registeredResourceIDs.push_back(placeholder.resourceID); // 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 = placeholder.failText; + request.id = placeholder.resourceID; + request.asset = placeholder.currentText; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = std::string{"Sans"}; - request.props["color"] = col.fail; + request.props["color"] = (placeholder.isFailText) ? col.fail : col.font; request.props["font_size"] = (int)size.y / 4; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); - - placeholder.canGetNewFail = false; } void CPasswordInputField::updateHiddenInputState() { @@ -423,7 +433,7 @@ void CPasswordInputField::updateColors() { col.stateNum = col.invertNum ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock; col.stateCaps = g_pHyprlock->m_bCapsLock; - if (placeholder.failID.empty()) { + if (!placeholder.isFailText) { if (g_pHyprlock->m_bFadeStarted) { if (TARGET == col.check) SOURCE = BORDERLESS ? col.inner : col.outer; diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index e78a6019..e92d99a8 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -22,7 +22,7 @@ class CPasswordInputField : public IWidget { private: void updateDots(); void updateFade(); - void updateFailTex(); + void updatePlaceholder(); void updateHiddenInputState(); void updateColors(); @@ -39,7 +39,7 @@ class CPasswordInputField : public IWidget { Vector2D configPos; Vector2D configSize; - std::string halign, valign, configFailText, outputStringPort; + std::string halign, valign, configFailText, outputStringPort, configPlaceholderText; int outThick, rounding; @@ -63,13 +63,17 @@ class CPasswordInputField : public IWidget { } fade; struct { - std::string resourceID = ""; - SPreloadedAsset* asset = nullptr; + std::string resourceID = ""; + SPreloadedAsset* asset = nullptr; + + std::string currentText = ""; + bool canGetNewText = true; + bool isFailText = false; + + std::string lastAuthFeedback; + + std::vector registeredResourceIDs; - std::string failID = ""; - SPreloadedAsset* failAsset = nullptr; - bool canGetNewFail = true; - std::string failText = ""; } placeholder; struct { From d45db6387cf88b06a28eeb0cb232858b2a436644 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Tue, 19 Mar 2024 19:45:29 +0100 Subject: [PATCH 02/17] input-field: fixup failedAttempts and color change Credits to @bvr-yr --- src/renderer/widgets/PasswordInputField.cpp | 11 ++++------- src/renderer/widgets/PasswordInputField.hpp | 8 ++++---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 9b85306e..2d3492bd 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -174,10 +174,6 @@ bool CPasswordInputField::draw(const SRenderData& data) { bool forceReload = false; - if (passwordLength == 0 && g_pHyprlock->getPasswordFailedAttempts() > failedAttempts) - forceReload = true; - - failedAttempts = g_pHyprlock->getPasswordFailedAttempts(); passwordLength = g_pHyprlock->getPasswordBufferDisplayLen(); checkWaiting = g_pAuth->checkWaiting(); @@ -322,9 +318,10 @@ void CPasswordInputField::updatePlaceholder() { return; const auto AUTHFEEDBACK = AUTHFEEDBACKOPT.value(); - if (AUTHFEEDBACK.text.empty() || placeholder.lastAuthFeedback == AUTHFEEDBACK.text) + if (AUTHFEEDBACK.text.empty() || (placeholder.lastAuthFeedback == AUTHFEEDBACK.text && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts)) return; + placeholder.failedAttempts = g_pHyprlock->getPasswordFailedAttempts(); placeholder.isFailText = AUTHFEEDBACK.isFail; placeholder.lastAuthFeedback = AUTHFEEDBACK.text; @@ -333,7 +330,7 @@ void CPasswordInputField::updatePlaceholder() { if (placeholder.isFailText) { placeholder.currentText = configFailText; replaceAll(placeholder.currentText, "$FAIL", AUTHFEEDBACK.text); - replaceAll(placeholder.currentText, "$ATTEMPTS", std::to_string(g_pHyprlock->getPasswordFailedAttempts())); + replaceAll(placeholder.currentText, "$ATTEMPTS", std::to_string(placeholder.failedAttempts)); } else { placeholder.currentText = configPlaceholderText; replaceAll(placeholder.currentText, "$PROMPT", AUTHFEEDBACK.text); @@ -433,7 +430,7 @@ void CPasswordInputField::updateColors() { col.stateNum = col.invertNum ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock; col.stateCaps = g_pHyprlock->m_bCapsLock; - if (!placeholder.isFailText) { + if (!placeholder.isFailText || passwordLength > 0 || (passwordLength == 0 && checkWaiting)) { if (g_pHyprlock->m_bFadeStarted) { if (TARGET == col.check) SOURCE = BORDERLESS ? col.inner : col.outer; diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index e92d99a8..a85f0a2e 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -31,7 +31,6 @@ class CPasswordInputField : public IWidget { bool checkWaiting = false; size_t passwordLength = 0; - size_t failedAttempts = 0; Vector2D size; Vector2D pos; @@ -66,9 +65,10 @@ class CPasswordInputField : public IWidget { std::string resourceID = ""; SPreloadedAsset* asset = nullptr; - std::string currentText = ""; - bool canGetNewText = true; - bool isFailText = false; + std::string currentText = ""; + size_t failedAttempts = 0; + bool canGetNewText = true; + bool isFailText = false; std::string lastAuthFeedback; From 898583b2cfe7271d4e3cbdec4985c460f68c1203 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Thu, 21 Mar 2024 08:58:07 +0100 Subject: [PATCH 03/17] pam: set default module to hyprland --- src/config/ConfigManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index f444b760..a1499543 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -49,7 +49,7 @@ void CConfigManager::init() { m_config.addConfigValue("general:no_fade_in", Hyprlang::INT{0}); m_config.addConfigValue("general:no_fade_out", Hyprlang::INT{0}); m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0}); - m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"su"}); + m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"hyprlock"}); m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""}); From f1eb6d6fdf089dfef8adf5b001daedec71c23806 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Thu, 21 Mar 2024 10:01:31 +0100 Subject: [PATCH 04/17] input-field: backup previous asset --- src/renderer/widgets/PasswordInputField.cpp | 8 +++++++- src/renderer/widgets/PasswordInputField.hpp | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 2d3492bd..c89312ba 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -289,6 +289,11 @@ bool CPasswordInputField::draw(const SRenderData& data) { currAsset = placeholder.asset; + if (!currAsset) { + currAsset = placeholder.previousAsset; + forceReload = true; + } + if (currAsset) { Vector2D pos = outerBox.pos() + outerBox.size() / 2.f; pos = pos - currAsset->texture.m_vSize / 2.f; @@ -325,7 +330,8 @@ void CPasswordInputField::updatePlaceholder() { placeholder.isFailText = AUTHFEEDBACK.isFail; placeholder.lastAuthFeedback = AUTHFEEDBACK.text; - placeholder.asset = nullptr; + placeholder.previousAsset = placeholder.asset; + placeholder.asset = nullptr; if (placeholder.isFailText) { placeholder.currentText = configFailText; diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index a85f0a2e..be6350a5 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -62,8 +62,9 @@ class CPasswordInputField : public IWidget { } fade; struct { - std::string resourceID = ""; - SPreloadedAsset* asset = nullptr; + std::string resourceID = ""; + SPreloadedAsset* asset = nullptr; + SPreloadedAsset* previousAsset = nullptr; std::string currentText = ""; size_t failedAttempts = 0; From 5a3ca3f597c4895c53742a6d77494f9eab7d0022 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Thu, 21 Mar 2024 11:13:30 +0100 Subject: [PATCH 05/17] auth: restart auth in onPasswordCheckTimer --- src/core/Auth.cpp | 16 +++++----------- src/core/Auth.hpp | 1 - src/core/hyprlock.cpp | 6 ++++++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index e6b313fa..dc18854c 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -50,20 +50,14 @@ static void passwordCheckTimerCallback(std::shared_ptr self, void* data) } void CAuth::start() { - m_authThread = std::thread([this]() { + std::thread([this]() { static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:pam_module")); - while (!g_pHyprlock->m_bTerminate) { - bool success = auth(*PPAMMODULE); - g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); - if (success) - break; - std::this_thread::sleep_for(std::chrono::seconds(1)); // TODO: Make sure that the callback succeeded - resetConversation(); - } - }); + resetConversation(); + auth(*PPAMMODULE); - m_authThread.detach(); + g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); + }).detach(); } bool CAuth::auth(std::string pam_module) { diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index eaaf3324..90f50974 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -44,7 +44,6 @@ class CAuth { private: SPamConversationState m_sConversationState; - std::thread m_authThread; void resetConversation(); }; diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 494c806e..83192f0c 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -735,6 +735,10 @@ static const ext_session_lock_v1_listener sessionLockListener = { // end session_lock +static void restartAuthTimerCallback(std::shared_ptr self, void* data) { + g_pAuth->start(); +} + void CHyprlock::onPasswordCheckTimer() { // check result if (g_pAuth->didAuthSucceed()) { @@ -749,6 +753,8 @@ void CHyprlock::onPasswordCheckTimer() { for (auto& o : m_vOutputs) { o->sessionLockSurface->render(); } + + g_pHyprlock->addTimer(/* controls error message duration */ std::chrono::seconds(1), restartAuthTimerCallback, nullptr); } } From 7fed3c74603f32f6c51bf7817108e72b8e2e244b Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Thu, 21 Mar 2024 23:37:23 +0100 Subject: [PATCH 06/17] auth: immediately switch to waiting when input was submitted --- src/core/Auth.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index dc18854c..a7b82691 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -28,6 +28,7 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp if (VERIFICATIONSTATE->prompt != VERIFICATIONSTATE->lastPrompt) g_pAuth->waitForInput(); + // Needed for unlocks via SIGUSR1 if (g_pHyprlock->m_bTerminate) return PAM_CONV_ERR; @@ -39,8 +40,6 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp } } - VERIFICATIONSTATE->waitingForPamAuth = true; - *resp = pam_reply; return PAM_SUCCESS; } @@ -105,17 +104,18 @@ void CAuth::waitForInput() { } void CAuth::submitInput(const std::string& input) { - std::unique_lock lock(m_sConversationState.inputMutex); - m_sConversationState.input = input; - m_sConversationState.inputSubmitted = true; + std::unique_lock lk(m_sConversationState.inputMutex); + m_sConversationState.input = input; + m_sConversationState.inputSubmitted = true; + m_sConversationState.waitingForPamAuth = true; m_sConversationState.inputSubmittedCondition.notify_all(); } std::optional CAuth::getFeedback() { - if (!m_sConversationState.inputSubmitted) { - return SFeedback{m_sConversationState.prompt, false}; - } else if (!m_sConversationState.failReason.empty()) { + if (!m_sConversationState.failReason.empty()) { return SFeedback{m_sConversationState.failReason, true}; + } else if (!m_sConversationState.inputSubmitted) { + return SFeedback{m_sConversationState.prompt, false}; } return std::nullopt; From 284de29bdb1eec5d3df62ac7200cb7d9fbd73b53 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Thu, 21 Mar 2024 23:47:39 +0100 Subject: [PATCH 07/17] auth: remove redundant waitingForPamAuth --- src/core/Auth.cpp | 22 ++++++++++------------ src/core/Auth.hpp | 6 ++---- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index a7b82691..08db8ba9 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -75,7 +75,7 @@ bool CAuth::auth(std::string pam_module) { ret = pam_authenticate(handle, 0); - m_sConversationState.waitingForPamAuth = false; + m_sConversationState.inputSubmitted = false; if (ret != PAM_SUCCESS) { m_sConversationState.success = false; @@ -105,9 +105,8 @@ void CAuth::waitForInput() { void CAuth::submitInput(const std::string& input) { std::unique_lock lk(m_sConversationState.inputMutex); - m_sConversationState.input = input; - m_sConversationState.inputSubmitted = true; - m_sConversationState.waitingForPamAuth = true; + m_sConversationState.input = input; + m_sConversationState.inputSubmitted = true; m_sConversationState.inputSubmittedCondition.notify_all(); } @@ -127,7 +126,7 @@ void CAuth::setPrompt(const char* prompt) { } bool CAuth::checkWaiting() { - return m_sConversationState.waitingForPamAuth; + return m_sConversationState.inputSubmitted; } void CAuth::terminate() { @@ -135,11 +134,10 @@ void CAuth::terminate() { } void CAuth::resetConversation() { - m_sConversationState.input = ""; - m_sConversationState.prompt = ""; - m_sConversationState.lastPrompt = ""; - m_sConversationState.failReason = ""; - m_sConversationState.inputSubmitted = false; - m_sConversationState.waitingForPamAuth = false; - m_sConversationState.success = false; + m_sConversationState.input = ""; + m_sConversationState.prompt = ""; + m_sConversationState.lastPrompt = ""; + m_sConversationState.failReason = ""; + m_sConversationState.inputSubmitted = false; + m_sConversationState.success = false; } diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index 90f50974..d201ff67 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -5,7 +5,6 @@ #include #include #include -#include class CAuth { public: @@ -18,9 +17,8 @@ class CAuth { std::mutex inputMutex; std::condition_variable inputSubmittedCondition; - bool waitingForPamAuth = false; - bool inputSubmitted = false; - bool success = false; + bool inputSubmitted = false; + bool success = false; }; struct SFeedback { From 18d970d2f61752fb17c1a8b055662cae8088c026 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Fri, 22 Mar 2024 00:37:01 +0100 Subject: [PATCH 08/17] auth: add inputRequested and reschedule submitInput --- src/core/Auth.cpp | 20 ++++++++++++++++++-- src/core/Auth.hpp | 3 ++- src/core/hyprlock.cpp | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index 08db8ba9..c0a0b2e5 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -99,14 +99,29 @@ bool CAuth::didAuthSucceed() { void CAuth::waitForInput() { std::unique_lock lk(m_sConversationState.inputMutex); + m_sConversationState.inputRequested = true; m_sConversationState.inputSubmitted = false; m_sConversationState.inputSubmittedCondition.wait(lk, [this] { return m_sConversationState.inputSubmitted || g_pHyprlock->m_bTerminate; }); } -void CAuth::submitInput(const std::string& input) { + +static void submitInputTimerCallback(std::shared_ptr self, void* data) { + const auto INPUT = (const char*)data; + g_pAuth->submitInput(INPUT); +} + +void CAuth::submitInput(const char* input) { std::unique_lock lk(m_sConversationState.inputMutex); + + m_sConversationState.inputSubmitted = true; // Blocks further input + + if (!m_sConversationState.inputRequested) { + g_pHyprlock->addTimer(std::chrono::milliseconds(1), submitInputTimerCallback, (void*)input); + return; + } + m_sConversationState.input = input; - m_sConversationState.inputSubmitted = true; + m_sConversationState.inputRequested = false; m_sConversationState.inputSubmittedCondition.notify_all(); } @@ -139,5 +154,6 @@ void CAuth::resetConversation() { m_sConversationState.lastPrompt = ""; m_sConversationState.failReason = ""; m_sConversationState.inputSubmitted = false; + m_sConversationState.inputRequested = false; m_sConversationState.success = false; } diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index d201ff67..ca332863 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -18,6 +18,7 @@ class CAuth { std::condition_variable inputSubmittedCondition; bool inputSubmitted = false; + bool inputRequested = false; bool success = false; }; @@ -31,7 +32,7 @@ class CAuth { bool didAuthSucceed(); void waitForInput(); - void submitInput(const std::string& input); + void submitInput(const char* input); void setPrompt(const char* prompt); std::optional getFeedback(); diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 83192f0c..7977cb85 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -829,7 +829,7 @@ void CHyprlock::onKey(uint32_t key, bool down) { } } else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) { Debug::log(LOG, "Authenticating"); - g_pAuth->submitInput(m_sPasswordState.passBuffer); + g_pAuth->submitInput(m_sPasswordState.passBuffer.c_str()); } else if (SYM == XKB_KEY_Escape) { Debug::log(LOG, "Clearing password buffer"); From 587639d36f68c2f083935d3dd269400f90544e25 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Fri, 22 Mar 2024 09:26:19 +0100 Subject: [PATCH 09/17] auth: clear password buffer and handle submitInput before input is requested --- src/core/Auth.cpp | 38 +++++++++++++++++++++++++++----------- src/core/Auth.hpp | 8 ++++++-- src/core/hyprlock.cpp | 9 ++++++++- src/core/hyprlock.hpp | 1 + 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index c0a0b2e5..3d772d45 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -97,32 +97,46 @@ bool CAuth::didAuthSucceed() { return m_sConversationState.success; } +static void onWaitForInputTimerCallback(std::shared_ptr self, void* data) { + g_pHyprlock->clearPasswordBuffer(); +} + void CAuth::waitForInput() { std::unique_lock lk(m_sConversationState.inputMutex); - m_sConversationState.inputRequested = true; + g_pHyprlock->addTimer(std::chrono::milliseconds(1), onWaitForInputTimerCallback, nullptr); m_sConversationState.inputSubmitted = false; + m_sConversationState.inputRequested = true; m_sConversationState.inputSubmittedCondition.wait(lk, [this] { return m_sConversationState.inputSubmitted || g_pHyprlock->m_bTerminate; }); } - -static void submitInputTimerCallback(std::shared_ptr self, void* data) { - const auto INPUT = (const char*)data; - g_pAuth->submitInput(INPUT); +static void unhandledSubmitInputTimerCallback(std::shared_ptr self, void* data) { + g_pAuth->submitInput(std::nullopt); } -void CAuth::submitInput(const char* input) { +void CAuth::submitInput(std::optional input) { std::unique_lock lk(m_sConversationState.inputMutex); - m_sConversationState.inputSubmitted = true; // Blocks further input - if (!m_sConversationState.inputRequested) { - g_pHyprlock->addTimer(std::chrono::milliseconds(1), submitInputTimerCallback, (void*)input); + m_sConversationState.blockInput = true; + m_sConversationState.unhandledInput = input.value_or(""); + g_pHyprlock->addTimer(std::chrono::milliseconds(1), unhandledSubmitInputTimerCallback, nullptr); return; } - m_sConversationState.input = input; + if (input.has_value()) + m_sConversationState.input = input.value(); + else if (!m_sConversationState.unhandledInput.empty()) { + m_sConversationState.input = m_sConversationState.unhandledInput; + m_sConversationState.unhandledInput = ""; + } else { + Debug::log(ERR, "No input to submit"); + m_sConversationState.input = ""; + } + m_sConversationState.inputRequested = false; + m_sConversationState.inputSubmitted = true; m_sConversationState.inputSubmittedCondition.notify_all(); + m_sConversationState.blockInput = false; } std::optional CAuth::getFeedback() { @@ -141,7 +155,7 @@ void CAuth::setPrompt(const char* prompt) { } bool CAuth::checkWaiting() { - return m_sConversationState.inputSubmitted; + return m_sConversationState.blockInput || m_sConversationState.inputSubmitted; } void CAuth::terminate() { @@ -155,5 +169,7 @@ void CAuth::resetConversation() { m_sConversationState.failReason = ""; m_sConversationState.inputSubmitted = false; m_sConversationState.inputRequested = false; + m_sConversationState.blockInput = false; + m_sConversationState.unhandledInput = ""; m_sConversationState.success = false; } diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index ca332863..98332f5b 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -19,7 +19,11 @@ class CAuth { bool inputSubmitted = false; bool inputRequested = false; - bool success = false; + + bool blockInput = false; + std::string unhandledInput = ""; + + bool success = false; }; struct SFeedback { @@ -32,7 +36,7 @@ class CAuth { bool didAuthSucceed(); void waitForInput(); - void submitInput(const char* input); + void submitInput(std::optional input); void setPrompt(const char* prompt); std::optional getFeedback(); diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 7977cb85..2dbd0b6d 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -758,6 +758,13 @@ void CHyprlock::onPasswordCheckTimer() { } } +void CHyprlock::clearPasswordBuffer() { + m_sPasswordState.passBuffer = ""; + for (auto& o : m_vOutputs) { + o->sessionLockSurface->render(); + } +} + void CHyprlock::renderOutput(const std::string& stringPort) { const auto MON = std::find_if(m_vOutputs.begin(), m_vOutputs.end(), [stringPort](const auto& other) { return other->stringPort == stringPort; }); @@ -829,7 +836,7 @@ void CHyprlock::onKey(uint32_t key, bool down) { } } else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) { Debug::log(LOG, "Authenticating"); - g_pAuth->submitInput(m_sPasswordState.passBuffer.c_str()); + g_pAuth->submitInput(m_sPasswordState.passBuffer); } else if (SYM == XKB_KEY_Escape) { Debug::log(LOG, "Clearing password buffer"); diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 293453ce..39c0973c 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -53,6 +53,7 @@ class CHyprlock { void onKey(uint32_t key, bool down); void onPasswordCheckTimer(); + void clearPasswordBuffer(); bool passwordCheckWaiting(); std::optional passwordLastFailReason(); From 390b27b7ae41a2730b9f068416da094d30df51aa Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Fri, 22 Mar 2024 12:47:17 +0100 Subject: [PATCH 10/17] Revert "input-field: backup previous asset" This reverts commit 89702945be6af4aa43f54688ad34a4ccba994a3e. Without the backup we avoid rendering the prompt placeholder for one frame when the failText is not available. Looks better this way. --- src/renderer/widgets/PasswordInputField.cpp | 8 +------- src/renderer/widgets/PasswordInputField.hpp | 5 ++--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index c89312ba..2d3492bd 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -289,11 +289,6 @@ bool CPasswordInputField::draw(const SRenderData& data) { currAsset = placeholder.asset; - if (!currAsset) { - currAsset = placeholder.previousAsset; - forceReload = true; - } - if (currAsset) { Vector2D pos = outerBox.pos() + outerBox.size() / 2.f; pos = pos - currAsset->texture.m_vSize / 2.f; @@ -330,8 +325,7 @@ void CPasswordInputField::updatePlaceholder() { placeholder.isFailText = AUTHFEEDBACK.isFail; placeholder.lastAuthFeedback = AUTHFEEDBACK.text; - placeholder.previousAsset = placeholder.asset; - placeholder.asset = nullptr; + placeholder.asset = nullptr; if (placeholder.isFailText) { placeholder.currentText = configFailText; diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index be6350a5..a85f0a2e 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -62,9 +62,8 @@ class CPasswordInputField : public IWidget { } fade; struct { - std::string resourceID = ""; - SPreloadedAsset* asset = nullptr; - SPreloadedAsset* previousAsset = nullptr; + std::string resourceID = ""; + SPreloadedAsset* asset = nullptr; std::string currentText = ""; size_t failedAttempts = 0; From 9cec5075be6030259a855199f30851a29547a19e Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Fri, 22 Mar 2024 13:35:59 +0100 Subject: [PATCH 11/17] auth: fallback to su if pam_module not in /etc/pam.d rare occasion where a path check even works on nix --- src/core/Auth.cpp | 15 ++++++++++++--- src/core/Auth.hpp | 6 +++++- src/core/hyprlock.cpp | 8 ++++++-- src/main.cpp | 5 ++--- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index 3d772d45..adddfc00 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -3,6 +3,7 @@ #include "../helpers/Log.hpp" #include "src/config/ConfigManager.hpp" +#include #include #include #include @@ -44,16 +45,24 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp return PAM_SUCCESS; } +CAuth::CAuth() { + static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:pam_module")); + m_sPamModule = *PPAMMODULE; + + if (!std::filesystem::exists(std::filesystem::path("/etc/pam.d/") / m_sPamModule)) { + Debug::log(ERR, "Pam module \"{}\" not found! Falling back to \"su\"", m_sPamModule); + m_sPamModule = "su"; + } +} + static void passwordCheckTimerCallback(std::shared_ptr self, void* data) { g_pHyprlock->onPasswordCheckTimer(); } void CAuth::start() { std::thread([this]() { - static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:pam_module")); - resetConversation(); - auth(*PPAMMODULE); + auth(m_sPamModule); g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); }).detach(); diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index 98332f5b..23f98340 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -31,6 +31,8 @@ class CAuth { bool isFail = false; }; + CAuth(); + void start(); bool auth(std::string pam_module); bool didAuthSucceed(); @@ -48,7 +50,9 @@ class CAuth { private: SPamConversationState m_sConversationState; + std::string m_sPamModule; + void resetConversation(); }; -inline std::unique_ptr g_pAuth = std::make_unique(); +inline std::unique_ptr g_pAuth; diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 2dbd0b6d..c23deabc 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -379,6 +379,9 @@ void CHyprlock::run() { acquireSessionLock(); + g_pAuth = std::make_unique(); + g_pAuth->start(); + registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART); registerSignalAction(SIGUSR2, handleForceUpdateSignal); registerSignalAction(SIGRTMIN, handlePollTerminate); @@ -452,8 +455,6 @@ void CHyprlock::run() { m_sLoopState.event = true; // let it process once - g_pAuth->start(); - while (1) { std::unique_lock lk(m_sLoopState.eventRequestMutex); if (m_sLoopState.event == false) @@ -759,6 +760,9 @@ void CHyprlock::onPasswordCheckTimer() { } void CHyprlock::clearPasswordBuffer() { + if (m_sPasswordState.passBuffer.empty()) + return; + m_sPasswordState.passBuffer = ""; for (auto& o : m_vOutputs) { o->sessionLockSurface->render(); diff --git a/src/main.cpp b/src/main.cpp index cf47ac0c..8bc156f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,7 @@ void help() { int main(int argc, char** argv, char** envp) { std::string configPath; std::string wlDisplay; - bool immediate = false; + bool immediate = false; for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; @@ -32,8 +32,7 @@ int main(int argc, char** argv, char** envp) { else if (arg == "--display" && i + 1 < argc) { wlDisplay = argv[i + 1]; i++; - } - else if (arg == "--immediate") { + } else if (arg == "--immediate") { immediate = true; } else if (arg == "--help" || arg == "-h") { help(); From ee79d30db93b915a67730b02ead1c03ebac598fc Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Mon, 8 Apr 2024 08:34:54 +0200 Subject: [PATCH 12/17] auth: rename inputSubmitted and resubmit callback --- src/core/Auth.cpp | 38 +++++++++++++++++++------------------- src/core/Auth.hpp | 4 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index adddfc00..325d1f6e 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -84,7 +84,7 @@ bool CAuth::auth(std::string pam_module) { ret = pam_authenticate(handle, 0); - m_sConversationState.inputSubmitted = false; + m_sConversationState.waitingForPamAuth = false; if (ret != PAM_SUCCESS) { m_sConversationState.success = false; @@ -113,12 +113,12 @@ static void onWaitForInputTimerCallback(std::shared_ptr self, void* data void CAuth::waitForInput() { std::unique_lock lk(m_sConversationState.inputMutex); g_pHyprlock->addTimer(std::chrono::milliseconds(1), onWaitForInputTimerCallback, nullptr); - m_sConversationState.inputSubmitted = false; - m_sConversationState.inputRequested = true; - m_sConversationState.inputSubmittedCondition.wait(lk, [this] { return m_sConversationState.inputSubmitted || g_pHyprlock->m_bTerminate; }); + m_sConversationState.waitingForPamAuth = false; + m_sConversationState.inputRequested = true; + m_sConversationState.inputSubmittedCondition.wait(lk, [this] { return !m_sConversationState.inputRequested || g_pHyprlock->m_bTerminate; }); } -static void unhandledSubmitInputTimerCallback(std::shared_ptr self, void* data) { +static void submitUnhandledInputTimerCallback(std::shared_ptr self, void* data) { g_pAuth->submitInput(std::nullopt); } @@ -128,7 +128,7 @@ void CAuth::submitInput(std::optional input) { if (!m_sConversationState.inputRequested) { m_sConversationState.blockInput = true; m_sConversationState.unhandledInput = input.value_or(""); - g_pHyprlock->addTimer(std::chrono::milliseconds(1), unhandledSubmitInputTimerCallback, nullptr); + g_pHyprlock->addTimer(std::chrono::milliseconds(1), submitUnhandledInputTimerCallback, nullptr); return; } @@ -142,8 +142,8 @@ void CAuth::submitInput(std::optional input) { m_sConversationState.input = ""; } - m_sConversationState.inputRequested = false; - m_sConversationState.inputSubmitted = true; + m_sConversationState.inputRequested = false; + m_sConversationState.waitingForPamAuth = true; m_sConversationState.inputSubmittedCondition.notify_all(); m_sConversationState.blockInput = false; } @@ -151,7 +151,7 @@ void CAuth::submitInput(std::optional input) { std::optional CAuth::getFeedback() { if (!m_sConversationState.failReason.empty()) { return SFeedback{m_sConversationState.failReason, true}; - } else if (!m_sConversationState.inputSubmitted) { + } else if (!m_sConversationState.waitingForPamAuth) { return SFeedback{m_sConversationState.prompt, false}; } @@ -164,7 +164,7 @@ void CAuth::setPrompt(const char* prompt) { } bool CAuth::checkWaiting() { - return m_sConversationState.blockInput || m_sConversationState.inputSubmitted; + return m_sConversationState.blockInput || m_sConversationState.waitingForPamAuth; } void CAuth::terminate() { @@ -172,13 +172,13 @@ void CAuth::terminate() { } void CAuth::resetConversation() { - m_sConversationState.input = ""; - m_sConversationState.prompt = ""; - m_sConversationState.lastPrompt = ""; - m_sConversationState.failReason = ""; - m_sConversationState.inputSubmitted = false; - m_sConversationState.inputRequested = false; - m_sConversationState.blockInput = false; - m_sConversationState.unhandledInput = ""; - m_sConversationState.success = false; + m_sConversationState.input = ""; + m_sConversationState.prompt = ""; + m_sConversationState.lastPrompt = ""; + m_sConversationState.failReason = ""; + m_sConversationState.waitingForPamAuth = false; + m_sConversationState.inputRequested = false; + m_sConversationState.blockInput = false; + m_sConversationState.unhandledInput = ""; + m_sConversationState.success = false; } diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index 23f98340..37c4d921 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -17,8 +17,8 @@ class CAuth { std::mutex inputMutex; std::condition_variable inputSubmittedCondition; - bool inputSubmitted = false; - bool inputRequested = false; + bool waitingForPamAuth = false; + bool inputRequested = false; bool blockInput = false; std::string unhandledInput = ""; From f8e32d2bffcb0f51916bdcd228dc7c3161e3b2c0 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Mon, 8 Apr 2024 22:04:20 +0200 Subject: [PATCH 13/17] auth: detach failText from the conversation --- src/core/Auth.cpp | 68 +++++++++------------ src/core/Auth.hpp | 27 ++++---- src/core/hyprlock.cpp | 18 ++++-- src/renderer/widgets/IWidget.cpp | 6 +- src/renderer/widgets/PasswordInputField.cpp | 10 +-- 5 files changed, 59 insertions(+), 70 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index 325d1f6e..152d6340 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -76,8 +76,8 @@ bool CAuth::auth(std::string pam_module) { int ret = pam_start(pam_module.c_str(), uidPassword->pw_name, &localConv, &handle); if (ret != PAM_SUCCESS) { - m_sConversationState.success = false; - m_sConversationState.failReason = "pam_start failed"; + m_sConversationState.success = false; + m_sFailText = "pam_start failed"; Debug::log(ERR, "auth: pam_start failed for {}", pam_module); return false; } @@ -87,16 +87,16 @@ bool CAuth::auth(std::string pam_module) { m_sConversationState.waitingForPamAuth = false; if (ret != PAM_SUCCESS) { - m_sConversationState.success = false; - m_sConversationState.failReason = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; - Debug::log(ERR, "auth: {} for {}", m_sConversationState.failReason, pam_module); + m_sConversationState.success = false; + m_sFailText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; + Debug::log(ERR, "auth: {} for {}", m_sFailText, pam_module); return false; } ret = pam_end(handle, ret); - m_sConversationState.success = true; - m_sConversationState.failReason = "Successfully authenticated"; + m_sConversationState.success = true; + m_sFailText = "Successfully authenticated"; Debug::log(LOG, "auth: authenticated for {}", pam_module); return true; @@ -106,56 +106,43 @@ bool CAuth::didAuthSucceed() { return m_sConversationState.success; } +// clearing the input must be done from the main thread! static void onWaitForInputTimerCallback(std::shared_ptr self, void* data) { g_pHyprlock->clearPasswordBuffer(); } void CAuth::waitForInput() { + if (!m_sConversationState.lastPrompt.empty()) + g_pHyprlock->addTimer(std::chrono::milliseconds(1), onWaitForInputTimerCallback, nullptr); + std::unique_lock lk(m_sConversationState.inputMutex); - g_pHyprlock->addTimer(std::chrono::milliseconds(1), onWaitForInputTimerCallback, nullptr); + m_bBlockInput = false; m_sConversationState.waitingForPamAuth = false; m_sConversationState.inputRequested = true; m_sConversationState.inputSubmittedCondition.wait(lk, [this] { return !m_sConversationState.inputRequested || g_pHyprlock->m_bTerminate; }); + m_bBlockInput = true; } -static void submitUnhandledInputTimerCallback(std::shared_ptr self, void* data) { - g_pAuth->submitInput(std::nullopt); -} - -void CAuth::submitInput(std::optional input) { +void CAuth::submitInput(std::string input) { std::unique_lock lk(m_sConversationState.inputMutex); - if (!m_sConversationState.inputRequested) { - m_sConversationState.blockInput = true; - m_sConversationState.unhandledInput = input.value_or(""); - g_pHyprlock->addTimer(std::chrono::milliseconds(1), submitUnhandledInputTimerCallback, nullptr); - return; - } - - if (input.has_value()) - m_sConversationState.input = input.value(); - else if (!m_sConversationState.unhandledInput.empty()) { - m_sConversationState.input = m_sConversationState.unhandledInput; - m_sConversationState.unhandledInput = ""; - } else { - Debug::log(ERR, "No input to submit"); - m_sConversationState.input = ""; - } + if (!m_sConversationState.inputRequested) + Debug::log(ERR, "SubmitInput called, but the auth thread is not waiting for input!"); + m_sConversationState.input = input; m_sConversationState.inputRequested = false; m_sConversationState.waitingForPamAuth = true; m_sConversationState.inputSubmittedCondition.notify_all(); - m_sConversationState.blockInput = false; } -std::optional CAuth::getFeedback() { - if (!m_sConversationState.failReason.empty()) { - return SFeedback{m_sConversationState.failReason, true}; - } else if (!m_sConversationState.waitingForPamAuth) { +CAuth::SFeedback CAuth::getFeedback() { + if (!m_sFailText.empty()) { + return SFeedback{m_sFailText, true}; + } else if (!m_sConversationState.prompt.empty()) { return SFeedback{m_sConversationState.prompt, false}; + } else { + return SFeedback{"Starting Auth...", false}; } - - return std::nullopt; } void CAuth::setPrompt(const char* prompt) { @@ -163,8 +150,12 @@ void CAuth::setPrompt(const char* prompt) { m_sConversationState.prompt = prompt; } +void CAuth::clearFailText() { + m_sFailText = ""; +} + bool CAuth::checkWaiting() { - return m_sConversationState.blockInput || m_sConversationState.waitingForPamAuth; + return m_bBlockInput || m_sConversationState.waitingForPamAuth; } void CAuth::terminate() { @@ -175,10 +166,7 @@ void CAuth::resetConversation() { m_sConversationState.input = ""; m_sConversationState.prompt = ""; m_sConversationState.lastPrompt = ""; - m_sConversationState.failReason = ""; m_sConversationState.waitingForPamAuth = false; m_sConversationState.inputRequested = false; - m_sConversationState.blockInput = false; - m_sConversationState.unhandledInput = ""; m_sConversationState.success = false; } diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index 37c4d921..f856a3b2 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -12,7 +11,6 @@ class CAuth { std::string input = ""; std::string prompt = ""; std::string lastPrompt = ""; - std::string failReason = ""; std::mutex inputMutex; std::condition_variable inputSubmittedCondition; @@ -20,9 +18,6 @@ class CAuth { bool waitingForPamAuth = false; bool inputRequested = false; - bool blockInput = false; - std::string unhandledInput = ""; - bool success = false; }; @@ -33,23 +28,27 @@ class CAuth { CAuth(); - void start(); - bool auth(std::string pam_module); - bool didAuthSucceed(); + void start(); + bool auth(std::string pam_module); + bool didAuthSucceed(); - void waitForInput(); - void submitInput(std::optional input); + void waitForInput(); + void submitInput(std::string input); - void setPrompt(const char* prompt); - std::optional getFeedback(); + void setPrompt(const char* prompt); + void clearFailText(); + SFeedback getFeedback(); - bool checkWaiting(); + bool checkWaiting(); - void terminate(); + void terminate(); private: SPamConversationState m_sConversationState; + std::string m_sFailText; + bool m_bBlockInput = true; + std::string m_sPamModule; void resetConversation(); diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index c23deabc..583eebb6 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -736,8 +736,12 @@ static const ext_session_lock_v1_listener sessionLockListener = { // end session_lock -static void restartAuthTimerCallback(std::shared_ptr self, void* data) { - g_pAuth->start(); +static void clearFailTextTimerCallback(std::shared_ptr self, void* data) { + g_pAuth->clearFailText(); + + for (auto& o : g_pHyprlock->m_vOutputs) { + o->sessionLockSurface->render(); + } } void CHyprlock::onPasswordCheckTimer() { @@ -745,17 +749,19 @@ void CHyprlock::onPasswordCheckTimer() { if (g_pAuth->didAuthSucceed()) { unlock(); } else { - Debug::log(LOG, "Authentication failed: {}", (g_pAuth->getFeedback().has_value()) ? g_pAuth->getFeedback().value().text : "unknown reason"); + Debug::log(LOG, "Failed attempts: {}", m_sPasswordState.failedAttempts); + m_sPasswordState.passBuffer = ""; m_sPasswordState.failedAttempts += 1; - Debug::log(LOG, "Failed attempts: {}", m_sPasswordState.failedAttempts); forceUpdateTimers(); + g_pHyprlock->addTimer(/* controls error message duration */ std::chrono::seconds(1), clearFailTextTimerCallback, nullptr); + + g_pAuth->start(); + for (auto& o : m_vOutputs) { o->sessionLockSurface->render(); } - - g_pHyprlock->addTimer(/* controls error message duration */ std::chrono::seconds(1), restartAuthTimerCallback, nullptr); } } diff --git a/src/renderer/widgets/IWidget.cpp b/src/renderer/widgets/IWidget.cpp index 76f55228..0d1fce81 100644 --- a/src/renderer/widgets/IWidget.cpp +++ b/src/renderer/widgets/IWidget.cpp @@ -120,9 +120,9 @@ IWidget::SFormatResult IWidget::formatString(std::string in) { } if (in.contains("$FAIL") || in.contains("$PROMPT")) { - const auto AUTHFEEDBACKOPT = g_pAuth->getFeedback(); - replaceAll(in, "$FAIL", (AUTHFEEDBACKOPT.has_value() && AUTHFEEDBACKOPT->isFail) ? AUTHFEEDBACKOPT->text : ""); - replaceAll(in, "$PROMPT", (AUTHFEEDBACKOPT.has_value() && !AUTHFEEDBACKOPT->isFail) ? AUTHFEEDBACKOPT->text : ""); + const auto AUTHFEEDBACK = g_pAuth->getFeedback(); + replaceAll(in, "$FAIL", AUTHFEEDBACK.isFail ? AUTHFEEDBACK.text : ""); + replaceAll(in, "$PROMPT", !AUTHFEEDBACK.isFail ? AUTHFEEDBACK.text : ""); result.allowForceUpdate = true; } diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 2d3492bd..073041f8 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -302,7 +302,7 @@ bool CPasswordInputField::draw(const SRenderData& data) { } void CPasswordInputField::updatePlaceholder() { - if (passwordLength != 0 || (checkWaiting && passwordLength == 0)) { + if (passwordLength != 0) { if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ placeholder.isFailText) { std::erase(placeholder.registeredResourceIDs, placeholder.resourceID); g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset); @@ -313,12 +313,8 @@ void CPasswordInputField::updatePlaceholder() { return; } - const auto AUTHFEEDBACKOPT = g_pAuth->getFeedback(); - if (!AUTHFEEDBACKOPT.has_value()) - return; - - const auto AUTHFEEDBACK = AUTHFEEDBACKOPT.value(); - if (AUTHFEEDBACK.text.empty() || (placeholder.lastAuthFeedback == AUTHFEEDBACK.text && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts)) + const auto AUTHFEEDBACK = g_pAuth->getFeedback(); + if (placeholder.lastAuthFeedback == AUTHFEEDBACK.text && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts) return; placeholder.failedAttempts = g_pHyprlock->getPasswordFailedAttempts(); From 8ce7cf723bd4d4bbfab63a078865151c01920299 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Tue, 9 Apr 2024 00:00:46 +0200 Subject: [PATCH 14/17] fix rebase mistake --- src/core/hyprlock.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 583eebb6..19c3f2c3 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -844,13 +844,6 @@ void CHyprlock::onKey(uint32_t key, bool down) { m_sPasswordState.passBuffer.pop_back(); m_sPasswordState.passBuffer = m_sPasswordState.passBuffer.substr(0, m_sPasswordState.passBuffer.length() - 1); } - } else if (SYM == XKB_KEY_Return || SYM == XKB_KEY_KP_Enter) { - Debug::log(LOG, "Authenticating"); - g_pAuth->submitInput(m_sPasswordState.passBuffer); - } else if (SYM == XKB_KEY_Escape) { - Debug::log(LOG, "Clearing password buffer"); - - m_sPasswordState.passBuffer = ""; } else if (SYM == XKB_KEY_Caps_Lock) { m_bCapsLock = !m_bCapsLock; } else if (SYM == XKB_KEY_Num_Lock) { From 8967c95ee0fbd8a4625ff80f993378d5bb537e68 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Tue, 9 Apr 2024 15:33:10 +0200 Subject: [PATCH 15/17] auth: make sure prompt and failText are not reset when restarting auth needed for labels --- src/core/Auth.cpp | 65 +++++++++------------ src/core/Auth.hpp | 30 +++++----- src/core/hyprlock.cpp | 7 ++- src/core/hyprlock.hpp | 5 +- src/renderer/widgets/IWidget.cpp | 13 +++-- src/renderer/widgets/PasswordInputField.cpp | 14 +++-- 6 files changed, 68 insertions(+), 66 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index 152d6340..fe7224db 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -15,33 +15,38 @@ #include int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { - const auto VERIFICATIONSTATE = (CAuth::SPamConversationState*)appdata_ptr; - struct pam_response* pam_reply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response)); + const auto CONVERSATIONSTATE = (CAuth::SPamConversationState*)appdata_ptr; + struct pam_response* pamReply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response)); + bool initialPrompt = true; for (int i = 0; i < num_msg; ++i) { switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: { - g_pAuth->setPrompt(msg[i]->msg); + const auto PROMPT = std::string(msg[i]->msg); + Debug::log(LOG, "PAM_PROMPT: {}", PROMPT); + // Some pam configurations ask for the password twice for whatever reason (Fedora su for example) // When the prompt is the same as the last one, I guess our answer can be the same. - Debug::log(LOG, "PAM_PROMPT: {}", msg[i]->msg); - if (VERIFICATIONSTATE->prompt != VERIFICATIONSTATE->lastPrompt) + if (initialPrompt || PROMPT != CONVERSATIONSTATE->prompt) { + //TODO: Force update timers because of the prompt variable + CONVERSATIONSTATE->prompt = PROMPT; g_pAuth->waitForInput(); + } // Needed for unlocks via SIGUSR1 if (g_pHyprlock->m_bTerminate) return PAM_CONV_ERR; - pam_reply[i].resp = strdup(VERIFICATIONSTATE->input.c_str()); - break; - } + pamReply[i].resp = strdup(CONVERSATIONSTATE->input.c_str()); + initialPrompt = false; + } break; case PAM_ERROR_MSG: Debug::log(ERR, "PAM: {}", msg[i]->msg); break; case PAM_TEXT_INFO: Debug::log(LOG, "PAM: {}", msg[i]->msg); break; } } - *resp = pam_reply; + *resp = pamReply; return PAM_SUCCESS; } @@ -76,8 +81,8 @@ bool CAuth::auth(std::string pam_module) { int ret = pam_start(pam_module.c_str(), uidPassword->pw_name, &localConv, &handle); if (ret != PAM_SUCCESS) { - m_sConversationState.success = false; - m_sFailText = "pam_start failed"; + m_sConversationState.success = false; + m_sConversationState.failText = "pam_start failed"; Debug::log(ERR, "auth: pam_start failed for {}", pam_module); return false; } @@ -87,16 +92,16 @@ bool CAuth::auth(std::string pam_module) { m_sConversationState.waitingForPamAuth = false; if (ret != PAM_SUCCESS) { - m_sConversationState.success = false; - m_sFailText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; - Debug::log(ERR, "auth: {} for {}", m_sFailText, pam_module); + m_sConversationState.success = false; + m_sConversationState.failText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; + Debug::log(ERR, "auth: {} for {}", m_sConversationState.failText, pam_module); return false; } ret = pam_end(handle, ret); - m_sConversationState.success = true; - m_sFailText = "Successfully authenticated"; + m_sConversationState.success = true; + m_sConversationState.failText = "Successfully authenticated"; Debug::log(LOG, "auth: authenticated for {}", pam_module); return true; @@ -106,14 +111,13 @@ bool CAuth::didAuthSucceed() { return m_sConversationState.success; } -// clearing the input must be done from the main thread! -static void onWaitForInputTimerCallback(std::shared_ptr self, void* data) { +// clearing the input must be done from the main thread +static void clearInputTimerCallback(std::shared_ptr self, void* data) { g_pHyprlock->clearPasswordBuffer(); } void CAuth::waitForInput() { - if (!m_sConversationState.lastPrompt.empty()) - g_pHyprlock->addTimer(std::chrono::milliseconds(1), onWaitForInputTimerCallback, nullptr); + g_pHyprlock->addTimer(std::chrono::milliseconds(1), clearInputTimerCallback, nullptr); std::unique_lock lk(m_sConversationState.inputMutex); m_bBlockInput = false; @@ -135,23 +139,12 @@ void CAuth::submitInput(std::string input) { m_sConversationState.inputSubmittedCondition.notify_all(); } -CAuth::SFeedback CAuth::getFeedback() { - if (!m_sFailText.empty()) { - return SFeedback{m_sFailText, true}; - } else if (!m_sConversationState.prompt.empty()) { - return SFeedback{m_sConversationState.prompt, false}; - } else { - return SFeedback{"Starting Auth...", false}; - } -} - -void CAuth::setPrompt(const char* prompt) { - m_sConversationState.lastPrompt = m_sConversationState.prompt; - m_sConversationState.prompt = prompt; +std::optional CAuth::getLastFailText() { + return m_sConversationState.failText.empty() ? std::nullopt : std::optional(m_sConversationState.failText); } -void CAuth::clearFailText() { - m_sFailText = ""; +std::optional CAuth::getLastPrompt() { + return m_sConversationState.prompt.empty() ? std::nullopt : std::optional(m_sConversationState.prompt); } bool CAuth::checkWaiting() { @@ -164,8 +157,6 @@ void CAuth::terminate() { void CAuth::resetConversation() { m_sConversationState.input = ""; - m_sConversationState.prompt = ""; - m_sConversationState.lastPrompt = ""; m_sConversationState.waitingForPamAuth = false; m_sConversationState.inputRequested = false; m_sConversationState.success = false; diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index f856a3b2..7e25e79b 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -8,9 +9,9 @@ class CAuth { public: struct SPamConversationState { - std::string input = ""; - std::string prompt = ""; - std::string lastPrompt = ""; + std::string input = ""; + std::string prompt = ""; + std::string failText = ""; std::mutex inputMutex; std::condition_variable inputSubmittedCondition; @@ -28,25 +29,26 @@ class CAuth { CAuth(); - void start(); - bool auth(std::string pam_module); - bool didAuthSucceed(); + void start(); + bool auth(std::string pam_module); + bool didAuthSucceed(); - void waitForInput(); - void submitInput(std::string input); + void waitForInput(); + void submitInput(std::string input); - void setPrompt(const char* prompt); - void clearFailText(); - SFeedback getFeedback(); + std::optional getLastFailText(); + std::optional getLastPrompt(); - bool checkWaiting(); + bool checkWaiting(); - void terminate(); + void terminate(); + + // Should only be set via the main thread + bool m_bDisplayFailText = false; private: SPamConversationState m_sConversationState; - std::string m_sFailText; bool m_bBlockInput = true; std::string m_sPamModule; diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 19c3f2c3..a17f3aef 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -736,8 +736,8 @@ static const ext_session_lock_v1_listener sessionLockListener = { // end session_lock -static void clearFailTextTimerCallback(std::shared_ptr self, void* data) { - g_pAuth->clearFailText(); +static void displayFailTextTimerCallback(std::shared_ptr self, void* data) { + g_pAuth->m_bDisplayFailText = false; for (auto& o : g_pHyprlock->m_vOutputs) { o->sessionLockSurface->render(); @@ -753,9 +753,10 @@ void CHyprlock::onPasswordCheckTimer() { m_sPasswordState.passBuffer = ""; m_sPasswordState.failedAttempts += 1; + g_pAuth->m_bDisplayFailText = true; forceUpdateTimers(); - g_pHyprlock->addTimer(/* controls error message duration */ std::chrono::seconds(1), clearFailTextTimerCallback, nullptr); + g_pHyprlock->addTimer(/* controls error message duration */ std::chrono::seconds(1), displayFailTextTimerCallback, nullptr); g_pAuth->start(); diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 39c0973c..135c4ec3 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -130,8 +130,9 @@ class CHyprlock { } m_sLockState; struct { - std::string passBuffer = ""; - size_t failedAttempts = 0; + std::string passBuffer = ""; + size_t failedAttempts = 0; + bool displayFailText = false; } m_sPasswordState; struct { diff --git a/src/renderer/widgets/IWidget.cpp b/src/renderer/widgets/IWidget.cpp index 0d1fce81..fb742bc2 100644 --- a/src/renderer/widgets/IWidget.cpp +++ b/src/renderer/widgets/IWidget.cpp @@ -119,10 +119,15 @@ IWidget::SFormatResult IWidget::formatString(std::string in) { result.updateEveryMs = result.updateEveryMs != 0 && result.updateEveryMs < 1000 ? result.updateEveryMs : 1000; } - if (in.contains("$FAIL") || in.contains("$PROMPT")) { - const auto AUTHFEEDBACK = g_pAuth->getFeedback(); - replaceAll(in, "$FAIL", AUTHFEEDBACK.isFail ? AUTHFEEDBACK.text : ""); - replaceAll(in, "$PROMPT", !AUTHFEEDBACK.isFail ? AUTHFEEDBACK.text : ""); + if (in.contains("$FAIL")) { + const auto FAIL = g_pAuth->getLastFailText(); + replaceAll(in, "$FAIL", FAIL.has_value() ? FAIL.value() : ""); + result.allowForceUpdate = true; + } + + if (in.contains("$PROMPT")) { + const auto PROMPT = g_pAuth->getLastPrompt(); + replaceAll(in, "$PROMPT", PROMPT.has_value() ? PROMPT.value() : ""); result.allowForceUpdate = true; } diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 073041f8..de62c1d9 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -1,6 +1,7 @@ #include "PasswordInputField.hpp" #include "../Renderer.hpp" #include "../../core/hyprlock.hpp" +#include "src/core/Auth.hpp" #include static void replaceAll(std::string& str, const std::string& from, const std::string& to) { @@ -313,23 +314,24 @@ void CPasswordInputField::updatePlaceholder() { return; } - const auto AUTHFEEDBACK = g_pAuth->getFeedback(); - if (placeholder.lastAuthFeedback == AUTHFEEDBACK.text && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts) + const auto AUTHFEEDBACK = g_pAuth->m_bDisplayFailText ? g_pAuth->getLastFailText().value_or("Ups, no fail text?") : g_pAuth->getLastPrompt().value_or("Ups, no prompt?"); + + if (placeholder.lastAuthFeedback == AUTHFEEDBACK && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts) return; placeholder.failedAttempts = g_pHyprlock->getPasswordFailedAttempts(); - placeholder.isFailText = AUTHFEEDBACK.isFail; - placeholder.lastAuthFeedback = AUTHFEEDBACK.text; + placeholder.isFailText = g_pAuth->m_bDisplayFailText; + placeholder.lastAuthFeedback = AUTHFEEDBACK; placeholder.asset = nullptr; if (placeholder.isFailText) { placeholder.currentText = configFailText; - replaceAll(placeholder.currentText, "$FAIL", AUTHFEEDBACK.text); + replaceAll(placeholder.currentText, "$FAIL", AUTHFEEDBACK); replaceAll(placeholder.currentText, "$ATTEMPTS", std::to_string(placeholder.failedAttempts)); } else { placeholder.currentText = configPlaceholderText; - replaceAll(placeholder.currentText, "$PROMPT", AUTHFEEDBACK.text); + replaceAll(placeholder.currentText, "$PROMPT", AUTHFEEDBACK); } placeholder.resourceID = "placeholder:" + placeholder.currentText + std::to_string((uintptr_t)this); From 684e0d2e7b32f403177adb296edd781b4c452818 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Tue, 9 Apr 2024 16:15:15 +0200 Subject: [PATCH 16/17] auth: force update timers when the prompt changes --- src/core/Auth.cpp | 9 ++++++--- src/core/hyprlock.cpp | 5 +++++ src/core/hyprlock.hpp | 2 ++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index fe7224db..cc0a09ae 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -23,13 +23,16 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp switch (msg[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: case PAM_PROMPT_ECHO_ON: { - const auto PROMPT = std::string(msg[i]->msg); + const auto PROMPT = std::string(msg[i]->msg); + const auto PROMPTCHANGED = PROMPT != CONVERSATIONSTATE->prompt; Debug::log(LOG, "PAM_PROMPT: {}", PROMPT); + if (PROMPTCHANGED) + g_pHyprlock->enqueueForceUpdateTimers(); + // Some pam configurations ask for the password twice for whatever reason (Fedora su for example) // When the prompt is the same as the last one, I guess our answer can be the same. - if (initialPrompt || PROMPT != CONVERSATIONSTATE->prompt) { - //TODO: Force update timers because of the prompt variable + if (initialPrompt || PROMPTCHANGED) { CONVERSATIONSTATE->prompt = PROMPT; g_pAuth->waitForInput(); } diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index a17f3aef..e17c14d0 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -952,6 +952,11 @@ std::vector> CHyprlock::getTimers() { return m_vTimers; } +void CHyprlock::enqueueForceUpdateTimers() { + addTimer( + std::chrono::milliseconds(1), [](std::shared_ptr self, void* data) { forceUpdateTimers(); }, nullptr, false); +} + void CHyprlock::spawnAsync(const std::string& args) { Debug::log(LOG, "Executing (async) {}", args); diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 135c4ec3..a24df220 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -40,6 +40,8 @@ class CHyprlock { std::shared_ptr addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data, bool force = false); + void enqueueForceUpdateTimers(); + void onLockLocked(); void onLockFinished(); From 43b13bfb71899f73dba80567e5dae8216f21cdb1 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler Date: Tue, 9 Apr 2024 17:20:45 +0200 Subject: [PATCH 17/17] auth: remove unused stuff --- src/core/Auth.cpp | 12 ++++++------ src/core/Auth.hpp | 7 +------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/core/Auth.cpp b/src/core/Auth.cpp index cc0a09ae..ea3066fa 100644 --- a/src/core/Auth.cpp +++ b/src/core/Auth.cpp @@ -70,23 +70,23 @@ static void passwordCheckTimerCallback(std::shared_ptr self, void* data) void CAuth::start() { std::thread([this]() { resetConversation(); - auth(m_sPamModule); + auth(); g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); }).detach(); } -bool CAuth::auth(std::string pam_module) { +bool CAuth::auth() { const pam_conv localConv = {conv, (void*)&m_sConversationState}; pam_handle_t* handle = NULL; auto uidPassword = getpwuid(getuid()); - int ret = pam_start(pam_module.c_str(), uidPassword->pw_name, &localConv, &handle); + int ret = pam_start(m_sPamModule.c_str(), uidPassword->pw_name, &localConv, &handle); if (ret != PAM_SUCCESS) { m_sConversationState.success = false; m_sConversationState.failText = "pam_start failed"; - Debug::log(ERR, "auth: pam_start failed for {}", pam_module); + Debug::log(ERR, "auth: pam_start failed for {}", m_sPamModule); return false; } @@ -97,7 +97,7 @@ bool CAuth::auth(std::string pam_module) { if (ret != PAM_SUCCESS) { m_sConversationState.success = false; m_sConversationState.failText = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"; - Debug::log(ERR, "auth: {} for {}", m_sConversationState.failText, pam_module); + Debug::log(ERR, "auth: {} for {}", m_sConversationState.failText, m_sPamModule); return false; } @@ -105,7 +105,7 @@ bool CAuth::auth(std::string pam_module) { m_sConversationState.success = true; m_sConversationState.failText = "Successfully authenticated"; - Debug::log(LOG, "auth: authenticated for {}", pam_module); + Debug::log(LOG, "auth: authenticated for {}", m_sPamModule); return true; } diff --git a/src/core/Auth.hpp b/src/core/Auth.hpp index 7e25e79b..b0fea791 100644 --- a/src/core/Auth.hpp +++ b/src/core/Auth.hpp @@ -22,15 +22,10 @@ class CAuth { bool success = false; }; - struct SFeedback { - std::string text; - bool isFail = false; - }; - CAuth(); void start(); - bool auth(std::string pam_module); + bool auth(); bool didAuthSucceed(); void waitForInput();