core/input: add feedback for password verification

This commit is contained in:
Vaxry 2024-02-20 00:53:49 +00:00
parent 96f2818915
commit 9074ff702d
6 changed files with 144 additions and 40 deletions

View file

@ -1,9 +1,12 @@
#include "Password.hpp" #include "Password.hpp"
#include "hyprlock.hpp"
#include <unistd.h> #include <unistd.h>
#include <security/pam_appl.h> #include <security/pam_appl.h>
#include <security/pam_misc.h> #include <security/pam_misc.h>
#include <thread>
struct pam_response* reply; struct pam_response* reply;
// //
@ -12,14 +15,27 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
return PAM_SUCCESS; return PAM_SUCCESS;
} }
CPassword::SVerificationResult CPassword::verify(const std::string& pass) { static void passwordCheckTimerCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->onPasswordCheckTimer();
}
std::shared_ptr<CPassword::SVerificationResult> CPassword::verify(const std::string& pass) {
std::shared_ptr<CPassword::SVerificationResult> result = std::make_shared<CPassword::SVerificationResult>(false);
std::thread([this, result, pass]() {
const pam_conv localConv = {conv, NULL}; const pam_conv localConv = {conv, NULL};
pam_handle_t* handle = NULL; pam_handle_t* handle = NULL;
int ret = pam_start("su", getlogin(), &localConv, &handle); int ret = pam_start("su", getlogin(), &localConv, &handle);
if (ret != PAM_SUCCESS) if (ret != PAM_SUCCESS) {
return {false, "pam_start failed"}; result->success = false;
result->failReason = "pam_start failed";
result->realized = true;
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
return;
}
reply = (struct pam_response*)malloc(sizeof(struct pam_response)); reply = (struct pam_response*)malloc(sizeof(struct pam_response));
@ -27,10 +43,21 @@ CPassword::SVerificationResult CPassword::verify(const std::string& pass) {
reply->resp_retcode = 0; reply->resp_retcode = 0;
ret = pam_authenticate(handle, 0); ret = pam_authenticate(handle, 0);
if (ret != PAM_SUCCESS) if (ret != PAM_SUCCESS) {
return {false, ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"}; 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); ret = pam_end(handle, ret);
return {true, "Successfully authenticated"}; result->success = true;
result->failReason = "Successfully authenticated";
result->realized = true;
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
}).detach();
return result;
} }

View file

@ -2,15 +2,17 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <atomic>
class CPassword { class CPassword {
public: public:
struct SVerificationResult { struct SVerificationResult {
std::atomic<bool> realized = false;
bool success = false; bool success = false;
std::string failReason = ""; std::string failReason = "";
}; };
SVerificationResult verify(const std::string& pass); std::shared_ptr<SVerificationResult> verify(const std::string& pass);
}; };
inline std::unique_ptr<CPassword> g_pPassword = std::make_unique<CPassword>(); inline std::unique_ptr<CPassword> g_pPassword = std::make_unique<CPassword>();

View file

@ -405,6 +405,27 @@ static const ext_session_lock_v1_listener sessionLockListener = {
// end session_lock // 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<std::string> CHyprlock::passwordLastFailReason() {
return m_sPasswordState.lastFailReason;
}
void CHyprlock::onKey(uint32_t key) { void CHyprlock::onKey(uint32_t key) {
const auto SYM = xkb_state_key_get_one_sym(m_pXKBState, key + 8); 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) { } else if (SYM == XKB_KEY_Return) {
Debug::log(LOG, "Authenticating"); Debug::log(LOG, "Authenticating");
const auto RESULT = g_pPassword->verify(m_sPasswordState.passBuffer); m_sPasswordState.result = g_pPassword->verify(m_sPasswordState.passBuffer);
Debug::log(LOG, "Password auth result: {}", RESULT.failReason);
if (RESULT.success)
unlockSession();
m_sPasswordState.passBuffer = "";
} else { } else {
char buf[16] = {0}; char buf[16] = {0};
int len = xkb_keysym_to_utf8(SYM, buf, 16); int len = xkb_keysym_to_utf8(SYM, buf, 16);

View file

@ -7,10 +7,12 @@
#include "Output.hpp" #include "Output.hpp"
#include "CursorShape.hpp" #include "CursorShape.hpp"
#include "Timer.hpp" #include "Timer.hpp"
#include "Password.hpp"
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <condition_variable> #include <condition_variable>
#include <optional>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
@ -32,6 +34,9 @@ class CHyprlock {
void unlockSession(); void unlockSession();
void onKey(uint32_t key); void onKey(uint32_t key);
void onPasswordCheckTimer();
bool passwordCheckWaiting();
std::optional<std::string> passwordLastFailReason();
size_t getPasswordBufferLen(); size_t getPasswordBufferLen();
@ -70,6 +75,8 @@ class CHyprlock {
struct { struct {
std::string passBuffer = ""; std::string passBuffer = "";
std::shared_ptr<CPassword::SVerificationResult> result;
std::optional<std::string> lastFailReason;
} m_sPasswordState; } m_sPasswordState;
struct { struct {

View file

@ -23,7 +23,7 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport, const std::un
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = std::string{"Sans"}; 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["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); g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
} }
} }
@ -86,13 +86,20 @@ bool CPasswordInputField::draw(const SRenderData& data) {
CBox inputFieldBox = {pos, size}; CBox inputFieldBox = {pos, size};
CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}}; CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}};
bool forceReload = false;
updateFade(); updateFade();
updateDots(); updateDots();
updateFailTex();
float passAlpha = g_pHyprlock->passwordCheckWaiting() ? 0.5 : 1.0;
CColor outerCol = outer; CColor outerCol = outer;
outer.a = fade.a * data.opacity; outer.a = fade.a * data.opacity;
CColor innerCol = inner; CColor innerCol = inner;
innerCol.a = fade.a * data.opacity; 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(outerBox, outerCol, outerBox.h / 2.0);
g_pRenderer->renderRect(inputFieldBox, innerCol, inputFieldBox.h / 2.0); g_pRenderer->renderRect(inputFieldBox, innerCol, inputFieldBox.h / 2.0);
@ -103,7 +110,6 @@ bool CPasswordInputField::draw(const SRenderData& data) {
for (size_t i = 0; i < std::floor(dots.currentAmount); ++i) { 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}; 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}}; CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
CColor fontCol = font;
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0); g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
} }
@ -111,7 +117,6 @@ bool CPasswordInputField::draw(const SRenderData& data) {
Vector2D currentPos = 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}; 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}}; CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
CColor fontCol = font;
fontCol.a = (dots.currentAmount - std::floor(dots.currentAmount)) * data.opacity; fontCol.a = (dots.currentAmount - std::floor(dots.currentAmount)) * data.opacity;
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0); g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
} }
@ -119,16 +124,60 @@ bool CPasswordInputField::draw(const SRenderData& data) {
const auto PASSLEN = g_pHyprlock->getPasswordBufferLen(); const auto PASSLEN = g_pHyprlock->getPasswordBufferLen();
if (PASSLEN == 0 && !placeholder.resourceID.empty()) { if (PASSLEN == 0 && !placeholder.resourceID.empty()) {
SPreloadedAsset* currAsset = nullptr;
if (!placeholder.failID.empty()) {
if (!placeholder.failAsset)
placeholder.failAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.failID);
currAsset = placeholder.failAsset;
} else {
if (!placeholder.asset) if (!placeholder.asset)
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID); placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
if (placeholder.asset) { currAsset = 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);
}
} }
return dots.currentAmount != PASSLEN || data.opacity < 1.0 || fade.a < 1.0; 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 || 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 = "<span style=\"italic\">" + FAIL.value() + "</span>";
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;
} }

View file

@ -19,6 +19,7 @@ class CPasswordInputField : public IWidget {
private: private:
void updateDots(); void updateDots();
void updateFade(); void updateFade();
void updateFailTex();
Vector2D size; Vector2D size;
Vector2D pos; Vector2D pos;
@ -43,6 +44,10 @@ class CPasswordInputField : public IWidget {
struct { struct {
std::string resourceID = ""; std::string resourceID = "";
SPreloadedAsset* asset = nullptr; SPreloadedAsset* asset = nullptr;
std::string failID = "";
SPreloadedAsset* failAsset = nullptr;
bool canGetNewFail = true;
} placeholder; } placeholder;
bool fadeOnEmpty; bool fadeOnEmpty;