mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-03 20:32:07 -04:00
fix(lock): Restore system PAM fallback, faillock support, and auth feedback
- Re-add loginConfigWatcher so installs can still fall through to /etc/pam.d instead of the bundled PAM assets - Add login-faillock bundled PAM asset at runtime. Use it as the bundled fallback when dankshell config is absent - Fix invalid bare property writes (u2fPending, u2fState, unlockInProgress, state) in Pam.qml - Improve lockscreen auth feedback
This commit is contained in:
@@ -505,6 +505,7 @@ Singleton {
|
|||||||
|
|
||||||
property bool enableFprint: false
|
property bool enableFprint: false
|
||||||
property int maxFprintTries: 15
|
property int maxFprintTries: 15
|
||||||
|
property bool lockFaillockSupported: false
|
||||||
property bool fprintdAvailable: false
|
property bool fprintdAvailable: false
|
||||||
property bool lockFingerprintCanEnable: false
|
property bool lockFingerprintCanEnable: false
|
||||||
property bool lockFingerprintReady: false
|
property bool lockFingerprintReady: false
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Singleton {
|
|||||||
property int pamSupportProbeExitCode: 0
|
property int pamSupportProbeExitCode: 0
|
||||||
property bool pamFprintSupportDetected: false
|
property bool pamFprintSupportDetected: false
|
||||||
property bool pamU2fSupportDetected: false
|
property bool pamU2fSupportDetected: false
|
||||||
|
property bool pamFaillockSupportDetected: false
|
||||||
|
|
||||||
readonly property string homeDir: Quickshell.env("HOME") || ""
|
readonly property string homeDir: Quickshell.env("HOME") || ""
|
||||||
readonly property string u2fKeysPath: homeDir ? homeDir + "/.config/Yubico/u2f_keys" : ""
|
readonly property string u2fKeysPath: homeDir ? homeDir + "/.config/Yubico/u2f_keys" : ""
|
||||||
@@ -70,14 +71,13 @@ Singleton {
|
|||||||
fingerprintProbeState = forcedFprintAvailable ? "ready" : "probe_failed";
|
fingerprintProbeState = forcedFprintAvailable ? "ready" : "probe_failed";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forcedFprintAvailable === null || forcedU2fAvailable === null) {
|
|
||||||
pamFprintSupportDetected = false;
|
pamFprintSupportDetected = false;
|
||||||
pamU2fSupportDetected = false;
|
pamU2fSupportDetected = false;
|
||||||
|
pamFaillockSupportDetected = false;
|
||||||
pamSupportProbeOutput = "";
|
pamSupportProbeOutput = "";
|
||||||
pamSupportProbeStreamFinished = false;
|
pamSupportProbeStreamFinished = false;
|
||||||
pamSupportProbeExited = false;
|
pamSupportProbeExited = false;
|
||||||
pamSupportDetectionProcess.running = true;
|
pamSupportDetectionProcess.running = true;
|
||||||
}
|
|
||||||
|
|
||||||
recomputeAuthCapabilities();
|
recomputeAuthCapabilities();
|
||||||
}
|
}
|
||||||
@@ -321,6 +321,7 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
recomputeFingerprintCapabilities();
|
recomputeFingerprintCapabilities();
|
||||||
recomputeU2fCapabilities();
|
recomputeU2fCapabilities();
|
||||||
|
settingsRoot.lockFaillockSupported = pamFaillockSupportDetected;
|
||||||
settingsRoot.fprintdAvailable = settingsRoot.lockFingerprintReady || settingsRoot.greeterFingerprintReady;
|
settingsRoot.fprintdAvailable = settingsRoot.lockFingerprintReady || settingsRoot.greeterFingerprintReady;
|
||||||
settingsRoot.u2fAvailable = settingsRoot.lockU2fReady || settingsRoot.greeterU2fReady;
|
settingsRoot.u2fAvailable = settingsRoot.lockU2fReady || settingsRoot.greeterU2fReady;
|
||||||
}
|
}
|
||||||
@@ -338,6 +339,7 @@ Singleton {
|
|||||||
|
|
||||||
pamFprintSupportDetected = false;
|
pamFprintSupportDetected = false;
|
||||||
pamU2fSupportDetected = false;
|
pamU2fSupportDetected = false;
|
||||||
|
pamFaillockSupportDetected = false;
|
||||||
|
|
||||||
const lines = (pamSupportProbeOutput || "").trim().split(/\r?\n/);
|
const lines = (pamSupportProbeOutput || "").trim().split(/\r?\n/);
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
@@ -348,6 +350,8 @@ Singleton {
|
|||||||
pamFprintSupportDetected = parts[1] === "true";
|
pamFprintSupportDetected = parts[1] === "true";
|
||||||
else if (parts[0] === "pam_u2f.so")
|
else if (parts[0] === "pam_u2f.so")
|
||||||
pamU2fSupportDetected = parts[1] === "true";
|
pamU2fSupportDetected = parts[1] === "true";
|
||||||
|
else if (parts[0] === "pam_faillock.so")
|
||||||
|
pamFaillockSupportDetected = parts[1] === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forcedFprintAvailable === null && fingerprintProbeState === "missing_pam_support")
|
if (forcedFprintAvailable === null && fingerprintProbeState === "missing_pam_support")
|
||||||
@@ -401,7 +405,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property var pamSupportDetectionProcess: Process {
|
property var pamSupportDetectionProcess: Process {
|
||||||
command: ["sh", "-c", "for module in pam_fprintd.so pam_u2f.so; do found=false; for dir in /usr/lib64/security /usr/lib/security /lib/security /lib/x86_64-linux-gnu/security /usr/lib/x86_64-linux-gnu/security /usr/lib/aarch64-linux-gnu/security /run/current-system/sw/lib/security; do if [ -f \"$dir/$module\" ]; then found=true; break; fi; done; printf '%s:%s\\n' \"$module\" \"$found\"; done"]
|
command: ["sh", "-c", "for module in pam_fprintd.so pam_u2f.so pam_faillock.so; do found=false; for dir in /usr/lib64/security /usr/lib/security /lib/security /lib/x86_64-linux-gnu/security /usr/lib/x86_64-linux-gnu/security /usr/lib/aarch64-linux-gnu/security /run/current-system/sw/lib/security; do if [ -f \"$dir/$module\" ]; then found=true; break; fi; done; printf '%s:%s\\n' \"$module\" \"$found\"; done"]
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
|
|||||||
@@ -328,6 +328,7 @@ var SPEC = {
|
|||||||
lockAtStartup: { def: false },
|
lockAtStartup: { def: false },
|
||||||
enableFprint: { def: false },
|
enableFprint: { def: false },
|
||||||
maxFprintTries: { def: 15 },
|
maxFprintTries: { def: 15 },
|
||||||
|
lockFaillockSupported: { def: false, persist: false },
|
||||||
fprintdAvailable: { def: false, persist: false },
|
fprintdAvailable: { def: false, persist: false },
|
||||||
lockFingerprintCanEnable: { def: false, persist: false },
|
lockFingerprintCanEnable: { def: false, persist: false },
|
||||||
lockFingerprintReady: { def: false, persist: false },
|
lockFingerprintReady: { def: false, persist: false },
|
||||||
|
|||||||
@@ -39,6 +39,38 @@ Item {
|
|||||||
lockerReadyArmed = true;
|
lockerReadyArmed = true;
|
||||||
unlocking = false;
|
unlocking = false;
|
||||||
pamState = "";
|
pamState = "";
|
||||||
|
if (pam)
|
||||||
|
pam.lockMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentAuthFeedbackText() {
|
||||||
|
if (!pam)
|
||||||
|
return "";
|
||||||
|
if (pam.u2fState === "insert" && !pam.u2fPending)
|
||||||
|
return I18n.tr("Insert your security key...");
|
||||||
|
if (pam.u2fState === "waiting" && !pam.u2fPending)
|
||||||
|
return I18n.tr("Touch your security key...");
|
||||||
|
if (pam.lockMessage && pam.lockMessage.length > 0)
|
||||||
|
return pam.lockMessage;
|
||||||
|
if (pam.fprintState === "error") {
|
||||||
|
const detail = (pam.fprint.message || "").trim();
|
||||||
|
return detail.length > 0 ? I18n.tr("Fingerprint error: %1").arg(detail) : I18n.tr("Fingerprint error");
|
||||||
|
}
|
||||||
|
if (pam.fprintState === "max")
|
||||||
|
return I18n.tr("Maximum fingerprint attempts reached. Please use password.");
|
||||||
|
if (pam.fprintState === "fail")
|
||||||
|
return I18n.tr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(SettingsData.maxFprintTries);
|
||||||
|
if (root.pamState === "error")
|
||||||
|
return I18n.tr("Authentication error - try again");
|
||||||
|
if (root.pamState === "max")
|
||||||
|
return I18n.tr("Too many attempts - locked out");
|
||||||
|
if (root.pamState === "fail")
|
||||||
|
return I18n.tr("Incorrect password - try again");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function authFeedbackIsHint() {
|
||||||
|
return pam && (pam.u2fState === "waiting" || pam.u2fState === "insert") && !pam.u2fPending;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
@@ -1025,24 +1057,18 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
id: authFeedbackText
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 20
|
Layout.preferredHeight: text.length > 0 ? Math.min(implicitHeight, Math.ceil(Theme.fontSizeSmall * 4.5)) : 0
|
||||||
text: {
|
text: root.currentAuthFeedbackText()
|
||||||
if (root.pamState === "error") {
|
color: root.authFeedbackIsHint() ? Theme.outline : Theme.error
|
||||||
return "Authentication error - try again";
|
|
||||||
}
|
|
||||||
if (root.pamState === "max") {
|
|
||||||
return "Too many attempts - locked out";
|
|
||||||
}
|
|
||||||
if (root.pamState === "fail") {
|
|
||||||
return "Incorrect password - try again";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
color: Theme.error
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
opacity: root.pamState !== "" ? 1 : 0
|
wrapMode: Text.WordWrap
|
||||||
|
maximumLineCount: 3
|
||||||
|
elide: Text.ElideRight
|
||||||
|
opacity: text.length > 0 ? 1 : 0
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ Scope {
|
|||||||
u2fPendingTimeout.running = false;
|
u2fPendingTimeout.running = false;
|
||||||
passwdActiveTimeout.running = false;
|
passwdActiveTimeout.running = false;
|
||||||
unlockRequestTimeout.running = false;
|
unlockRequestTimeout.running = false;
|
||||||
u2fPending = false;
|
root.u2fPending = false;
|
||||||
u2fState = "";
|
root.u2fState = "";
|
||||||
unlockInProgress = false;
|
root.unlockInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function recoverFromAuthStall(newState: string): void {
|
function recoverFromAuthStall(newState: string): void {
|
||||||
resetAuthFlows();
|
resetAuthFlows();
|
||||||
state = newState;
|
root.state = newState;
|
||||||
flashMsg();
|
flashMsg();
|
||||||
stateReset.restart();
|
stateReset.restart();
|
||||||
fprint.checkAvail();
|
fprint.checkAvail();
|
||||||
@@ -46,16 +46,16 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function completeUnlock(): void {
|
function completeUnlock(): void {
|
||||||
if (!unlockInProgress) {
|
if (!root.unlockInProgress) {
|
||||||
unlockInProgress = true;
|
root.unlockInProgress = true;
|
||||||
passwd.abort();
|
passwd.abort();
|
||||||
fprint.abort();
|
fprint.abort();
|
||||||
u2f.abort();
|
u2f.abort();
|
||||||
errorRetry.running = false;
|
errorRetry.running = false;
|
||||||
u2fErrorRetry.running = false;
|
u2fErrorRetry.running = false;
|
||||||
u2fPendingTimeout.running = false;
|
u2fPendingTimeout.running = false;
|
||||||
u2fPending = false;
|
root.u2fPending = false;
|
||||||
u2fState = "";
|
root.u2fState = "";
|
||||||
unlockRequestTimeout.restart();
|
unlockRequestTimeout.restart();
|
||||||
unlockRequested();
|
unlockRequested();
|
||||||
}
|
}
|
||||||
@@ -70,13 +70,13 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cancelU2fPending(): void {
|
function cancelU2fPending(): void {
|
||||||
if (!u2fPending)
|
if (!root.u2fPending)
|
||||||
return;
|
return;
|
||||||
u2f.abort();
|
u2f.abort();
|
||||||
u2fErrorRetry.running = false;
|
u2fErrorRetry.running = false;
|
||||||
u2fPendingTimeout.running = false;
|
u2fPendingTimeout.running = false;
|
||||||
u2fPending = false;
|
root.u2fPending = false;
|
||||||
u2fState = "";
|
root.u2fState = "";
|
||||||
fprint.checkAvail();
|
fprint.checkAvail();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +87,13 @@ Scope {
|
|||||||
printErrors: false
|
printErrors: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: loginConfigWatcher
|
||||||
|
|
||||||
|
path: "/etc/pam.d/login"
|
||||||
|
printErrors: false
|
||||||
|
}
|
||||||
|
|
||||||
FileView {
|
FileView {
|
||||||
id: u2fConfigWatcher
|
id: u2fConfigWatcher
|
||||||
|
|
||||||
@@ -94,17 +101,22 @@ Scope {
|
|||||||
printErrors: false
|
printErrors: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property string bundledPasswdConfig: SettingsData.lockFaillockSupported ? "login-faillock" : "login"
|
||||||
|
|
||||||
PamContext {
|
PamContext {
|
||||||
id: passwd
|
id: passwd
|
||||||
|
|
||||||
config: dankshellConfigWatcher.loaded ? "dankshell" : "login"
|
config: dankshellConfigWatcher.loaded ? "dankshell" : root.bundledPasswdConfig
|
||||||
configDirectory: dankshellConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
|
configDirectory: (dankshellConfigWatcher.loaded || loginConfigWatcher.loaded) ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
|
||||||
|
|
||||||
onMessageChanged: {
|
onMessageChanged: {
|
||||||
if (message.startsWith("The account is locked"))
|
if (message.startsWith("The account is locked")) {
|
||||||
root.lockMessage = message;
|
root.lockMessage = message;
|
||||||
else if (root.lockMessage && message.endsWith(" left to unlock)"))
|
} else if (root.lockMessage && message.endsWith(" left to unlock)")) {
|
||||||
root.lockMessage += "\n" + message;
|
root.lockMessage += "\n" + message;
|
||||||
|
} else if (root.lockMessage && message && message.length > 0) {
|
||||||
|
root.lockMessage = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onResponseRequiredChanged: {
|
onResponseRequiredChanged: {
|
||||||
|
|||||||
7
quickshell/assets/pam/login-faillock
Normal file
7
quickshell/assets/pam/login-faillock
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#%PAM-1.0
|
||||||
|
auth required pam_env.so
|
||||||
|
auth required pam_faillock.so preauth
|
||||||
|
auth [success=1 default=bad] pam_unix.so try_first_pass nullok
|
||||||
|
auth [default=die] pam_faillock.so authfail
|
||||||
|
auth required pam_faillock.so authsucc
|
||||||
|
account required pam_unix.so
|
||||||
Reference in New Issue
Block a user