auth: add an interface for different authentication methods (#578)

* auth: add an interface for different authentication methods

* auth: pick inline feedback based on last active implementation

* config: move auth options to auth:<auth_impl>

BREAKING:
- general:pam_module -> auth:pam:module
- general:enable_fingerprint -> auth:fingerprint:enabled
- general:fingerprint_ready_message -> auth:fingerprint:ready_message
- general:fingerprint_present_message ->
auth:fingerprint:present_message

* auth: don't clear password input for fingerprint auth check

* fingerprint: checkAuthenticated when handling verfiy status

* Revert conditionally clearing the password input buffer

Makes sure the input field can show the fail text for fingerprint auth.

* auth: virtual instead of override, remove braces

* pam: join the thread

* auth: remove isAuthenticated and switch to a control flow based unlock

* auth: initialize authentication before aquiring the session lock
This commit is contained in:
Maximilian Seidler 2024-12-16 18:58:36 +00:00 committed by GitHub
parent 4681f8f7f3
commit a4b0562749
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 355 additions and 174 deletions

116
src/auth/Auth.cpp Normal file
View file

@ -0,0 +1,116 @@
#include "Auth.hpp"
#include "Pam.hpp"
#include "Fingerprint.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/hyprlock.hpp"
#include "src/helpers/Log.hpp"
#include <hyprlang.hpp>
#include <memory>
CAuth::CAuth() {
static auto* const PENABLEPAM = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("auth:pam:enabled");
if (**PENABLEPAM)
m_vImpls.push_back(std::make_shared<CPam>());
static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("auth:fingerprint:enabled");
if (**PENABLEFINGERPRINT)
m_vImpls.push_back(std::make_shared<CFingerprint>());
RASSERT(!m_vImpls.empty(), "At least one authentication method must be enabled!");
}
void CAuth::start() {
for (const auto& i : m_vImpls) {
i->init();
}
}
void CAuth::submitInput(const std::string& input) {
for (const auto& i : m_vImpls) {
i->handleInput(input);
}
}
bool CAuth::checkWaiting() {
for (const auto& i : m_vImpls) {
if (i->checkWaiting())
return true;
}
return false;
}
std::string CAuth::getInlineFeedback() {
std::optional<std::string> firstFeedback = std::nullopt;
for (const auto& i : m_vImpls) {
const auto FEEDBACK = (m_bDisplayFailText) ? i->getLastFailText() : i->getLastPrompt();
if (!FEEDBACK.has_value())
continue;
if (!firstFeedback.has_value())
firstFeedback = FEEDBACK;
if (i->getImplType() == m_eLastActiveImpl)
return FEEDBACK.value();
}
return firstFeedback.value_or("Ups, no authentication feedack");
}
std::optional<std::string> CAuth::getFailText(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i->getLastFailText();
}
return std::nullopt;
}
std::optional<std::string> CAuth::getPrompt(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i->getLastPrompt();
}
return std::nullopt;
}
std::shared_ptr<IAuthImplementation> CAuth::getImpl(eAuthImplementations implType) {
for (const auto& i : m_vImpls) {
if (i->getImplType() == implType)
return i;
}
return nullptr;
}
void CAuth::terminate() {
for (const auto& i : m_vImpls) {
i->terminate();
}
}
static void passwordFailCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->clearPasswordBuffer();
g_pAuth->m_iFailedAttempts++;
Debug::log(LOG, "Failed attempts: {}", g_pAuth->m_iFailedAttempts);
g_pAuth->m_bDisplayFailText = true;
g_pHyprlock->enqueueForceUpdateTimers();
g_pHyprlock->renderAllOutputs();
}
static void passwordUnlockCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->unlock();
}
void CAuth::enqueueFail() {
g_pHyprlock->addTimer(std::chrono::milliseconds(0), passwordFailCallback, nullptr);
}
void CAuth::enqueueUnlock() {
g_pHyprlock->addTimer(std::chrono::milliseconds(0), passwordUnlockCallback, nullptr);
}
void CAuth::postActivity(eAuthImplementations implType) {
m_eLastActiveImpl = implType;
}

61
src/auth/Auth.hpp Normal file
View file

