mirror of
https://github.com/hyprwm/hyprlock.git
synced 2026-05-20 11:48:07 +02:00
auth: start PAM authentication immediately without pre-collecting input
Previously, CPam::init() hardcoded a "Password: " prompt and blocked on waitForInput() before ever calling pam_authenticate(). This meant non-interactive PAM modules (howdy face recognition via pam_python, FIDO2 devices) could never run until the user typed something and pressed Enter — defeating the purpose of passwordless auth. Now pam_authenticate() fires immediately on lock. Non-interactive modules run first without needing the PAM conversation. If they succeed (e.g. face match), the screen unlocks instantly with no keypress. If they fail, subsequent modules (pam_unix) trigger the conv() callback which blocks for password input at that point. The conv() callback no longer skips waitForInput() for the "initial" prompt, since input is no longer pre-collected. Every PAM prompt now properly blocks until the user submits input. Fixes #926 Related: #535 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d332164dd9
commit
732069bf98
1 changed files with 14 additions and 16 deletions
|
|
@ -17,32 +17,28 @@
|
|||
int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) {
|
||||
const auto CONVERSATIONSTATE = (CPam::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: {
|
||||
const auto PROMPT = std::string(msg[i]->msg);
|
||||
const auto PROMPTCHANGED = PROMPT != CONVERSATIONSTATE->prompt;
|
||||
const auto PROMPT = std::string(msg[i]->msg);
|
||||
Log::logger->log(Log::INFO, "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 && PROMPTCHANGED) {
|
||||
CONVERSATIONSTATE->prompt = PROMPT;
|
||||
CONVERSATIONSTATE->waitForInput();
|
||||
}
|
||||
// Update the prompt and wait for user input. Since
|
||||
// pam_authenticate runs immediately (not after pre-collecting
|
||||
// input), every prompt from a PAM module must block here.
|
||||
// Non-interactive modules (face auth, FIDO2) never send
|
||||
// prompts, so this only fires for password-based modules.
|
||||
CONVERSATIONSTATE->prompt = PROMPT;
|
||||
g_pHyprlock->enqueueForceUpdateTimers();
|
||||
CONVERSATIONSTATE->waitForInput();
|
||||
|
||||
// Needed for unlocks via SIGUSR1
|
||||
if (g_pHyprlock->isUnlocked())
|
||||
return PAM_CONV_ERR;
|
||||
|
||||
pamReply[i].resp = strdup(CONVERSATIONSTATE->input.c_str());
|
||||
initialPrompt = false;
|
||||
} break;
|
||||
case PAM_ERROR_MSG: Log::logger->log(Log::ERR, "PAM: {}", msg[i]->msg); break;
|
||||
case PAM_TEXT_INFO:
|
||||
|
|
@ -83,9 +79,11 @@ void CPam::init() {
|
|||
while (true) {
|
||||
resetConversation();
|
||||
|
||||
// Initial input
|
||||
m_sConversationState.prompt = "Password: ";
|
||||
waitForInput();
|
||||
// Start PAM authentication immediately. Non-interactive modules
|
||||
// (e.g. pam_python/howdy for face recognition, FIDO2) run first
|
||||
// without needing user input. If they succeed, we unlock instantly.
|
||||
// If they fail, pam_unix will trigger the conv() callback which
|
||||
// blocks for password input at that point.
|
||||
|
||||
// For grace or SIGUSR1 unlocks
|
||||
if (g_pHyprlock->isUnlocked())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue