1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-03 20:32:07 -04:00

feat(Auth): Unify shared PAM sync across greeter & lockscreen

- Add a neutral `dms auth sync` command and reuse the shared auth flow from:
- Settings auth toggle auto-apply
- `dms greeter sync`
- `dms greeter install`
- greeter auth cleanup paths

- Rework lockscreen PAM so DMS builds /etc/pam.d/dankshell from the system login stack, but removes fingerprint and U2F from that password path. Keep /etc/pam.d/dankshell-u2f separate.

- Preserve custom PAM files in place to avoid adding duplicate greeter auth when the distro already provides it, and keep NixOS on the non-writing path.
This commit is contained in:
purian23
2026-03-27 12:52:08 -04:00
parent 521a3fa6e8
commit e7ee26ce74
17 changed files with 1968 additions and 514 deletions

View File

@@ -1203,13 +1203,23 @@ Singleton {
Quickshell.execDetached(["sh", "-lc", script]);
}
function scheduleAuthApply() {
if (isGreeterMode)
return;
Qt.callLater(() => {
Processes.settingsRoot = root;
Processes.scheduleAuthApply();
});
}
readonly property var _hooks: ({
"applyStoredTheme": applyStoredTheme,
"regenSystemThemes": regenSystemThemes,
"updateCompositorLayout": updateCompositorLayout,
"applyStoredIconTheme": applyStoredIconTheme,
"updateBarConfigs": updateBarConfigs,
"updateCompositorCursor": updateCompositorCursor
"updateCompositorCursor": updateCompositorCursor,
"scheduleAuthApply": scheduleAuthApply
})
function set(key, value) {

View File

@@ -4,6 +4,8 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
Singleton {
id: root
@@ -52,6 +54,14 @@ Singleton {
readonly property var forcedFprintAvailable: envFlag("DMS_FORCE_FPRINT_AVAILABLE")
readonly property var forcedU2fAvailable: envFlag("DMS_FORCE_U2F_AVAILABLE")
property bool authApplyRunning: false
property bool authApplyQueued: false
property bool authApplyRerunRequested: false
property bool authApplyTerminalFallbackFromPrecheck: false
property string authApplyStdout: ""
property string authApplyStderr: ""
property string authApplySudoProbeStderr: ""
property string authApplyTerminalFallbackStderr: ""
function detectQtTools() {
qtToolsDetectionProcess.running = true;
@@ -92,6 +102,50 @@ Singleton {
pluginSettingsCheckProcess.running = true;
}
function scheduleAuthApply() {
if (!settingsRoot || settingsRoot.isGreeterMode)
return;
authApplyQueued = true;
if (authApplyRunning) {
authApplyRerunRequested = true;
return;
}
authApplyDebounce.restart();
}
function beginAuthApply() {
if (!authApplyQueued || authApplyRunning || !settingsRoot || settingsRoot.isGreeterMode)
return;
authApplyQueued = false;
authApplyRerunRequested = false;
authApplyStdout = "";
authApplyStderr = "";
authApplySudoProbeStderr = "";
authApplyTerminalFallbackStderr = "";
authApplyTerminalFallbackFromPrecheck = false;
authApplyRunning = true;
authApplySudoProbeProcess.running = true;
}
function launchAuthApplyTerminalFallback(fromPrecheck, details) {
authApplyTerminalFallbackFromPrecheck = fromPrecheck;
if (details && details !== "")
ToastService.showInfo(I18n.tr("Authentication changes need sudo. Opening terminal so you can use password or fingerprint."), details, "", "auth-sync");
authApplyTerminalFallbackStderr = "";
authApplyTerminalFallbackProcess.running = true;
}
function finishAuthApply() {
const shouldRerun = authApplyQueued || authApplyRerunRequested;
authApplyRunning = false;
authApplyRerunRequested = false;
if (shouldRerun)
authApplyDebounce.restart();
}
function stripPamComment(line) {
if (!line)
return "";
@@ -417,6 +471,91 @@ Singleton {
}
}
Timer {
id: authApplyDebounce
interval: 300
repeat: false
onTriggered: root.beginAuthApply()
}
property var authApplyProcess: Process {
command: ["dms", "auth", "sync", "--yes"]
running: false
stdout: StdioCollector {
onStreamFinished: root.authApplyStdout = text || ""
}
stderr: StdioCollector {
onStreamFinished: root.authApplyStderr = text || ""
}
onExited: exitCode => {
const out = (root.authApplyStdout || "").trim();
const err = (root.authApplyStderr || "").trim();
if (exitCode === 0) {
let details = out;
if (err !== "")
details = details !== "" ? details + "\n\nstderr:\n" + err : "stderr:\n" + err;
ToastService.showInfo(I18n.tr("Authentication changes applied."), details, "", "auth-sync");
root.detectAuthCapabilities();
root.finishAuthApply();
return;
}
let details = "";
if (out !== "")
details = out;
if (err !== "")
details = details !== "" ? details + "\n\nstderr:\n" + err : "stderr:\n" + err;
ToastService.showWarning(I18n.tr("Background authentication sync failed. Trying terminal mode."), details, "", "auth-sync");
root.launchAuthApplyTerminalFallback(false, "");
}
}
property var authApplySudoProbeProcess: Process {
command: ["sudo", "-n", "true"]
running: false
stderr: StdioCollector {
onStreamFinished: root.authApplySudoProbeStderr = text || ""
}
onExited: exitCode => {
const err = (root.authApplySudoProbeStderr || "").trim();
if (exitCode === 0) {
ToastService.showInfo(I18n.tr("Applying authentication changes…"), "", "", "auth-sync");
root.authApplyProcess.running = true;
return;
}
root.launchAuthApplyTerminalFallback(true, err);
}
}
property var authApplyTerminalFallbackProcess: Process {
command: ["dms", "auth", "sync", "--terminal", "--yes"]
running: false
stderr: StdioCollector {
onStreamFinished: root.authApplyTerminalFallbackStderr = text || ""
}
onExited: exitCode => {
if (exitCode === 0) {
const message = root.authApplyTerminalFallbackFromPrecheck
? I18n.tr("Terminal opened. Complete authentication setup there; it will close automatically when done.")
: I18n.tr("Terminal fallback opened. Complete authentication setup there; it will close automatically when done.");
ToastService.showInfo(message, "", "", "auth-sync");
} else {
let details = (root.authApplyTerminalFallbackStderr || "").trim();
ToastService.showError(I18n.tr("Terminal fallback failed. Install a supported terminal emulator or run 'dms auth sync' manually.") + " (exit " + exitCode + ")", details, "", "auth-sync");
}
root.finishAuthApply();
}
}
FileView {
id: greetdPamWatcher
path: "/etc/pam.d/greetd"

View File

@@ -169,8 +169,8 @@ var SPEC = {
lockDateFormat: { def: "" },
greeterRememberLastSession: { def: true },
greeterRememberLastUser: { def: true },
greeterEnableFprint: { def: false },
greeterEnableU2f: { def: false },
greeterEnableFprint: { def: false, onChange: "scheduleAuthApply" },
greeterEnableU2f: { def: false, onChange: "scheduleAuthApply" },
greeterWallpaperPath: { def: "" },
greeterUse24HourClock: { def: true },
greeterShowSeconds: { def: false },
@@ -353,7 +353,7 @@ var SPEC = {
lockScreenShowMediaPlayer: { def: true },
lockScreenPowerOffMonitorsOnLock: { def: false },
lockAtStartup: { def: false },
enableFprint: { def: false },
enableFprint: { def: false, onChange: "scheduleAuthApply" },
maxFprintTries: { def: 15 },
fprintdAvailable: { def: false, persist: false },
lockFingerprintCanEnable: { def: false, persist: false },
@@ -363,7 +363,7 @@ var SPEC = {
greeterFingerprintReady: { def: false, persist: false },
greeterFingerprintReason: { def: "probe_failed", persist: false },
greeterFingerprintSource: { def: "none", persist: false },
enableU2f: { def: false },
enableU2f: { def: false, onChange: "scheduleAuthApply" },
u2fMode: { def: "or" },
u2fAvailable: { def: false, persist: false },
lockU2fCanEnable: { def: false, persist: false },

View File

@@ -52,6 +52,12 @@ Item {
return I18n.tr("Touch your security key...");
if (pam.lockMessage && pam.lockMessage.length > 0)
return pam.lockMessage;
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");
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");
@@ -60,12 +66,6 @@ Item {
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 "";
}

View File

@@ -91,9 +91,9 @@ Scope {
}
FileView {
id: loginConfigWatcher
id: nixosMarker
path: "/etc/pam.d/login"
path: "/etc/NIXOS"
printErrors: false
}
@@ -108,7 +108,7 @@ Scope {
id: passwd
config: dankshellConfigWatcher.loaded ? "dankshell" : "login"
configDirectory: (dankshellConfigWatcher.loaded || loginConfigWatcher.loaded) ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
configDirectory: (dankshellConfigWatcher.loaded || nixosMarker.loaded) ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
onMessageChanged: {
if (message.startsWith("The account is locked")) {

View File

@@ -36,7 +36,7 @@ Item {
switch (reason) {
case "ready":
return SettingsData.greeterEnableFprint ? I18n.tr("Run Sync to apply. Fingerprint-only login may not unlock GNOME Keyring.") : I18n.tr("Only affects DMS-managed PAM. If greetd already includes pam_fprintd, fingerprint stays enabled.");
return SettingsData.greeterEnableFprint ? I18n.tr("Authentication changes apply automatically. Fingerprint-only login may not unlock Keyring.") : I18n.tr("Only affects DMS-managed PAM. If greetd already includes pam_fprintd, fingerprint stays enabled.");
case "missing_enrollment":
if (SettingsData.greeterEnableFprint)
return I18n.tr("Enabled, but no prints are enrolled yet. Enroll fingerprints and run Sync.");
@@ -60,7 +60,7 @@ Item {
switch (reason) {
case "ready":
return SettingsData.greeterEnableU2f ? I18n.tr("Run Sync to apply.") : I18n.tr("Available.");
return SettingsData.greeterEnableU2f ? I18n.tr("Authentication changes apply automatically.") : I18n.tr("Available.");
case "missing_key_registration":
if (SettingsData.greeterEnableU2f)
return I18n.tr("Enabled, but no registered security key was found yet. Register a key and run Sync.");
@@ -448,7 +448,7 @@ Item {
settingKey: "greeterStatus"
StyledText {
text: I18n.tr("Check sync status on demand. Sync copies your theme, settings, PAM config, and wallpaper to the login screen in one step. Must run Sync to apply changes.")
text: I18n.tr("Check sync status on demand. Sync copies your theme, settings, and wallpaper configuration to the login screen. Authentication changes apply automatically.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
@@ -525,7 +525,7 @@ Item {
settingKey: "greeterAuth"
StyledText {
text: I18n.tr("Enable fingerprint or security key for DMS Greeter. Run Sync to apply and configure PAM.")
text: I18n.tr("Enable fingerprint or security key for DMS Greeter. Authentication changes apply automatically.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
@@ -754,7 +754,7 @@ Item {
settingKey: "greeterDeps"
StyledText {
text: I18n.tr("DMS greeter needs: greetd, dms-greeter. Fingerprint: fprintd, pam_fprintd. Security keys: pam_u2f. Add your user to the greeter group. Sync checks sudo first and opens a terminal when interactive authentication is required.")
text: I18n.tr("DMS greeter needs: greetd, dms-greeter. Fingerprint: fprintd, pam_fprintd. Security keys: pam_u2f. Add your user to the greeter group. Authentication changes apply automatically and may open a terminal when sudo authentication is required.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width

View File

@@ -15,10 +15,10 @@ Item {
function lockFingerprintDescription() {
switch (SettingsData.lockFingerprintReason) {
case "ready":
return I18n.tr("Use fingerprint authentication for the lock screen.");
return SettingsData.enableFprint ? I18n.tr("Authentication changes apply automatically.") : I18n.tr("Use fingerprint authentication for the lock screen.");
case "missing_enrollment":
if (SettingsData.enableFprint)
return I18n.tr("Enabled, but no prints are enrolled yet. Enroll fingerprints to use it.");
return I18n.tr("Enabled, but no prints are enrolled yet. Authentication changes apply automatically once you enroll fingerprints.");
return I18n.tr("Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and enroll later.");
case "missing_reader":
return SettingsData.enableFprint ? I18n.tr("Enabled, but no fingerprint reader was detected.") : I18n.tr("No fingerprint reader detected.");
@@ -32,10 +32,10 @@ Item {
function lockU2fDescription() {
switch (SettingsData.lockU2fReason) {
case "ready":
return I18n.tr("Use a security key for lock screen authentication.", "lock screen U2F security key setting");
return SettingsData.enableU2f ? I18n.tr("Authentication changes apply automatically.") : I18n.tr("Use a security key for lock screen authentication.", "lock screen U2F security key setting");
case "missing_key_registration":
if (SettingsData.enableU2f)
return I18n.tr("Enabled, but no registered security key was found yet. Register a key or update your U2F config.");
return I18n.tr("Enabled, but no registered security key was found yet. Authentication changes apply automatically once your key is registered or your U2F config is updated.");
return I18n.tr("Security-key support was detected, but no registered key was found yet. You can enable this now and register one later.");
case "missing_pam_support":
return I18n.tr("Not available — install or configure pam_u2f.");
@@ -213,6 +213,15 @@ Item {
onToggled: checked => SettingsData.set("lockAtStartup", checked)
}
StyledText {
text: I18n.tr("Lock screen authentication changes apply automatically and may open a terminal when sudo authentication is required.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
wrapMode: Text.Wrap
topPadding: Theme.spacingS
}
SettingsToggleRow {
settingKey: "enableFprint"
tags: ["lock", "screen", "fingerprint", "authentication", "biometric", "fprint"]