@ -0,0 +1,61 @@
#pragma once
#include <memory>
#include <optional>
#include <vector>
enum eAuthImplementations {
AUTH_IMPL_PAM = 0,
AUTH_IMPL_FINGERPRINT = 1,
};
class IAuthImplementation {
public:
virtual ~IAuthImplementation() = default;
virtual eAuthImplementations getImplType() = 0;
virtual void init() = 0;
virtual void handleInput(const std::string& input) = 0;
virtual bool checkWaiting() = 0;
virtual std::optional<std::string> getLastFailText() = 0;
virtual std::optional<std::string> getLastPrompt() = 0;
virtual void terminate() = 0;
friend class CAuth;
};
class CAuth {
public:
CAuth();
void start();
void submitInput(const std::string& input);
bool checkWaiting();
// Used by the PasswordInput field. We are constraint to a single line for the authentication feedback there.
// Based on m_bDisplayFailText, this will return either the fail text or the prompt.
// Based on m_eLastActiveImpl, it will select the implementation.
std::string getInlineFeedback();
std::optional<std::string> getFailText(eAuthImplementations implType);
std::optional<std::string> getPrompt(eAuthImplementations implType);
std::shared_ptr<IAuthImplementation> getImpl(eAuthImplementations implType);
void terminate();
// Should only be set via the main thread
bool m_bDisplayFailText = false;
size_t m_iFailedAttempts = 0;
void enqueueUnlock();
void enqueueFail();
void postActivity(eAuthImplementations implType);
private:
std::vector<std::shared_ptr<IAuthImplementation>> m_vImpls;
std::optional<eAuthImplementations> m_eLastActiveImpl = std::nullopt;
};
inline std::unique_ptr<CAuth> g_pAuth;

View file

