mirror of
https://github.com/hyprwm/hyprlock.git
synced 2024-12-22 13:29:48 +01:00
core/input: add feedback for password verification
This commit is contained in:
parent
96f2818915
commit
9074ff702d
6 changed files with 144 additions and 40 deletions
|
@ -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;
|
||||
static void passwordCheckTimerCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||
g_pHyprlock->onPasswordCheckTimer();
|
||||
}
|
||||
|
||||
int ret = pam_start("su", getlogin(), &localConv, &handle);
|
||||
std::shared_ptr<CPassword::SVerificationResult> CPassword::verify(const std::string& pass) {
|
||||
|
||||
if (ret != PAM_SUCCESS)
|
||||
return {false, "pam_start failed"};
|
||||
std::shared_ptr<CPassword::SVerificationResult> result = std::make_shared<CPassword::SVerificationResult>(false);
|
||||
|
||||
reply = (struct pam_response*)malloc(sizeof(struct pam_response));
|
||||
std::thread([this, result, pass]() {
|
||||
const pam_conv localConv = {conv, NULL};
|
||||
pam_handle_t* handle = NULL;
|
||||
|
||||
reply->resp = strdup(pass.c_str());
|
||||
reply->resp_retcode = 0;
|
||||
ret = pam_authenticate(handle, 0);
|
||||
int ret = pam_start("su", getlogin(), &localConv, &handle);
|
||||
|
||||
if (ret != PAM_SUCCESS)
|
||||
return {false, ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"};
|
||||
if (ret != PAM_SUCCESS) {
|
||||
result->success = false;
|
||||
result->failReason = "pam_start failed";
|
||||
result->realized = true;
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = pam_end(handle, ret);
|
||||
reply = (struct pam_response*)malloc(sizeof(struct pam_response));
|
||||
|
||||
return {true, "Successfully authenticated"};
|
||||
reply->resp = strdup(pass.c_str());
|
||||
reply->resp_retcode = 0;
|
||||
ret = pam_authenticate(handle, 0);
|
||||
|
||||
if (ret != PAM_SUCCESS) {
|
||||
result->success = false;
|
||||
result->failReason = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed";
|
||||
result->realized = true;
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = pam_end(handle, ret);
|
||||
|
||||
result->success = true;
|
||||
result->failReason = "Successfully authenticated";
|
||||
result->realized = true;
|
||||
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
|
||||
}).detach();
|
||||
|
||||
return result;
|
||||
}
|
|
@ -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>();
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue