1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-20 18:15:24 -04:00

fix(lock): show the faillock lockout reason instead of "incorrect password" (#2669)

When pam_faillock locks the account (DMS authenticates through the system PAM
stack: /etc/pam.d/login -> system-auth), the lock screen kept showing the
generic "Incorrect password - try again" even though the real cause is a
lockout, so a correct password looks rejected and only a reboot (which clears
the tmpfs /run/faillock tally) appears to help. See #2647.

The previous onMessageChanged only matched the *English* faillock strings
("The account is locked ...") and then wiped that text again on the trailing
pam_unix "Password:" prompt. On a non-English system (e.g. German) the strings
never matched, so the lockout was never surfaced at all.

Detect the notice by position rather than by text: pam emits its informational
messages within an attempt before the password prompt. Collect every non-prompt
info message and, once the prompt arrives, surface the collected lines (minus
the prompt itself) as lockMessage. If the stack short-circuits without ever
prompting (e.g. pam_faillock preauth configured as requisite), the notice is
surfaced on completion instead. This is locale-independent. A per-attempt flag
keeps the message stable across repeated locked attempts and retires it when an
attempt completes without a lockout (faillock reset / unlock_time elapsed).

Fixes #2647
This commit is contained in:
Rocho
2026-06-18 15:54:02 +02:00
committed by GitHub
parent 475ef5d1ca
commit 097290f7da
+28 -7
View File
@@ -23,6 +23,9 @@ Scope {
property string u2fPendingMode
property string buffer
property var attemptInfoMessages: []
property bool lockoutAnnouncedThisAttempt: false
signal flashMsg
signal unlockRequested
@@ -118,23 +121,37 @@ Scope {
configDirectory: (dankshellConfigWatcher.loaded || nixosMarker.loaded || root.runningFromNixStore) ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
onMessageChanged: {
if (message.startsWith("The account is locked")) {
root.lockMessage = message;
} else if (root.lockMessage && message.endsWith(" left to unlock)")) {
root.lockMessage += "\n" + message;
} else if (root.lockMessage && message && message.length > 0) {
root.lockMessage = "";
}
// collected by position, not text, so it works in any locale
if (message.length > 0 && !responseRequired)
root.attemptInfoMessages = root.attemptInfoMessages.concat([message]);
}
onResponseRequiredChanged: {
if (!responseRequired)
return;
const notice = root.attemptInfoMessages.filter(m => m !== message);
if (notice.length > 0) {
root.lockMessage = notice.join("\n");
root.lockoutAnnouncedThisAttempt = true;
}
root.attemptInfoMessages = [];
respond(root.buffer);
}
onCompleted: res => {
// requisite preauth can lock without ever prompting; surface it here too
if (!root.lockoutAnnouncedThisAttempt) {
if (root.attemptInfoMessages.length > 0) {
root.lockMessage = root.attemptInfoMessages.join("\n");
root.lockoutAnnouncedThisAttempt = true;
} else {
root.lockMessage = "";
}
root.attemptInfoMessages = [];
}
if (res === PamResult.Success) {
if (!root.unlockInProgress) {
fprint.abort();
@@ -168,6 +185,8 @@ Scope {
function onActiveChanged() {
if (passwd.active) {
root.attemptInfoMessages = [];
root.lockoutAnnouncedThisAttempt = false;
passwdActiveTimeout.restart();
} else {
passwdActiveTimeout.running = false;
@@ -393,6 +412,8 @@ Scope {
root.u2fPending = false;
root.u2fPendingMode = "";
root.lockMessage = "";
root.attemptInfoMessages = [];
root.lockoutAnnouncedThisAttempt = false;
root.resetAuthFlows();
fprint.checkAvail();
u2f.checkAvail();