@ -1,13 +1,13 @@
#include "Fingerprint.hpp" #include "Fingerprint.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Log.hpp" #include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp" #include "../config/ConfigManager.hpp"
#include <filesystem> #include <memory>
#include <unistd.h> #include <unistd.h>
#include <pwd.h> #include <pwd.h>
#include <cstring> #include <cstring>
#include <thread>
static const auto FPRINT = sdbus::ServiceName{"net.reactivated.Fprint"}; static const auto FPRINT = sdbus::ServiceName{"net.reactivated.Fprint"};
static const auto DEVICE = sdbus::ServiceName{"net.reactivated.Fprint.Device"}; static const auto DEVICE = sdbus::ServiceName{"net.reactivated.Fprint.Device"};
@ -37,18 +37,17 @@ static std::map<std::string, MatchResult> s_mapStringToTestType = {{"verify-no-m
{"verify-unknown-error", MATCH_UNKNOWN_ERROR}}; {"verify-unknown-error", MATCH_UNKNOWN_ERROR}};
CFingerprint::CFingerprint() { CFingerprint::CFingerprint() {
static auto* const PFINGERPRINTREADY = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:fingerprint_ready_message")); static auto* const PFINGERPRINTREADY = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("auth:fingerprint:ready_message"));
m_sFingerprintReady = *PFINGERPRINTREADY; m_sFingerprintReady = *PFINGERPRINTREADY;
static auto* const PFINGERPRINTPRESENT = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:fingerprint_present_message")); static auto* const PFINGERPRINTPRESENT = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("auth:fingerprint:present_message"));
m_sFingerprintPresent = *PFINGERPRINTPRESENT; m_sFingerprintPresent = *PFINGERPRINTPRESENT;
static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:enable_fingerprint");
m_bEnabled = **PENABLEFINGERPRINT;
} }
std::shared_ptr<sdbus::IConnection> CFingerprint::start() { CFingerprint::~CFingerprint() {
if (!m_bEnabled) ;
return {}; }
void CFingerprint::init() {
m_sDBUSState.connection = sdbus::createSystemBusConnection(); m_sDBUSState.connection = sdbus::createSystemBusConnection();
m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"}); m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection, sdbus::ServiceName{"org.freedesktop.login1"}, sdbus::ObjectPath{"/org/freedesktop/login1"});
m_sDBUSState.login->getPropertyAsync("PreparingForSleep").onInterface(LOGIN_MANAGER).uponReplyInvoke([this](std::optional<sdbus::Error> e, sdbus::Variant preparingForSleep) { m_sDBUSState.login->getPropertyAsync("PreparingForSleep").onInterface(LOGIN_MANAGER).uponReplyInvoke([this](std::optional<sdbus::Error> e, sdbus::Variant preparingForSleep) {
@ -75,22 +74,33 @@ std::shared_ptr<sdbus::IConnection> CFingerprint::start() {
startVerify(); startVerify();
} }
}); });
return m_sDBUSState.connection;
} }
bool CFingerprint::isAuthenticated() { void CFingerprint::handleInput(const std::string& input) {
return m_bAuthenticated; ;
} }
std::optional<std::string> CFingerprint::getLastMessage() { std::optional<std::string> CFingerprint::getLastFailText() {
return m_sDBUSState.message.empty() ? std::nullopt : std::optional(m_sDBUSState.message); return m_sDBUSState.message.empty() ? std::nullopt : std::optional(m_sDBUSState.message);
} }
std::optional<std::string> CFingerprint::getLastPrompt() {
return std::nullopt;
}
bool CFingerprint::checkWaiting() {
return false;
}
void CFingerprint::terminate() { void CFingerprint::terminate() {
if (!m_sDBUSState.abort) if (!m_sDBUSState.abort)
releaseDevice(); releaseDevice();
} }
std::shared_ptr<sdbus::IConnection> CFingerprint::getConnection() {
return m_sDBUSState.connection;
}
void CFingerprint::inhibitSleep() { void CFingerprint::inhibitSleep() {
m_sDBUSState.login->callMethodAsync("Inhibit") m_sDBUSState.login->callMethodAsync("Inhibit")
.onInterface(LOGIN_MANAGER) .onInterface(LOGIN_MANAGER)
@ -139,8 +149,10 @@ bool CFingerprint::createDeviceProxy() {
} }
void CFingerprint::handleVerifyStatus(const std::string& result, bool done) { void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
g_pAuth->postActivity(AUTH_IMPL_FINGERPRINT);
Debug::log(LOG, "fprint: handling status {}", result); Debug::log(LOG, "fprint: handling status {}", result);
auto matchResult = s_mapStringToTestType[result]; auto matchResult = s_mapStringToTestType[result];
bool authenticated = false;
if (m_sDBUSState.sleeping && matchResult != MATCH_DISCONNECTED) if (m_sDBUSState.sleeping && matchResult != MATCH_DISCONNECTED)
return; return;
switch (matchResult) { switch (matchResult) {
@ -148,21 +160,22 @@ void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
case MATCH_NO_MATCH: case MATCH_NO_MATCH:
stopVerify(); stopVerify();
if (m_sDBUSState.retries >= 3) { if (m_sDBUSState.retries >= 3) {
m_sDBUSState.message = "Fingerprint auth disabled: too many failed attempts"; m_sDBUSState.message = "Fingerprint auth disabled (too many failed attempts)";
} else { } else {
done = false; done = false;
startVerify(true); startVerify(true);
m_sDBUSState.message = "Fingerprint not matched";
} }
break; break;
case MATCH_UNKNOWN_ERROR: case MATCH_UNKNOWN_ERROR:
stopVerify(); stopVerify();
m_sDBUSState.message = "Unknown fingerprint error, disabling fingerprint auth"; m_sDBUSState.message = "Fingerprint auth disabled (unknown error)";
break; break;
case MATCH_MATCHED: case MATCH_MATCHED:
stopVerify(); stopVerify();
m_bAuthenticated = true;
m_sDBUSState.message = ""; m_sDBUSState.message = "";
g_pHyprlock->unlock(); authenticated = true;
g_pAuth->enqueueUnlock();
break; break;
case MATCH_RETRY: m_sDBUSState.message = "Please retry fingerprint scan"; break; case MATCH_RETRY: m_sDBUSState.message = "Please retry fingerprint scan"; break;
case MATCH_SWIPE_TOO_SHORT: m_sDBUSState.message = "Swipe too short - try again"; break; case MATCH_SWIPE_TOO_SHORT: m_sDBUSState.message = "Swipe too short - try again"; break;
@ -173,7 +186,10 @@ void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
m_sDBUSState.abort = true; m_sDBUSState.abort = true;
break; break;
} }
g_pHyprlock->enqueueForceUpdateTimers();
if (!authenticated)
g_pAuth->enqueueFail();
if (done || m_sDBUSState.abort) if (done || m_sDBUSState.abort)
m_sDBUSState.done = true; m_sDBUSState.done = true;
} }
@ -203,7 +219,7 @@ void CFingerprint::startVerify(bool isRetry) {
if (e) { if (e) {
Debug::log(WARN, "fprint: could not start verifying, {}", e->what()); Debug::log(WARN, "fprint: could not start verifying, {}", e->what());
if (isRetry) if (isRetry)
m_sDBUSState.message = "Fingerprint auth disabled: could not restart verification"; m_sDBUSState.message = "Fingerprint auth disabled (failed to restart)";
} else { } else {
Debug::log(LOG, "fprint: started verifying"); Debug::log(LOG, "fprint: started verifying");
if (isRetry) { if (isRetry) {

View file

@ -1,20 +1,28 @@
#pragma once #pragma once
#include "hyprlock.hpp" #include "Auth.hpp"
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <sdbus-c++/sdbus-c++.h> #include <sdbus-c++/sdbus-c++.h>
class CFingerprint { class CFingerprint : public IAuthImplementation {
public: public:
CFingerprint(); CFingerprint();
std::shared_ptr<sdbus::IConnection> start(); virtual ~CFingerprint();
bool isAuthenticated(); virtual eAuthImplementations getImplType() {
std::optional<std::string> getLastMessage(); return AUTH_IMPL_FINGERPRINT;
void terminate(); }
virtual void init();
virtual void handleInput(const std::string& input);
virtual bool checkWaiting();
virtual std::optional<std::string> getLastFailText();
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
std::shared_ptr<sdbus::IConnection> getConnection();
private: private:
struct SDBUSState { struct SDBUSState {
@ -33,8 +41,6 @@ class CFingerprint {
std::string m_sFingerprintReady; std::string m_sFingerprintReady;
std::string m_sFingerprintPresent; std::string m_sFingerprintPresent;
bool m_bAuthenticated = false;
bool m_bEnabled = false;
void handleVerifyStatus(const std::string& result, const bool done); void handleVerifyStatus(const std::string& result, const bool done);
@ -46,5 +52,3 @@ class CFingerprint {
bool stopVerify(); bool stopVerify();
bool releaseDevice(); bool releaseDevice();
}; };
inline std::unique_ptr<CFingerprint> g_pFingerprint;

View file

@ -1,7 +1,7 @@
#include "Auth.hpp" #include "Pam.hpp"
#include "hyprlock.hpp" #include "../core/hyprlock.hpp"
#include "../helpers/Log.hpp" #include "../helpers/Log.hpp"
#include "src/config/ConfigManager.hpp" #include "../config/ConfigManager.hpp"
#include <filesystem> #include <filesystem>
#include <unistd.h> #include <unistd.h>
@ -15,7 +15,7 @@
#include <thread> #include <thread>
int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) {
const auto CONVERSATIONSTATE = (CAuth::SPamConversationState*)appdata_ptr; const auto CONVERSATIONSTATE = (CPam::SPamConversationState*)appdata_ptr;
struct pam_response* pamReply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response)); struct pam_response* pamReply = (struct pam_response*)calloc(num_msg, sizeof(struct pam_response));
bool initialPrompt = true; bool initialPrompt = true;
@ -34,7 +34,7 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
// When the prompt is the same as the last one, I guess our answer can be the same. // When the prompt is the same as the last one, I guess our answer can be the same.
if (!initialPrompt && PROMPTCHANGED) { if (!initialPrompt && PROMPTCHANGED) {
CONVERSATIONSTATE->prompt = PROMPT; CONVERSATIONSTATE->prompt = PROMPT;
g_pAuth->waitForInput(); CONVERSATIONSTATE->waitForInput();
} }
// Needed for unlocks via SIGUSR1 // Needed for unlocks via SIGUSR1
@ -60,22 +60,25 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
return PAM_SUCCESS; return PAM_SUCCESS;
} }
CAuth::CAuth() { CPam::CPam() {
static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("general:pam_module")); static auto* const PPAMMODULE = (Hyprlang::STRING*)(g_pConfigManager->getValuePtr("auth:pam:module"));
m_sPamModule = *PPAMMODULE; m_sPamModule = *PPAMMODULE;
if (!std::filesystem::exists(std::filesystem::path("/etc/pam.d/") / m_sPamModule)) { if (!std::filesystem::exists(std::filesystem::path("/etc/pam.d/") / m_sPamModule)) {
Debug::log(ERR, "Pam module \"/etc/pam.d/{}\" does not exist! Falling back to \"/etc/pam.d/su\"", m_sPamModule); Debug::log(ERR, "Pam module \"/etc/pam.d/{}\" does not exist! Falling back to \"/etc/pam.d/su\"", m_sPamModule);
m_sPamModule = "su"; m_sPamModule = "su";
} }
m_sConversationState.waitForInput = [this]() { this->waitForInput(); };
} }
static void passwordCheckTimerCallback(std::shared_ptr<CTimer> self, void* data) { CPam::~CPam() {
g_pHyprlock->onPasswordCheckTimer(); ;
} }
void CAuth::start() { void CPam::init() {
std::thread([this]() { m_thread = std::thread([this]() {
while (true) {
resetConversation(); resetConversation();
// Initial input // Initial input
@ -87,17 +90,22 @@ void CAuth::start() {
return; return;
const auto AUTHENTICATED = auth(); const auto AUTHENTICATED = auth();
m_bAuthenticated = AUTHENTICATED;
// For SIGUSR1 unlocks // For SIGUSR1 unlocks
if (g_pHyprlock->isUnlocked()) if (g_pHyprlock->isUnlocked())
return; return;
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr); if (!AUTHENTICATED)
}).detach(); g_pAuth->enqueueFail();
else {
g_pAuth->enqueueUnlock();
return;
}
}
});
} }
bool CAuth::auth() { bool CPam::auth() {
const pam_conv localConv = {conv, (void*)&m_sConversationState}; const pam_conv localConv = {conv, (void*)&m_sConversationState};
pam_handle_t* handle = NULL; pam_handle_t* handle = NULL;
auto uidPassword = getpwuid(getuid()); auto uidPassword = getpwuid(getuid());
@ -115,6 +123,7 @@ bool CAuth::auth() {
handle = nullptr; handle = nullptr;
m_sConversationState.waitingForPamAuth = false; m_sConversationState.waitingForPamAuth = false;
g_pAuth->postActivity(AUTH_IMPL_PAM);
if (ret != PAM_SUCCESS) { if (ret != PAM_SUCCESS) {
if (!m_sConversationState.failTextFromPam) if (!m_sConversationState.failTextFromPam)
@ -129,16 +138,12 @@ bool CAuth::auth() {
return true; return true;
} }
bool CAuth::isAuthenticated() {
return m_bAuthenticated;
}
// clearing the input must be done from the main thread // clearing the input must be done from the main thread
static void clearInputTimerCallback(std::shared_ptr<CTimer> self, void* data) { static void clearInputTimerCallback(std::shared_ptr<CTimer> self, void* data) {
g_pHyprlock->clearPasswordBuffer(); g_pHyprlock->clearPasswordBuffer();
} }
void CAuth::waitForInput() { void CPam::waitForInput() {
g_pHyprlock->addTimer(std::chrono::milliseconds(1), clearInputTimerCallback, nullptr); g_pHyprlock->addTimer(std::chrono::milliseconds(1), clearInputTimerCallback, nullptr);
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex); std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
@ -149,7 +154,8 @@ void CAuth::waitForInput() {
m_bBlockInput = true; m_bBlockInput = true;
} }
void CAuth::submitInput(std::string input) { void CPam::handleInput(const std::string& input) {
g_pAuth->postActivity(AUTH_IMPL_PAM);
std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex); std::unique_lock<std::mutex> lk(m_sConversationState.inputMutex);
if (!m_sConversationState.inputRequested) if (!m_sConversationState.inputRequested)
@ -161,23 +167,25 @@ void CAuth::submitInput(std::string input) {
m_sConversationState.inputSubmittedCondition.notify_all(); m_sConversationState.inputSubmittedCondition.notify_all();
} }
std::optional<std::string> CAuth::getLastFailText() { std::optional<std::string> CPam::getLastFailText() {
return m_sConversationState.failText.empty() ? std::nullopt : std::optional(m_sConversationState.failText); return m_sConversationState.failText.empty() ? std::nullopt : std::optional(m_sConversationState.failText);
} }
std::optional<std::string> CAuth::getLastPrompt() { std::optional<std::string> CPam::getLastPrompt() {
return m_sConversationState.prompt.empty() ? std::nullopt : std::optional(m_sConversationState.prompt); return m_sConversationState.prompt.empty() ? std::nullopt : std::optional(m_sConversationState.prompt);
} }
bool CAuth::checkWaiting() { bool CPam::checkWaiting() {
return m_bBlockInput || m_sConversationState.waitingForPamAuth; return m_bBlockInput || m_sConversationState.waitingForPamAuth;
} }
void CAuth::terminate() { void CPam::terminate() {
m_sConversationState.inputSubmittedCondition.notify_all(); m_sConversationState.inputSubmittedCondition.notify_all();
if (m_thread.joinable())
m_thread.join();
} }
void CAuth::resetConversation() { void CPam::resetConversation() {
m_sConversationState.input = ""; m_sConversationState.input = "";
m_sConversationState.waitingForPamAuth = false; m_sConversationState.waitingForPamAuth = false;
m_sConversationState.inputRequested = false; m_sConversationState.inputRequested = false;

52
src/auth/Pam.hpp Normal file
View file

@ -0,0 +1,52 @@
#pragma once
#include "Auth.hpp"
#include <optional>
#include <string>
#include <mutex>
#include <condition_variable>
#include <functional>
class CPam : public IAuthImplementation {
public:
struct SPamConversationState {
std::string input = "";
std::string prompt = "";
std::string failText = "";
std::mutex inputMutex;
std::condition_variable inputSubmittedCondition;
bool waitingForPamAuth = false;
bool inputRequested = false;
bool failTextFromPam = false;
std::function<void()> waitForInput = []() {};
};
CPam();
void waitForInput();
virtual ~CPam();
virtual eAuthImplementations getImplType() {
return AUTH_IMPL_PAM;
}
virtual void init();
virtual void handleInput(const std::string& input);
virtual bool checkWaiting();
virtual std::optional<std::string> getLastFailText();
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
private:
std::thread m_thread;
SPamConversationState m_sConversationState;
bool m_bBlockInput = true;
std::string m_sPamModule;
bool auth();
void resetConversation();
};

View file

@ -171,11 +171,13 @@ void CConfigManager::init() {
m_config.addConfigValue("general:no_fade_out", 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:ignore_empty_input", Hyprlang::INT{0});
m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0}); m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0});
m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"hyprlock"});
m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2}); m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2});
m_config.addConfigValue("general:enable_fingerprint", Hyprlang::INT{0});
m_config.addConfigValue("general:fingerprint_ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"}); m_config.addConfigValue("auth:pam:enabled", Hyprlang::INT{1});
m_config.addConfigValue("general:fingerprint_present_message", Hyprlang::STRING{"Scanning fingerprint"}); m_config.addConfigValue("auth:pam:module", Hyprlang::STRING{"hyprlock"});
m_config.addConfigValue("auth:fingerprint:enabled", Hyprlang::INT{0});
m_config.addConfigValue("auth:fingerprint:ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"});
m_config.addConfigValue("auth:fingerprint:present_message", Hyprlang::STRING{"Scanning fingerprint"});
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});

