mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-03 20:32:07 -04:00
greeter(auth): Enhance fingerprint/U2F auth support w/Quickshell PAM
- Split auth capability state by lock screen and greeter - Share detection between settings UI and lock runtime - Broaden greeter PAM include detection across supported distros
This commit is contained in:
@@ -524,6 +524,16 @@ func syncInTerminal(nonInteractive bool, forceAuth bool, local bool) error {
|
||||
return runCommandInTerminal(shellCmd)
|
||||
}
|
||||
|
||||
func resolveLocalWrapperShell() (string, error) {
|
||||
for _, shellName := range []string{"bash", "sh"} {
|
||||
shellPath, err := exec.LookPath(shellName)
|
||||
if err == nil {
|
||||
return shellPath, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("could not find bash or sh in PATH for local greeter wrapper")
|
||||
}
|
||||
|
||||
func syncGreeter(nonInteractive bool, forceAuth bool, local bool) error {
|
||||
if !nonInteractive {
|
||||
fmt.Println("=== DMS Greeter Theme Sync ===")
|
||||
@@ -660,8 +670,12 @@ func syncGreeter(nonInteractive bool, forceAuth bool, local bool) error {
|
||||
localWrapperScript := filepath.Join(dmsPath, "Modules", "Greetd", "assets", "dms-greeter")
|
||||
restoreWrapperOverride := func() {}
|
||||
if info, statErr := os.Stat(localWrapperScript); statErr == nil && !info.IsDir() {
|
||||
wrapperShell, shellErr := resolveLocalWrapperShell()
|
||||
if shellErr != nil {
|
||||
return shellErr
|
||||
}
|
||||
previousWrapperOverride, hadWrapperOverride := os.LookupEnv("DMS_GREETER_WRAPPER_CMD")
|
||||
wrapperCmdOverride := "/usr/bin/bash " + localWrapperScript
|
||||
wrapperCmdOverride := wrapperShell + " " + localWrapperScript
|
||||
_ = os.Setenv("DMS_GREETER_WRAPPER_CMD", wrapperCmdOverride)
|
||||
restoreWrapperOverride = func() {
|
||||
if hadWrapperOverride {
|
||||
|
||||
@@ -33,14 +33,23 @@ const (
|
||||
legacyGreeterPamU2FComment = "# DMS greeter U2F"
|
||||
)
|
||||
|
||||
var includedPamAuthFiles = []string{"system-auth", "common-auth", "password-auth"}
|
||||
// Common PAM auth stack names referenced by greetd across supported distros.
|
||||
var includedPamAuthFiles = []string{
|
||||
"system-auth",
|
||||
"common-auth",
|
||||
"password-auth",
|
||||
"system-login",
|
||||
"system-local-login",
|
||||
"common-auth-pc",
|
||||
"login",
|
||||
}
|
||||
|
||||
func DetectDMSPath() (string, error) {
|
||||
return config.LocateDMSConfig()
|
||||
}
|
||||
|
||||
// IsNixOS returns true when running on NixOS, which manages PAM configs through
|
||||
// its module system. The DMS PAM managed block must not be written on NixOS.
|
||||
// its module system. The DMS PAM managed block won't be written on NixOS.
|
||||
func IsNixOS() bool {
|
||||
_, err := os.Stat("/etc/NIXOS")
|
||||
return err == nil
|
||||
@@ -440,8 +449,21 @@ func TryInstallGreeterPackage(logFunc func(string), sudoPassword string) bool {
|
||||
obsSlug := getDebianOBSSlug(osInfo)
|
||||
keyURL := fmt.Sprintf("https://download.opensuse.org/repositories/home:AvengeMedia:danklinux/%s/Release.key", obsSlug)
|
||||
repoLine := fmt.Sprintf("deb [signed-by=/etc/apt/keyrings/danklinux.gpg] https://download.opensuse.org/repositories/home:/AvengeMedia:/danklinux/%s/ /", obsSlug)
|
||||
failHint = fmt.Sprintf("⚠ dms-greeter install failed. Add OBS repo manually:\ncurl -fsSL %s | sudo gpg --dearmor -o /etc/apt/keyrings/danklinux.gpg\necho '%s' | sudo tee /etc/apt/sources.list.d/danklinux.list\nsudo apt update && sudo apt install dms-greeter", keyURL, repoLine)
|
||||
failHint = fmt.Sprintf("⚠ dms-greeter install failed. Add OBS repo manually:\nsudo apt-get install -y gnupg\nsudo mkdir -p /etc/apt/keyrings\ncurl -fsSL %s | sudo gpg --dearmor -o /etc/apt/keyrings/danklinux.gpg\necho '%s' | sudo tee /etc/apt/sources.list.d/danklinux.list\nsudo apt update && sudo apt-get install -y dms-greeter", keyURL, repoLine)
|
||||
logFunc(fmt.Sprintf("Adding DankLinux OBS repository (%s)...", obsSlug))
|
||||
if _, err := exec.LookPath("gpg"); err != nil {
|
||||
logFunc("Installing gnupg for OBS repository key import...")
|
||||
installGPGCmd := exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "gnupg")
|
||||
installGPGCmd.Stdout = os.Stdout
|
||||
installGPGCmd.Stderr = os.Stderr
|
||||
if err := installGPGCmd.Run(); err != nil {
|
||||
logFunc(fmt.Sprintf("⚠ Failed to install gnupg: %v", err))
|
||||
}
|
||||
}
|
||||
mkdirCmd := exec.CommandContext(ctx, "sudo", "mkdir", "-p", "/etc/apt/keyrings")
|
||||
mkdirCmd.Stdout = os.Stdout
|
||||
mkdirCmd.Stderr = os.Stderr
|
||||
mkdirCmd.Run()
|
||||
addKeyCmd := exec.CommandContext(ctx, "bash", "-c",
|
||||
fmt.Sprintf(`curl -fsSL %s | sudo gpg --dearmor -o /etc/apt/keyrings/danklinux.gpg`, keyURL))
|
||||
addKeyCmd.Stdout = os.Stdout
|
||||
@@ -465,7 +487,7 @@ func TryInstallGreeterPackage(logFunc func(string), sudoPassword string) bool {
|
||||
exec.CommandContext(ctx, "sudo", "zypper", "refresh").Run()
|
||||
installCmd = exec.CommandContext(ctx, "sudo", "zypper", "install", "-y", "dms-greeter")
|
||||
case distros.FamilyUbuntu:
|
||||
failHint = "⚠ dms-greeter install failed. Add PPA manually: sudo add-apt-repository ppa:avengemedia/danklinux && sudo apt-get update && sudo apt-get install dms-greeter"
|
||||
failHint = "⚠ dms-greeter install failed. Add PPA manually: sudo add-apt-repository ppa:avengemedia/danklinux && sudo apt-get update && sudo apt-get install -y dms-greeter"
|
||||
logFunc("Enabling PPA ppa:avengemedia/danklinux...")
|
||||
ppacmd := exec.CommandContext(ctx, "sudo", "add-apt-repository", "-y", "ppa:avengemedia/danklinux")
|
||||
ppacmd.Stdout = os.Stdout
|
||||
@@ -834,7 +856,14 @@ func EnsureACLInstalled(logFunc func(string), sudoPassword string) error {
|
||||
installCmd = exec.CommandContext(ctx, "sudo", "zypper", "install", "-y", "acl")
|
||||
}
|
||||
|
||||
case distros.FamilyUbuntu, distros.FamilyDebian:
|
||||
case distros.FamilyUbuntu:
|
||||
if sudoPassword != "" {
|
||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword, "apt-get install -y acl")
|
||||
} else {
|
||||
installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "acl")
|
||||
}
|
||||
|
||||
case distros.FamilyDebian:
|
||||
if sudoPassword != "" {
|
||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword, "apt-get install -y acl")
|
||||
} else {
|
||||
@@ -1209,9 +1238,14 @@ func pamModuleExists(module string) bool {
|
||||
for _, libDir := range []string{
|
||||
"/usr/lib64/security",
|
||||
"/usr/lib/security",
|
||||
"/lib64/security",
|
||||
"/lib/security",
|
||||
"/lib/x86_64-linux-gnu/security",
|
||||
"/usr/lib/x86_64-linux-gnu/security",
|
||||
"/lib/aarch64-linux-gnu/security",
|
||||
"/usr/lib/aarch64-linux-gnu/security",
|
||||
"/run/current-system/sw/lib64/security",
|
||||
"/run/current-system/sw/lib/security",
|
||||
} {
|
||||
if _, err := os.Stat(filepath.Join(libDir, module)); err == nil {
|
||||
return true
|
||||
@@ -2149,7 +2183,7 @@ func DisableConflictingDisplayManagers(sudoPassword string, logFunc func(string)
|
||||
switch state {
|
||||
case "enabled", "enabled-runtime", "static", "indirect", "alias":
|
||||
logFunc(fmt.Sprintf("Disabling conflicting display manager: %s", dm))
|
||||
if err := runSudoCmd(sudoPassword, "systemctl", "disable", "--now", dm); err != nil {
|
||||
if err := runSudoCmd(sudoPassword, "systemctl", "disable", dm); err != nil {
|
||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to disable %s: %v", dm, err))
|
||||
} else {
|
||||
logFunc(fmt.Sprintf("✓ Disabled %s", dm))
|
||||
|
||||
@@ -506,6 +506,23 @@ Singleton {
|
||||
property bool enableFprint: false
|
||||
property int maxFprintTries: 15
|
||||
property bool fprintdAvailable: false
|
||||
property bool lockFingerprintCanEnable: false
|
||||
property bool lockFingerprintReady: false
|
||||
property string lockFingerprintReason: "probe_failed"
|
||||
property bool greeterFingerprintCanEnable: false
|
||||
property bool greeterFingerprintReady: false
|
||||
property string greeterFingerprintReason: "probe_failed"
|
||||
property string greeterFingerprintSource: "none"
|
||||
property bool enableU2f: false
|
||||
property string u2fMode: "or"
|
||||
property bool u2fAvailable: false
|
||||
property bool lockU2fCanEnable: false
|
||||
property bool lockU2fReady: false
|
||||
property string lockU2fReason: "probe_failed"
|
||||
property bool greeterU2fCanEnable: false
|
||||
property bool greeterU2fReady: false
|
||||
property string greeterU2fReason: "probe_failed"
|
||||
property string greeterU2fSource: "none"
|
||||
property string lockScreenActiveMonitor: "all"
|
||||
property string lockScreenInactiveColor: "#000000"
|
||||
property int lockScreenNotificationMode: 0
|
||||
@@ -992,8 +1009,7 @@ Singleton {
|
||||
if (isGreeterMode)
|
||||
return;
|
||||
Processes.settingsRoot = root;
|
||||
Processes.detectFprintd();
|
||||
Processes.detectU2f();
|
||||
Processes.detectAuthCapabilities();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -10,6 +10,49 @@ Singleton {
|
||||
|
||||
property var settingsRoot: null
|
||||
|
||||
property string greetdPamText: ""
|
||||
property string systemAuthPamText: ""
|
||||
property string commonAuthPamText: ""
|
||||
property string passwordAuthPamText: ""
|
||||
property string systemLoginPamText: ""
|
||||
property string systemLocalLoginPamText: ""
|
||||
property string commonAuthPcPamText: ""
|
||||
property string loginPamText: ""
|
||||
property string dankshellU2fPamText: ""
|
||||
property string u2fKeysText: ""
|
||||
|
||||
property string fingerprintProbeOutput: ""
|
||||
property int fingerprintProbeExitCode: 0
|
||||
property bool fingerprintProbeStreamFinished: false
|
||||
property bool fingerprintProbeExited: false
|
||||
property string fingerprintProbeState: "probe_failed"
|
||||
|
||||
property string pamSupportProbeOutput: ""
|
||||
property bool pamSupportProbeStreamFinished: false
|
||||
property bool pamSupportProbeExited: false
|
||||
property int pamSupportProbeExitCode: 0
|
||||
property bool pamFprintSupportDetected: false
|
||||
property bool pamU2fSupportDetected: false
|
||||
|
||||
readonly property string homeDir: Quickshell.env("HOME") || ""
|
||||
readonly property string u2fKeysPath: homeDir ? homeDir + "/.config/Yubico/u2f_keys" : ""
|
||||
readonly property bool homeU2fKeysDetected: u2fKeysPath !== "" && u2fKeysWatcher.loaded && u2fKeysText.trim() !== ""
|
||||
readonly property bool lockU2fCustomConfigDetected: pamModuleEnabled(dankshellU2fPamText, "pam_u2f")
|
||||
readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd")
|
||||
readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f")
|
||||
|
||||
function envFlag(name) {
|
||||
const value = (Quickshell.env(name) || "").trim().toLowerCase();
|
||||
if (value === "1" || value === "true" || value === "yes" || value === "on")
|
||||
return true;
|
||||
if (value === "0" || value === "false" || value === "no" || value === "off")
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
readonly property var forcedFprintAvailable: envFlag("DMS_FORCE_FPRINT_AVAILABLE")
|
||||
readonly property var forcedU2fAvailable: envFlag("DMS_FORCE_U2F_AVAILABLE")
|
||||
|
||||
function detectQtTools() {
|
||||
qtToolsDetectionProcess.running = true;
|
||||
}
|
||||
@@ -18,10 +61,305 @@ Singleton {
|
||||
fprintdDetectionProcess.running = true;
|
||||
}
|
||||
|
||||
function detectAuthCapabilities() {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
|
||||
if (forcedFprintAvailable === null) {
|
||||
fingerprintProbeOutput = "";
|
||||
fingerprintProbeStreamFinished = false;
|
||||
fingerprintProbeExited = false;
|
||||
fingerprintProbeProcess.running = true;
|
||||
} else {
|
||||
fingerprintProbeState = forcedFprintAvailable ? "ready" : "probe_failed";
|
||||
}
|
||||
|
||||
if (forcedFprintAvailable === null || forcedU2fAvailable === null) {
|
||||
pamFprintSupportDetected = false;
|
||||
pamU2fSupportDetected = false;
|
||||
pamSupportProbeOutput = "";
|
||||
pamSupportProbeStreamFinished = false;
|
||||
pamSupportProbeExited = false;
|
||||
pamSupportDetectionProcess.running = true;
|
||||
}
|
||||
|
||||
recomputeAuthCapabilities();
|
||||
}
|
||||
|
||||
function detectFprintd() {
|
||||
detectAuthCapabilities();
|
||||
}
|
||||
|
||||
function detectU2f() {
|
||||
detectAuthCapabilities();
|
||||
}
|
||||
|
||||
function checkPluginSettings() {
|
||||
pluginSettingsCheckProcess.running = true;
|
||||
}
|
||||
|
||||
function stripPamComment(line) {
|
||||
if (!line)
|
||||
return "";
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith("#"))
|
||||
return "";
|
||||
const hashIdx = trimmed.indexOf("#");
|
||||
if (hashIdx >= 0)
|
||||
return trimmed.substring(0, hashIdx).trim();
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function pamModuleEnabled(pamText, moduleName) {
|
||||
if (!pamText || !moduleName)
|
||||
return false;
|
||||
const lines = pamText.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = stripPamComment(lines[i]);
|
||||
if (!line)
|
||||
continue;
|
||||
if (line.includes(moduleName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function pamTextIncludesFile(pamText, filename) {
|
||||
if (!pamText || !filename)
|
||||
return false;
|
||||
const lines = pamText.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = stripPamComment(lines[i]);
|
||||
if (!line)
|
||||
continue;
|
||||
if (line.includes(filename) && (line.includes("include") || line.includes("substack") || line.startsWith("@include")))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function greeterPamStackHasModule(moduleName) {
|
||||
if (pamModuleEnabled(greetdPamText, moduleName))
|
||||
return true;
|
||||
const includedPamStacks = [
|
||||
["system-auth", systemAuthPamText],
|
||||
["common-auth", commonAuthPamText],
|
||||
["password-auth", passwordAuthPamText],
|
||||
["system-login", systemLoginPamText],
|
||||
["system-local-login", systemLocalLoginPamText],
|
||||
["common-auth-pc", commonAuthPcPamText],
|
||||
["login", loginPamText]
|
||||
];
|
||||
for (let i = 0; i < includedPamStacks.length; i++) {
|
||||
const stack = includedPamStacks[i];
|
||||
if (pamTextIncludesFile(greetdPamText, stack[0]) && pamModuleEnabled(stack[1], moduleName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasEnrolledFingerprintOutput(output) {
|
||||
const lower = (output || "").toLowerCase();
|
||||
if (lower.includes("has fingers enrolled") || lower.includes("has fingerprints enrolled"))
|
||||
return true;
|
||||
const lines = lower.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const trimmed = lines[i].trim();
|
||||
if (trimmed.startsWith("finger:"))
|
||||
return true;
|
||||
if (trimmed.startsWith("- ") && trimmed.includes("finger"))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function hasMissingFingerprintEnrollmentOutput(output) {
|
||||
const lower = (output || "").toLowerCase();
|
||||
return lower.includes("no fingers enrolled")
|
||||
|| lower.includes("no fingerprints enrolled")
|
||||
|| lower.includes("no prints enrolled");
|
||||
}
|
||||
|
||||
function hasMissingFingerprintReaderOutput(output) {
|
||||
const lower = (output || "").toLowerCase();
|
||||
return lower.includes("no devices available")
|
||||
|| lower.includes("no device available")
|
||||
|| lower.includes("no devices found")
|
||||
|| lower.includes("list_devices failed")
|
||||
|| lower.includes("no device");
|
||||
}
|
||||
|
||||
function parseFingerprintProbe(exitCode, output) {
|
||||
if (hasEnrolledFingerprintOutput(output))
|
||||
return "ready";
|
||||
if (hasMissingFingerprintEnrollmentOutput(output))
|
||||
return "missing_enrollment";
|
||||
if (hasMissingFingerprintReaderOutput(output))
|
||||
return "missing_reader";
|
||||
if (exitCode === 0)
|
||||
return "missing_enrollment";
|
||||
if (exitCode === 127 || (output || "").includes("__missing_command__"))
|
||||
return "probe_failed";
|
||||
return pamFprintSupportDetected ? "probe_failed" : "missing_pam_support";
|
||||
}
|
||||
|
||||
function setLockFingerprintCapability(canEnable, ready, reason) {
|
||||
settingsRoot.lockFingerprintCanEnable = canEnable;
|
||||
settingsRoot.lockFingerprintReady = ready;
|
||||
settingsRoot.lockFingerprintReason = reason;
|
||||
}
|
||||
|
||||
function setLockU2fCapability(canEnable, ready, reason) {
|
||||
settingsRoot.lockU2fCanEnable = canEnable;
|
||||
settingsRoot.lockU2fReady = ready;
|
||||
settingsRoot.lockU2fReason = reason;
|
||||
}
|
||||
|
||||
function setGreeterFingerprintCapability(canEnable, ready, reason, source) {
|
||||
settingsRoot.greeterFingerprintCanEnable = canEnable;
|
||||
settingsRoot.greeterFingerprintReady = ready;
|
||||
settingsRoot.greeterFingerprintReason = reason;
|
||||
settingsRoot.greeterFingerprintSource = source;
|
||||
}
|
||||
|
||||
function setGreeterU2fCapability(canEnable, ready, reason, source) {
|
||||
settingsRoot.greeterU2fCanEnable = canEnable;
|
||||
settingsRoot.greeterU2fReady = ready;
|
||||
settingsRoot.greeterU2fReason = reason;
|
||||
settingsRoot.greeterU2fSource = source;
|
||||
}
|
||||
|
||||
function recomputeFingerprintCapabilities() {
|
||||
if (forcedFprintAvailable !== null) {
|
||||
const reason = forcedFprintAvailable ? "ready" : "probe_failed";
|
||||
const source = forcedFprintAvailable ? "dms" : "none";
|
||||
setLockFingerprintCapability(forcedFprintAvailable, forcedFprintAvailable, reason);
|
||||
setGreeterFingerprintCapability(forcedFprintAvailable, forcedFprintAvailable, reason, source);
|
||||
return;
|
||||
}
|
||||
|
||||
const state = fingerprintProbeState;
|
||||
|
||||
switch (state) {
|
||||
case "ready":
|
||||
setLockFingerprintCapability(true, true, "ready");
|
||||
break;
|
||||
case "missing_enrollment":
|
||||
setLockFingerprintCapability(true, false, "missing_enrollment");
|
||||
break;
|
||||
case "missing_reader":
|
||||
setLockFingerprintCapability(false, false, "missing_reader");
|
||||
break;
|
||||
case "missing_pam_support":
|
||||
setLockFingerprintCapability(false, false, "missing_pam_support");
|
||||
break;
|
||||
default:
|
||||
setLockFingerprintCapability(false, false, "probe_failed");
|
||||
break;
|
||||
}
|
||||
|
||||
if (greeterPamHasFprint) {
|
||||
switch (state) {
|
||||
case "ready":
|
||||
setGreeterFingerprintCapability(true, true, "configured_externally", "pam");
|
||||
break;
|
||||
case "missing_enrollment":
|
||||
setGreeterFingerprintCapability(true, false, "missing_enrollment", "pam");
|
||||
break;
|
||||
case "missing_reader":
|
||||
setGreeterFingerprintCapability(false, false, "missing_reader", "pam");
|
||||
break;
|
||||
default:
|
||||
setGreeterFingerprintCapability(true, false, "probe_failed", "pam");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case "ready":
|
||||
setGreeterFingerprintCapability(true, true, "ready", "dms");
|
||||
break;
|
||||
case "missing_enrollment":
|
||||
setGreeterFingerprintCapability(true, false, "missing_enrollment", "dms");
|
||||
break;
|
||||
case "missing_reader":
|
||||
setGreeterFingerprintCapability(false, false, "missing_reader", "none");
|
||||
break;
|
||||
case "missing_pam_support":
|
||||
setGreeterFingerprintCapability(false, false, "missing_pam_support", "none");
|
||||
break;
|
||||
default:
|
||||
setGreeterFingerprintCapability(false, false, "probe_failed", "none");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function recomputeU2fCapabilities() {
|
||||
if (forcedU2fAvailable !== null) {
|
||||
const reason = forcedU2fAvailable ? "ready" : "probe_failed";
|
||||
const source = forcedU2fAvailable ? "dms" : "none";
|
||||
setLockU2fCapability(forcedU2fAvailable, forcedU2fAvailable, reason);
|
||||
setGreeterU2fCapability(forcedU2fAvailable, forcedU2fAvailable, reason, source);
|
||||
return;
|
||||
}
|
||||
|
||||
const lockReady = lockU2fCustomConfigDetected || homeU2fKeysDetected;
|
||||
const lockCanEnable = lockReady || pamU2fSupportDetected;
|
||||
const lockReason = lockReady ? "ready" : (lockCanEnable ? "missing_key_registration" : "missing_pam_support");
|
||||
setLockU2fCapability(lockCanEnable, lockReady, lockReason);
|
||||
|
||||
if (greeterPamHasU2f) {
|
||||
setGreeterU2fCapability(true, true, "configured_externally", "pam");
|
||||
return;
|
||||
}
|
||||
|
||||
const greeterReady = homeU2fKeysDetected;
|
||||
const greeterCanEnable = greeterReady || pamU2fSupportDetected;
|
||||
const greeterReason = greeterReady ? "ready" : (greeterCanEnable ? "missing_key_registration" : "missing_pam_support");
|
||||
setGreeterU2fCapability(greeterCanEnable, greeterReady, greeterReason, greeterCanEnable ? "dms" : "none");
|
||||
}
|
||||
|
||||
function recomputeAuthCapabilities() {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
recomputeFingerprintCapabilities();
|
||||
recomputeU2fCapabilities();
|
||||
settingsRoot.fprintdAvailable = settingsRoot.lockFingerprintReady || settingsRoot.greeterFingerprintReady;
|
||||
settingsRoot.u2fAvailable = settingsRoot.lockU2fReady || settingsRoot.greeterU2fReady;
|
||||
}
|
||||
|
||||
function finalizeFingerprintProbe() {
|
||||
if (!fingerprintProbeStreamFinished || !fingerprintProbeExited)
|
||||
return;
|
||||
fingerprintProbeState = parseFingerprintProbe(fingerprintProbeExitCode, fingerprintProbeOutput);
|
||||
recomputeAuthCapabilities();
|
||||
}
|
||||
|
||||
function finalizePamSupportProbe() {
|
||||
if (!pamSupportProbeStreamFinished || !pamSupportProbeExited)
|
||||
return;
|
||||
|
||||
pamFprintSupportDetected = false;
|
||||
pamU2fSupportDetected = false;
|
||||
|
||||
const lines = (pamSupportProbeOutput || "").trim().split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const parts = lines[i].split(":");
|
||||
if (parts.length !== 2)
|
||||
continue;
|
||||
if (parts[0] === "pam_fprintd.so")
|
||||
pamFprintSupportDetected = parts[1] === "true";
|
||||
else if (parts[0] === "pam_u2f.so")
|
||||
pamU2fSupportDetected = parts[1] === "true";
|
||||
}
|
||||
|
||||
if (forcedFprintAvailable === null && fingerprintProbeState === "missing_pam_support")
|
||||
fingerprintProbeState = parseFingerprintProbe(fingerprintProbeExitCode, fingerprintProbeOutput);
|
||||
|
||||
recomputeAuthCapabilities();
|
||||
}
|
||||
|
||||
property var qtToolsDetectionProcess: Process {
|
||||
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
|
||||
running: false
|
||||
@@ -31,15 +369,15 @@ Singleton {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
if (text && text.trim()) {
|
||||
var lines = text.trim().split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i];
|
||||
if (line.startsWith('qt5ct:')) {
|
||||
settingsRoot.qt5ctAvailable = line.split(':')[1] === 'true';
|
||||
} else if (line.startsWith('qt6ct:')) {
|
||||
settingsRoot.qt6ctAvailable = line.split(':')[1] === 'true';
|
||||
} else if (line.startsWith('gtk:')) {
|
||||
settingsRoot.gtkAvailable = line.split(':')[1] === 'true';
|
||||
const lines = text.trim().split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.startsWith("qt5ct:")) {
|
||||
settingsRoot.qt5ctAvailable = line.split(":")[1] === "true";
|
||||
} else if (line.startsWith("qt6ct:")) {
|
||||
settingsRoot.qt6ctAvailable = line.split(":")[1] === "true";
|
||||
} else if (line.startsWith("gtk:")) {
|
||||
settingsRoot.gtkAvailable = line.split(":")[1] === "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,13 +385,181 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
property var fprintdDetectionProcess: Process {
|
||||
command: ["sh", "-c", "command -v fprintd-list >/dev/null 2>&1 && fprintd-list \"${USER:-$(id -un)}\" >/dev/null 2>&1"]
|
||||
property var fingerprintProbeProcess: Process {
|
||||
command: ["sh", "-c", "if command -v fprintd-list >/dev/null 2>&1; then fprintd-list \"${USER:-$(id -un)}\" 2>&1; else printf '__missing_command__\\n'; exit 127; fi"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.fingerprintProbeOutput = text || "";
|
||||
root.fingerprintProbeStreamFinished = true;
|
||||
root.finalizeFingerprintProbe();
|
||||
}
|
||||
}
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
settingsRoot.fprintdAvailable = (exitCode === 0);
|
||||
root.fingerprintProbeExitCode = exitCode;
|
||||
root.fingerprintProbeExited = true;
|
||||
root.finalizeFingerprintProbe();
|
||||
}
|
||||
}
|
||||
|
||||
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"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.pamSupportProbeOutput = text || "";
|
||||
root.pamSupportProbeStreamFinished = true;
|
||||
root.finalizePamSupportProbe();
|
||||
}
|
||||
}
|
||||
|
||||
onExited: function (exitCode) {
|
||||
root.pamSupportProbeExitCode = exitCode;
|
||||
root.pamSupportProbeExited = true;
|
||||
root.finalizePamSupportProbe();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: greetdPamWatcher
|
||||
path: "/etc/pam.d/greetd"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.greetdPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.greetdPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: systemAuthPamWatcher
|
||||
path: "/etc/pam.d/system-auth"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.systemAuthPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.systemAuthPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: commonAuthPamWatcher
|
||||
path: "/etc/pam.d/common-auth"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.commonAuthPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.commonAuthPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: passwordAuthPamWatcher
|
||||
path: "/etc/pam.d/password-auth"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.passwordAuthPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.passwordAuthPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: systemLoginPamWatcher
|
||||
path: "/etc/pam.d/system-login"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.systemLoginPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.systemLoginPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: systemLocalLoginPamWatcher
|
||||
path: "/etc/pam.d/system-local-login"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.systemLocalLoginPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.systemLocalLoginPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: commonAuthPcPamWatcher
|
||||
path: "/etc/pam.d/common-auth-pc"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.commonAuthPcPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.commonAuthPcPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: loginPamWatcher
|
||||
path: "/etc/pam.d/login"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.loginPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.loginPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: dankshellU2fPamWatcher
|
||||
path: "/etc/pam.d/dankshell-u2f"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.dankshellU2fPamText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.dankshellU2fPamText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: u2fKeysWatcher
|
||||
path: root.u2fKeysPath
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.u2fKeysText = text();
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.u2fKeysText = "";
|
||||
root.recomputeAuthCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -329,6 +329,23 @@ var SPEC = {
|
||||
enableFprint: { def: false },
|
||||
maxFprintTries: { def: 15 },
|
||||
fprintdAvailable: { def: false, persist: false },
|
||||
lockFingerprintCanEnable: { def: false, persist: false },
|
||||
lockFingerprintReady: { def: false, persist: false },
|
||||
lockFingerprintReason: { def: "probe_failed", persist: false },
|
||||
greeterFingerprintCanEnable: { def: false, persist: false },
|
||||
greeterFingerprintReady: { def: false, persist: false },
|
||||
greeterFingerprintReason: { def: "probe_failed", persist: false },
|
||||
greeterFingerprintSource: { def: "none", persist: false },
|
||||
enableU2f: { def: false },
|
||||
u2fMode: { def: "or" },
|
||||
u2fAvailable: { def: false, persist: false },
|
||||
lockU2fCanEnable: { def: false, persist: false },
|
||||
lockU2fReady: { def: false, persist: false },
|
||||
lockU2fReason: { def: "probe_failed", persist: false },
|
||||
greeterU2fCanEnable: { def: false, persist: false },
|
||||
greeterU2fReady: { def: false, persist: false },
|
||||
greeterU2fReason: { def: "probe_failed", persist: false },
|
||||
greeterU2fSource: { def: "none", persist: false },
|
||||
lockScreenActiveMonitor: { def: "all" },
|
||||
lockScreenInactiveColor: { def: "#000000" },
|
||||
lockScreenNotificationMode: { def: 0 },
|
||||
|
||||
@@ -36,7 +36,7 @@ Item {
|
||||
property bool passwordSubmitRequested: false
|
||||
property bool cancelingExternalAuthForPassword: false
|
||||
property int defaultAuthTimeoutMs: 10000
|
||||
property int externalAuthTimeoutMs: 36000
|
||||
property int externalAuthTimeoutMs: 30000
|
||||
property int memoryFlushDelayMs: 120
|
||||
property string pendingLaunchCommand: ""
|
||||
property var pendingLaunchEnv: []
|
||||
@@ -47,13 +47,17 @@ Item {
|
||||
property string systemAuthPamText: ""
|
||||
property string commonAuthPamText: ""
|
||||
property string passwordAuthPamText: ""
|
||||
property string systemLoginPamText: ""
|
||||
property string systemLocalLoginPamText: ""
|
||||
property string commonAuthPcPamText: ""
|
||||
property string loginPamText: ""
|
||||
property string faillockConfigText: ""
|
||||
property bool greeterWallpaperOverrideExists: false
|
||||
property string externalAuthAutoStartedForUser: ""
|
||||
property int passwordSessionTransitionRetryCount: 0
|
||||
property int maxPasswordSessionTransitionRetries: 2
|
||||
readonly property bool greeterPamHasFprint: pamModuleEnabled(greetdPamText, "pam_fprintd") || (greetdPamText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_fprintd")) || (greetdPamText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_fprintd")) || (greetdPamText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_fprintd"))
|
||||
readonly property bool greeterPamHasU2f: pamModuleEnabled(greetdPamText, "pam_u2f") || (greetdPamText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_u2f")) || (greetdPamText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_u2f")) || (greetdPamText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_u2f"))
|
||||
readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd")
|
||||
readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f")
|
||||
readonly property bool greeterExternalAuthAvailable: (greeterPamHasFprint && GreetdSettings.greeterEnableFprint) || (greeterPamHasU2f && GreetdSettings.greeterEnableU2f)
|
||||
readonly property bool greeterPamHasExternalAuth: greeterPamHasFprint || greeterPamHasU2f
|
||||
|
||||
@@ -95,6 +99,40 @@ Item {
|
||||
return false;
|
||||
}
|
||||
|
||||
function pamTextIncludesFile(pamText, filename) {
|
||||
if (!pamText || !filename)
|
||||
return false;
|
||||
const lines = pamText.split(/\r?\n/);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = stripPamComment(lines[i]);
|
||||
if (!line)
|
||||
continue;
|
||||
if (line.includes(filename) && (line.includes("include") || line.includes("substack") || line.startsWith("@include")))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function greeterPamStackHasModule(moduleName) {
|
||||
if (pamModuleEnabled(greetdPamText, moduleName))
|
||||
return true;
|
||||
const includedPamStacks = [
|
||||
["system-auth", systemAuthPamText],
|
||||
["common-auth", commonAuthPamText],
|
||||
["password-auth", passwordAuthPamText],
|
||||
["system-login", systemLoginPamText],
|
||||
["system-local-login", systemLocalLoginPamText],
|
||||
["common-auth-pc", commonAuthPcPamText],
|
||||
["login", loginPamText]
|
||||
];
|
||||
for (let i = 0; i < includedPamStacks.length; i++) {
|
||||
const stack = includedPamStacks[i];
|
||||
if (pamTextIncludesFile(greetdPamText, stack[0]) && pamModuleEnabled(stack[1], moduleName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function usesPamLockoutPolicy(pamText) {
|
||||
if (!pamText)
|
||||
return false;
|
||||
@@ -148,7 +186,7 @@ Item {
|
||||
}
|
||||
|
||||
function refreshPasswordAttemptPolicyHint() {
|
||||
const pamSources = [greetdPamText, systemAuthPamText, commonAuthPamText, passwordAuthPamText];
|
||||
const pamSources = [greetdPamText, systemAuthPamText, commonAuthPamText, passwordAuthPamText, systemLoginPamText, systemLocalLoginPamText, commonAuthPcPamText, loginPamText];
|
||||
let lockoutConfigured = false;
|
||||
let denyFromPam = -1;
|
||||
for (let i = 0; i < pamSources.length; i++) {
|
||||
@@ -271,6 +309,7 @@ Item {
|
||||
onLoaded: {
|
||||
root.systemAuthPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
root.maybeAutoStartExternalAuth();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.systemAuthPamText = "";
|
||||
@@ -285,6 +324,7 @@ Item {
|
||||
onLoaded: {
|
||||
root.commonAuthPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
root.maybeAutoStartExternalAuth();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.commonAuthPamText = "";
|
||||
@@ -299,6 +339,7 @@ Item {
|
||||
onLoaded: {
|
||||
root.passwordAuthPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
root.maybeAutoStartExternalAuth();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.passwordAuthPamText = "";
|
||||
@@ -306,6 +347,66 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: systemLoginPamWatcher
|
||||
path: "/etc/pam.d/system-login"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.systemLoginPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
root.maybeAutoStartExternalAuth();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.systemLoginPamText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: systemLocalLoginPamWatcher
|
||||
path: "/etc/pam.d/system-local-login"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.systemLocalLoginPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
root.maybeAutoStartExternalAuth();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.systemLocalLoginPamText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: commonAuthPcPamWatcher
|
||||
path: "/etc/pam.d/common-auth-pc"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.commonAuthPcPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
root.maybeAutoStartExternalAuth();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.commonAuthPcPamText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: loginPamWatcher
|
||||
path: "/etc/pam.d/login"
|
||||
printErrors: false
|
||||
onLoaded: {
|
||||
root.loginPamText = text();
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
root.maybeAutoStartExternalAuth();
|
||||
}
|
||||
onLoadFailed: {
|
||||
root.loginPamText = "";
|
||||
root.refreshPasswordAttemptPolicyHint();
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: faillockConfigWatcher
|
||||
path: "/etc/security/faillock.conf"
|
||||
|
||||
@@ -158,7 +158,7 @@ Scope {
|
||||
PamContext {
|
||||
id: fprint
|
||||
|
||||
property bool available
|
||||
property bool available: SettingsData.lockFingerprintReady
|
||||
property int tries
|
||||
property int errorTries
|
||||
|
||||
@@ -212,13 +212,71 @@ Scope {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: availProc
|
||||
PamContext {
|
||||
id: u2f
|
||||
|
||||
command: ["sh", "-c", "fprintd-list \"${USER:-$(id -un)}\""]
|
||||
onExited: code => {
|
||||
fprint.available = code === 0;
|
||||
property bool available: SettingsData.lockU2fReady
|
||||
|
||||
function checkAvail(): void {
|
||||
if (!available || !SettingsData.enableU2f || !root.lockSecured) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (SettingsData.u2fMode === "or") {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
function startForSecondFactor(): void {
|
||||
if (!available || !SettingsData.enableU2f) {
|
||||
root.completeUnlock();
|
||||
return;
|
||||
}
|
||||
abort();
|
||||
root.u2fPending = true;
|
||||
root.u2fState = "";
|
||||
u2fPendingTimeout.restart();
|
||||
start();
|
||||
}
|
||||
|
||||
config: u2fConfigWatcher.loaded ? "dankshell-u2f" : "u2f"
|
||||
configDirectory: u2fConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
|
||||
|
||||
onMessageChanged: {
|
||||
if (message.toLowerCase().includes("touch"))
|
||||
root.u2fState = "waiting";
|
||||
}
|
||||
|
||||
onCompleted: res => {
|
||||
if (!available || root.unlockInProgress)
|
||||
return;
|
||||
|
||||
if (res === PamResult.Success) {
|
||||
root.completeUnlock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (res === PamResult.Error || res === PamResult.MaxTries || res === PamResult.Failed) {
|
||||
abort();
|
||||
|
||||
if (root.u2fPending) {
|
||||
if (root.u2fState === "waiting") {
|
||||
// AND mode: device was found but auth failed → back to password
|
||||
root.u2fPending = false;
|
||||
root.u2fState = "";
|
||||
fprint.checkAvail();
|
||||
} else {
|
||||
// AND mode: no device found → keep pending, show "Insert...", retry
|
||||
root.u2fState = "insert";
|
||||
u2fErrorRetry.restart();
|
||||
}
|
||||
} else {
|
||||
// OR mode: prompt to insert key, silently retry
|
||||
root.u2fState = "insert";
|
||||
u2fErrorRetry.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,11 +343,13 @@ Scope {
|
||||
|
||||
onLockSecuredChanged: {
|
||||
if (lockSecured) {
|
||||
availProc.running = true;
|
||||
SettingsData.refreshAuthAvailability();
|
||||
root.state = "";
|
||||
root.fprintState = "";
|
||||
root.lockMessage = "";
|
||||
root.resetAuthFlows();
|
||||
fprint.checkAvail();
|
||||
u2f.checkAvail();
|
||||
} else {
|
||||
root.resetAuthFlows();
|
||||
}
|
||||
@@ -302,10 +362,18 @@ Scope {
|
||||
fprint.checkAvail();
|
||||
}
|
||||
|
||||
function onLockFingerprintReadyChanged(): void {
|
||||
fprint.checkAvail();
|
||||
}
|
||||
|
||||
function onEnableU2fChanged(): void {
|
||||
u2f.checkAvail();
|
||||
}
|
||||
|
||||
function onLockU2fReadyChanged(): void {
|
||||
u2f.checkAvail();
|
||||
}
|
||||
|
||||
function onU2fModeChanged(): void {
|
||||
if (root.lockSecured) {
|
||||
u2f.abort();
|
||||
|
||||
@@ -14,8 +14,63 @@ import qs.Modules.Settings.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property bool greeterFprintToggleAvailable: SettingsData.fprintdAvailable || SettingsData.greeterEnableFprint
|
||||
readonly property bool greeterU2fToggleAvailable: SettingsData.u2fAvailable || SettingsData.greeterEnableU2f
|
||||
readonly property bool greeterFprintToggleAvailable: SettingsData.greeterFingerprintCanEnable || SettingsData.greeterEnableFprint
|
||||
readonly property bool greeterU2fToggleAvailable: SettingsData.greeterU2fCanEnable || SettingsData.greeterEnableU2f
|
||||
|
||||
function greeterFingerprintDescription() {
|
||||
const source = SettingsData.greeterFingerprintSource;
|
||||
const reason = SettingsData.greeterFingerprintReason;
|
||||
|
||||
if (source === "pam") {
|
||||
switch (reason) {
|
||||
case "configured_externally":
|
||||
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled. PAM already provides fingerprint auth.") : I18n.tr("PAM already provides fingerprint auth. Enable this to show it at login.");
|
||||
case "missing_enrollment":
|
||||
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled. PAM provides fingerprint auth, but no prints are enrolled yet.") : I18n.tr("PAM provides fingerprint auth, but no prints are enrolled yet.");
|
||||
case "missing_reader":
|
||||
return I18n.tr("PAM provides fingerprint auth, but no reader was detected.");
|
||||
default:
|
||||
return I18n.tr("PAM provides fingerprint auth, but availability could not be confirmed.");
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
case "missing_enrollment":
|
||||
if (SettingsData.greeterEnableFprint)
|
||||
return I18n.tr("Enabled, but no prints are enrolled yet. Enroll fingerprints and run Sync.");
|
||||
return I18n.tr("Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and run Sync later.");
|
||||
case "missing_reader":
|
||||
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled, but no fingerprint reader was detected.") : I18n.tr("No fingerprint reader detected.");
|
||||
case "missing_pam_support":
|
||||
return I18n.tr("Not available — install fprintd and pam_fprintd, or configure greetd PAM.");
|
||||
default:
|
||||
return SettingsData.greeterEnableFprint ? I18n.tr("Enabled, but fingerprint availability could not be confirmed.") : I18n.tr("Fingerprint availability could not be confirmed.");
|
||||
}
|
||||
}
|
||||
|
||||
function greeterU2fDescription() {
|
||||
const source = SettingsData.greeterU2fSource;
|
||||
const reason = SettingsData.greeterU2fReason;
|
||||
|
||||
if (source === "pam") {
|
||||
return SettingsData.greeterEnableU2f ? I18n.tr("Enabled. PAM already provides security-key auth.") : I18n.tr("PAM already provides security-key auth. Enable this to show it at login.");
|
||||
}
|
||||
|
||||
switch (reason) {
|
||||
case "ready":
|
||||
return SettingsData.greeterEnableU2f ? I18n.tr("Run Sync to apply.") : 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.");
|
||||
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, or configure greetd PAM.");
|
||||
default:
|
||||
return SettingsData.greeterEnableU2f ? I18n.tr("Enabled, but security-key availability could not be confirmed.") : I18n.tr("Security-key availability could not be confirmed.");
|
||||
}
|
||||
}
|
||||
|
||||
function refreshAuthDetection() {
|
||||
SettingsData.refreshAuthAvailability();
|
||||
@@ -481,15 +536,8 @@ Item {
|
||||
settingKey: "greeterEnableFprint"
|
||||
tags: ["greeter", "fingerprint", "fprintd", "login", "auth"]
|
||||
text: I18n.tr("Enable fingerprint at login")
|
||||
description: {
|
||||
if (!SettingsData.fprintdAvailable) {
|
||||
if (SettingsData.greeterEnableFprint)
|
||||
return I18n.tr("Enabled in settings, but fingerprint availability could not yet be confirmed. Re-open after enrolling fingerprints or reconnecting the reader.");
|
||||
return I18n.tr("Not available — install fprintd and enroll fingerprints.");
|
||||
}
|
||||
return SettingsData.greeterEnableFprint ? I18n.tr("Run Sync to apply. Fingerprint-only login may not unlock GNOME Keyring.") : I18n.tr("Only off for DMS-managed PAM lines. If greetd includes system-auth/common-auth/password-auth with pam_fprintd, fingerprint still stays enabled.");
|
||||
}
|
||||
descriptionColor: SettingsData.fprintdAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||
description: root.greeterFingerprintDescription()
|
||||
descriptionColor: (SettingsData.greeterFingerprintReason === "ready" || SettingsData.greeterFingerprintReason === "configured_externally") ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.greeterEnableFprint
|
||||
enabled: root.greeterFprintToggleAvailable
|
||||
onToggled: checked => SettingsData.set("greeterEnableFprint", checked)
|
||||
@@ -499,15 +547,8 @@ Item {
|
||||
settingKey: "greeterEnableU2f"
|
||||
tags: ["greeter", "u2f", "security", "key", "login", "auth"]
|
||||
text: I18n.tr("Enable security key at login")
|
||||
description: {
|
||||
if (!SettingsData.u2fAvailable) {
|
||||
if (SettingsData.greeterEnableU2f)
|
||||
return I18n.tr("Enabled in settings, but security key availability could not yet be confirmed. Re-open after enrolling keys or updating pam_u2f.");
|
||||
return I18n.tr("Not available — install pam_u2f and enroll keys.");
|
||||
}
|
||||
return SettingsData.greeterEnableU2f ? I18n.tr("Run Sync to apply.") : I18n.tr("Disabled.");
|
||||
}
|
||||
descriptionColor: SettingsData.u2fAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||
description: root.greeterU2fDescription()
|
||||
descriptionColor: (SettingsData.greeterU2fReason === "ready" || SettingsData.greeterU2fReason === "configured_externally") ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.greeterEnableU2f
|
||||
enabled: root.greeterU2fToggleAvailable
|
||||
onToggled: checked => SettingsData.set("greeterEnableU2f", checked)
|
||||
|
||||
@@ -8,8 +8,40 @@ import qs.Modules.Settings.Widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property bool lockFprintToggleAvailable: SettingsData.fprintdAvailable || SettingsData.enableFprint
|
||||
readonly property bool lockU2fToggleAvailable: SettingsData.u2fAvailable || SettingsData.enableU2f
|
||||
readonly property bool lockFprintToggleAvailable: SettingsData.lockFingerprintCanEnable || SettingsData.enableFprint
|
||||
readonly property bool lockU2fToggleAvailable: SettingsData.lockU2fCanEnable || SettingsData.enableU2f
|
||||
|
||||
function lockFingerprintDescription() {
|
||||
switch (SettingsData.lockFingerprintReason) {
|
||||
case "ready":
|
||||
return 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("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.");
|
||||
case "missing_pam_support":
|
||||
return I18n.tr("Not available — install fprintd and pam_fprintd.");
|
||||
default:
|
||||
return SettingsData.enableFprint ? I18n.tr("Enabled, but fingerprint availability could not be confirmed.") : I18n.tr("Fingerprint availability could not be confirmed.");
|
||||
}
|
||||
}
|
||||
|
||||
function lockU2fDescription() {
|
||||
switch (SettingsData.lockU2fReason) {
|
||||
case "ready":
|
||||
return 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("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.");
|
||||
default:
|
||||
return SettingsData.enableU2f ? I18n.tr("Enabled, but security-key availability could not be confirmed.") : I18n.tr("Security-key availability could not be confirmed.");
|
||||
}
|
||||
}
|
||||
|
||||
function refreshAuthDetection() {
|
||||
SettingsData.refreshAuthAvailability();
|
||||
@@ -184,14 +216,8 @@ Item {
|
||||
settingKey: "enableFprint"
|
||||
tags: ["lock", "screen", "fingerprint", "authentication", "biometric", "fprint"]
|
||||
text: I18n.tr("Enable fingerprint authentication")
|
||||
description: {
|
||||
if (SettingsData.fprintdAvailable)
|
||||
return I18n.tr("Use fingerprint reader for lock screen authentication (requires enrolled fingerprints)");
|
||||
if (SettingsData.enableFprint)
|
||||
return I18n.tr("Enabled in settings, but fingerprint availability could not yet be confirmed. Re-open after enrolling fingerprints or reconnecting the reader.");
|
||||
return I18n.tr("Not available — install fprintd and enroll fingerprints.");
|
||||
}
|
||||
descriptionColor: SettingsData.fprintdAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||
description: root.lockFingerprintDescription()
|
||||
descriptionColor: SettingsData.lockFingerprintReason === "ready" ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.enableFprint
|
||||
enabled: root.lockFprintToggleAvailable
|
||||
onToggled: checked => SettingsData.set("enableFprint", checked)
|
||||
@@ -201,14 +227,8 @@ Item {
|
||||
settingKey: "enableU2f"
|
||||
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "fido", "authentication", "hardware"]
|
||||
text: I18n.tr("Enable security key authentication", "Enable FIDO2/U2F hardware security key for lock screen")
|
||||
description: {
|
||||
if (SettingsData.u2fAvailable)
|
||||
return I18n.tr("Use a FIDO2/U2F security key (e.g. YubiKey) for lock screen authentication (requires enrolled keys)", "lock screen U2F security key setting");
|
||||
if (SettingsData.enableU2f)
|
||||
return I18n.tr("Enabled in settings, but security key availability could not yet be confirmed. Re-open after enrolling keys or updating pam_u2f.");
|
||||
return I18n.tr("Not available — install pam_u2f and enroll keys.");
|
||||
}
|
||||
descriptionColor: SettingsData.u2fAvailable ? Theme.surfaceVariantText : Theme.warning
|
||||
description: root.lockU2fDescription()
|
||||
descriptionColor: SettingsData.lockU2fReason === "ready" ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.enableU2f
|
||||
enabled: root.lockU2fToggleAvailable
|
||||
onToggled: checked => SettingsData.set("enableU2f", checked)
|
||||
|
||||
Reference in New Issue
Block a user