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

View file

@ -2,15 +2,17 @@
#include <memory>
#include <string>
#include <atomic>
class CPassword {
public:
struct SVerificationResult {
bool success = false;
std::string failReason = "";
std::atomic<bool> realized = false;
bool success = false;
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>();

View file

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

View file

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

View file

@ -23,7 +23,7 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport, const std::un
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = std::string{"Sans"};
request.props["color"] = CColor{1.0 - font.r, 1.0 - font.g, 1.0 - font.b, 0.5};
request.props["font_size"] = 12;
request.props["font_size"] = (int)size.y / 4;
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
}
@ -86,13 +86,20 @@ bool CPasswordInputField::draw(const SRenderData& data) {
CBox inputFieldBox = {pos, size};
CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}};
bool forceReload = false;
updateFade();
updateDots();
updateFailTex();
float passAlpha = g_pHyprlock->passwordCheckWaiting() ? 0.5 : 1.0;
CColor outerCol = outer;
outer.a = fade.a * data.opacity;
CColor innerCol = inner;
innerCol.a = fade.a * data.opacity;
CColor fontCol = font;
fontCol.a *= fade.a * data.opacity * passAlpha;
g_pRenderer->renderRect(outerBox, outerCol, outerBox.h / 2.0);
g_pRenderer->renderRect(inputFieldBox, innerCol, inputFieldBox.h / 2.0);
@ -103,32 +110,74 @@ bool CPasswordInputField::draw(const SRenderData& data) {
for (size_t i = 0; i < std::floor(dots.currentAmount); ++i) {
Vector2D currentPos = inputFieldBox.pos() + Vector2D{PASS_SPACING * 2, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * i, 0};
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
CColor fontCol = font;
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
}
if (dots.currentAmount != std::floor(dots.currentAmount)) {
Vector2D currentPos =
inputFieldBox.pos() + Vector2D{PASS_SPACING * 2, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * std::floor(dots.currentAmount), 0};
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
CColor fontCol = font;
fontCol.a = (dots.currentAmount - std::floor(dots.currentAmount)) * data.opacity;
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
fontCol.a = (dots.currentAmount - std::floor(dots.currentAmount)) * data.opacity;
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
}
const auto PASSLEN = g_pHyprlock->getPasswordBufferLen();
if (PASSLEN == 0 && !placeholder.resourceID.empty()) {
if (!placeholder.asset)
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
SPreloadedAsset* currAsset = nullptr;
if (placeholder.asset) {
Vector2D pos = outerBox.pos() + outerBox.size() / 2.f;
pos = pos - placeholder.asset->texture.m_vSize / 2.f;
CBox textbox{pos, placeholder.asset->texture.m_vSize};
g_pRenderer->renderTexture(textbox, placeholder.asset->texture, data.opacity * fade.a, 0);
if (!placeholder.failID.empty()) {
if (!placeholder.failAsset)
placeholder.failAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.failID);
currAsset = placeholder.failAsset;
} else {
if (!placeholder.asset)
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
currAsset = placeholder.asset;
}
if (currAsset) {
Vector2D pos = outerBox.pos() + outerBox.size() / 2.f;
pos = pos - currAsset->texture.m_vSize / 2.f;
CBox textbox{pos, currAsset->texture.m_vSize};
g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a, 0);
} else
forceReload = true;
}
return dots.currentAmount != PASSLEN || data.opacity < 1.0 || fade.a < 1.0;
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:
void updateDots();
void updateFade();
void updateFailTex();
Vector2D size;
Vector2D pos;
@ -43,6 +44,10 @@ class CPasswordInputField : public IWidget {
struct {
std::string resourceID = "";
SPreloadedAsset* asset = nullptr;
std::string failID = "";
SPreloadedAsset* failAsset = nullptr;
bool canGetNewFail = true;
} placeholder;
bool fadeOnEmpty;