View file

@ -1,54 +0,0 @@
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <mutex>
#include <condition_variable>
class CAuth {
public:
struct SPamConversationState {
std::string input = "";
std::string prompt = "";
std::string failText = "";
std::mutex inputMutex;
std::condition_variable inputSubmittedCondition;
bool waitingForPamAuth = false;
bool inputRequested = false;
bool failTextFromPam = false;
};
CAuth();
void start();
bool auth();
bool isAuthenticated();
void waitForInput();
void submitInput(std::string input);
std::optional<std::string> getLastFailText();
std::optional<std::string> getLastPrompt();
bool checkWaiting();
void terminate();
// Should only be set via the main thread
bool m_bDisplayFailText = false;
private:
SPamConversationState m_sConversationState;
bool m_bBlockInput = true;
bool m_bAuthenticated = false;
std::string m_sPamModule;
void resetConversation();
};
inline std::unique_ptr<CAuth> g_pAuth;

View file

@ -2,9 +2,9 @@
#include "../helpers/Log.hpp" #include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp" #include "../config/ConfigManager.hpp"
#include "../renderer/Renderer.hpp" #include "../renderer/Renderer.hpp"
#include "Auth.hpp" #include "../auth/Auth.hpp"
#include "../auth/Fingerprint.hpp"
#include "Egl.hpp" #include "Egl.hpp"
#include "Fingerprint.hpp"
#include "linux-dmabuf-unstable-v1-protocol.h" #include "linux-dmabuf-unstable-v1-protocol.h"
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/poll.h> #include <sys/poll.h>
@ -388,6 +388,8 @@ void CHyprlock::run() {
wl_display_roundtrip(m_sWaylandState.display); wl_display_roundtrip(m_sWaylandState.display);
g_pRenderer = std::make_unique<CRenderer>(); g_pRenderer = std::make_unique<CRenderer>();
g_pAuth = std::make_unique<CAuth>();
g_pAuth->start();
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out"); static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
const bool NOFADEOUT = **PNOFADEOUT; const bool NOFADEOUT = **PNOFADEOUT;
@ -419,11 +421,8 @@ void CHyprlock::run() {
exit(1); exit(1);
} }
g_pAuth = std::make_unique<CAuth>(); const auto fingerprintAuth = g_pAuth->getImpl(AUTH_IMPL_FINGERPRINT);
g_pAuth->start(); const auto dbusConn = (fingerprintAuth) ? ((CFingerprint*)fingerprintAuth.get())->getConnection() : nullptr;
g_pFingerprint = std::make_unique<CFingerprint>();
std::shared_ptr<sdbus::IConnection> conn = g_pFingerprint->start();
registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART); registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART);
registerSignalAction(SIGUSR2, handleForceUpdateSignal); registerSignalAction(SIGUSR2, handleForceUpdateSignal);
@ -438,13 +437,13 @@ void CHyprlock::run() {
.fd = wl_display_get_fd(m_sWaylandState.display), .fd = wl_display_get_fd(m_sWaylandState.display),
.events = POLLIN, .events = POLLIN,
}; };
if (conn) { if (dbusConn) {
pollfds[1] = { pollfds[1] = {
.fd = conn->getEventLoopPollData().fd, .fd = dbusConn->getEventLoopPollData().fd,
.events = POLLIN, .events = POLLIN,
}; };
} }
size_t fdcount = conn ? 2 : 1; size_t fdcount = dbusConn ? 2 : 1;
std::thread pollThr([this, &pollfds, fdcount]() { std::thread pollThr([this, &pollfds, fdcount]() {
while (!m_bTerminate) { while (!m_bTerminate) {
@ -524,7 +523,7 @@ void CHyprlock::run() {
m_sLoopState.event = false; m_sLoopState.event = false;
if (pollfds[1].revents & POLLIN /* dbus */) { if (pollfds[1].revents & POLLIN /* dbus */) {
while (conn && conn->processPendingEvent()) { while (dbusConn && dbusConn->processPendingEvent()) {
; ;
} }
} }
@ -592,7 +591,6 @@ void CHyprlock::run() {
pthread_kill(pollThr.native_handle(), SIGRTMIN); pthread_kill(pollThr.native_handle(), SIGRTMIN);
g_pAuth->terminate(); g_pAuth->terminate();
g_pFingerprint->terminate();
// wait for threads to exit cleanly to avoid a coredump // wait for threads to exit cleanly to avoid a coredump
pollThr.join(); pollThr.join();
@ -811,24 +809,6 @@ static const ext_session_lock_v1_listener sessionLockListener = {
// end session_lock // end session_lock
void CHyprlock::onPasswordCheckTimer() {
// check result
if (g_pAuth->isAuthenticated()) {
unlock();
} else {
m_sPasswordState.passBuffer = "";
m_sPasswordState.failedAttempts += 1;
Debug::log(LOG, "Failed attempts: {}", m_sPasswordState.failedAttempts);
g_pAuth->m_bDisplayFailText = true;
forceUpdateTimers();
g_pAuth->start();
renderAllOutputs();
}
}
void CHyprlock::clearPasswordBuffer() { void CHyprlock::clearPasswordBuffer() {
if (m_sPasswordState.passBuffer.empty()) if (m_sPasswordState.passBuffer.empty())
return; return;
@ -1087,10 +1067,6 @@ size_t CHyprlock::getPasswordBufferDisplayLen() {
return std::count_if(m_sPasswordState.passBuffer.begin(), m_sPasswordState.passBuffer.end(), [](char c) { return (c & 0xc0) != 0x80; }); return std::count_if(m_sPasswordState.passBuffer.begin(), m_sPasswordState.passBuffer.end(), [](char c) { return (c & 0xc0) != 0x80; });
} }
size_t CHyprlock::getPasswordFailedAttempts() {
return m_sPasswordState.failedAttempts;
}
std::shared_ptr<CTimer> CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data, std::shared_ptr<CTimer> CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(std::shared_ptr<CTimer> self, void* data)> cb_, void* data,
bool force) { bool force) {
std::lock_guard<std::mutex> lg(m_sLoopState.timersMutex); std::lock_guard<std::mutex> lg(m_sLoopState.timersMutex);

View file

@ -68,7 +68,6 @@ class CHyprlock {
size_t getPasswordBufferLen(); size_t getPasswordBufferLen();
size_t getPasswordBufferDisplayLen(); size_t getPasswordBufferDisplayLen();
size_t getPasswordFailedAttempts();
ext_session_lock_manager_v1* getSessionLockMgr(); ext_session_lock_manager_v1* getSessionLockMgr();
ext_session_lock_v1* getSessionLock(); ext_session_lock_v1* getSessionLock();

View file

@ -1,8 +1,8 @@
#include "IWidget.hpp" #include "IWidget.hpp"
#include "../../helpers/Log.hpp" #include "../../helpers/Log.hpp"
#include "../../core/hyprlock.hpp" #include "../../core/hyprlock.hpp"
#include "../../core/Auth.hpp" #include "../../auth/Auth.hpp"
#include "../../core/Fingerprint.hpp" #include "../../auth/Fingerprint.hpp"
#include <chrono> #include <chrono>
#include <unistd.h> #include <unistd.h>
#include <pwd.h> #include <pwd.h>
@ -58,7 +58,7 @@ Vector2D IWidget::posFromHVAlign(const Vector2D& viewport, const Vector2D& size,
static void replaceAllAttempts(std::string& str) { static void replaceAllAttempts(std::string& str) {
const size_t ATTEMPTS = g_pHyprlock->getPasswordFailedAttempts(); const size_t ATTEMPTS = g_pAuth->m_iFailedAttempts;
const std::string STR = std::to_string(ATTEMPTS); const std::string STR = std::to_string(ATTEMPTS);
size_t pos = 0; size_t pos = 0;
@ -138,7 +138,8 @@ static std::string getTime12h() {
const auto HRS = hhmmss.hours().count(); const auto HRS = hhmmss.hours().count();
const auto MINS = hhmmss.minutes().count(); const auto MINS = hhmmss.minutes().count();
return (HRS == 12 || HRS == 0 ? "12" : (HRS % 12 < 10 ? "0" : "") + std::to_string(HRS % 12)) + ":" + (MINS < 10 ? "0" : "") + std::to_string(MINS) + (HRS < 12 ? " AM" : " PM"); return (HRS == 12 || HRS == 0 ? "12" : (HRS % 12 < 10 ? "0" : "") + std::to_string(HRS % 12)) + ":" + (MINS < 10 ? "0" : "") + std::to_string(MINS) +
(HRS < 12 ? " AM" : " PM");
} }
IWidget::SFormatResult IWidget::formatString(std::string in) { IWidget::SFormatResult IWidget::formatString(std::string in) {
@ -169,14 +170,14 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
} }
if (in.contains("$FAIL")) { if (in.contains("$FAIL")) {
const auto FAIL = g_pAuth->getLastFailText(); const auto FAIL = g_pAuth->getFailText(AUTH_IMPL_PAM);
replaceInString(in, "$FAIL", FAIL.has_value() ? FAIL.value() : ""); replaceInString(in, "$FAIL", FAIL.value_or(""));
result.allowForceUpdate = true; result.allowForceUpdate = true;
} }
if (in.contains("$PROMPT")) { if (in.contains("$PROMPT")) {
const auto PROMPT = g_pAuth->getLastPrompt(); const auto PROMPT = g_pAuth->getPrompt(AUTH_IMPL_PAM);
replaceInString(in, "$PROMPT", PROMPT.has_value() ? PROMPT.value() : ""); replaceInString(in, "$PROMPT", PROMPT.value_or(""));
result.allowForceUpdate = true; result.allowForceUpdate = true;
} }
@ -191,8 +192,8 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
} }
if (in.contains("$FPRINTMESSAGE")) { if (in.contains("$FPRINTMESSAGE")) {
const auto FPRINTMESSAGE = g_pFingerprint->getLastMessage(); const auto FPRINTMESSAGE = g_pAuth->getFailText(AUTH_IMPL_FINGERPRINT);
replaceInString(in, "$FPRINTMESSAGE", FPRINTMESSAGE.has_value() ? FPRINTMESSAGE.value() : ""); replaceInString(in, "$FPRINTMESSAGE", FPRINTMESSAGE.value_or(""));
result.allowForceUpdate = true; result.allowForceUpdate = true;
} }

View file

@ -1,7 +1,7 @@
#include "PasswordInputField.hpp" #include "PasswordInputField.hpp"
#include "../Renderer.hpp" #include "../Renderer.hpp"
#include "../../core/hyprlock.hpp" #include "../../core/hyprlock.hpp"
#include "../../core/Auth.hpp" #include "../../auth/Auth.hpp"
#include "../../config/ConfigDataValues.hpp" #include "../../config/ConfigDataValues.hpp"
#include "../../helpers/Log.hpp" #include "../../helpers/Log.hpp"
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
@ -334,13 +334,13 @@ void CPasswordInputField::updatePlaceholder() {
return; return;
} }
const auto AUTHFEEDBACK = g_pAuth->m_bDisplayFailText ? g_pAuth->getLastFailText().value_or("Ups, no fail text?") : g_pAuth->getLastPrompt().value_or("Ups, no prompt?"); const auto AUTHFEEDBACK = g_pAuth->getInlineFeedback();
const auto ALLOWCOLORSWAP = outThick == 0 && colorConfig.swapFont; const auto ALLOWCOLORSWAP = outThick == 0 && colorConfig.swapFont;
if (!ALLOWCOLORSWAP && placeholder.lastAuthFeedback == AUTHFEEDBACK && g_pHyprlock->getPasswordFailedAttempts() == placeholder.failedAttempts) if (!ALLOWCOLORSWAP && placeholder.lastAuthFeedback == AUTHFEEDBACK && g_pAuth->m_iFailedAttempts == placeholder.failedAttempts)
return; return;
placeholder.failedAttempts = g_pHyprlock->getPasswordFailedAttempts(); placeholder.failedAttempts = g_pAuth->m_iFailedAttempts;
placeholder.lastAuthFeedback = AUTHFEEDBACK; placeholder.lastAuthFeedback = AUTHFEEDBACK;
placeholder.asset = nullptr; placeholder.asset = nullptr;