mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
feat(auth): expand shared PAM support for custom policies
- Add support for DMS and System managed factor policies - Add validated dedicated security-key PAM / U2F Key sources - Restore U2F OR/AND flows Related: #2874 Port 1.5
This commit is contained in:
@@ -898,6 +898,8 @@ Singleton {
|
||||
property string lockPamPath: ""
|
||||
property bool lockPamInlineFprint: false
|
||||
property bool lockPamInlineU2f: false
|
||||
property bool lockPamExternallyManaged: false
|
||||
property string lockU2fPamPath: ""
|
||||
property bool greeterPamExternallyManaged: false
|
||||
property string lockScreenInactiveColor: "#000000"
|
||||
property int lockScreenNotificationMode: 0
|
||||
|
||||
@@ -63,6 +63,7 @@ Singleton {
|
||||
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 lockU2fCustomSourceDetected: (settingsRoot?.lockU2fPamPath || "") !== "" && customU2fPamWatcher.loaded
|
||||
readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd")
|
||||
readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f")
|
||||
|
||||
@@ -176,7 +177,7 @@ Singleton {
|
||||
readonly property bool lockU2fReady: {
|
||||
if (forcedU2fAvailable !== null)
|
||||
return forcedU2fAvailable;
|
||||
return lockU2fCustomConfigDetected || homeU2fKeysDetected;
|
||||
return lockU2fCustomSourceDetected || lockU2fCustomConfigDetected || homeU2fKeysDetected;
|
||||
}
|
||||
|
||||
readonly property bool lockU2fCanEnable: {
|
||||
@@ -727,6 +728,12 @@ Singleton {
|
||||
onLoadFailed: root.dankshellU2fPamText = ""
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: customU2fPamWatcher
|
||||
path: root.settingsRoot?.lockU2fPamPath || ""
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: u2fKeysWatcher
|
||||
path: root.u2fKeysPath
|
||||
|
||||
@@ -459,6 +459,8 @@ var SPEC = {
|
||||
lockPamPath: { def: "" },
|
||||
lockPamInlineFprint: { def: false },
|
||||
lockPamInlineU2f: { def: false },
|
||||
lockPamExternallyManaged: { def: false },
|
||||
lockU2fPamPath: { def: "" },
|
||||
lockScreenInactiveColor: { def: "#000000" },
|
||||
lockScreenNotificationMode: { def: 0 },
|
||||
lockScreenVideoEnabled: { def: false },
|
||||
|
||||
@@ -71,7 +71,7 @@ Scope {
|
||||
}
|
||||
|
||||
function proceedAfterPrimaryAuth(): void {
|
||||
if (SettingsData.enableU2f && SettingsData.u2fMode === "and" && u2f.available) {
|
||||
if (!root.u2fSuppressedByPrimaryPam && SettingsData.enableU2f && SettingsData.u2fMode === "and" && u2f.available) {
|
||||
u2f.startForSecondFactor();
|
||||
} else {
|
||||
completeUnlock();
|
||||
@@ -91,8 +91,9 @@ Scope {
|
||||
}
|
||||
|
||||
readonly property bool customPamActive: SettingsData.lockPamPath !== "" && customPamWatcher.loaded
|
||||
readonly property bool fprintSuppressedByCustomPam: customPamActive && SettingsData.lockPamInlineFprint
|
||||
readonly property bool u2fSuppressedByCustomPam: customPamActive && SettingsData.lockPamInlineU2f
|
||||
readonly property bool fprintSuppressedByPrimaryPam: SettingsData.lockPamExternallyManaged || (customPamActive && SettingsData.lockPamInlineFprint)
|
||||
readonly property bool u2fSuppressedByPrimaryPam: SettingsData.lockPamExternallyManaged || (customPamActive && SettingsData.lockPamInlineU2f)
|
||||
readonly property bool customU2fPamActive: SettingsData.lockU2fPamPath !== "" && customU2fPamWatcher.loaded
|
||||
|
||||
FileView {
|
||||
id: customPamWatcher
|
||||
@@ -109,16 +110,16 @@ Scope {
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: nixosMarker
|
||||
id: u2fConfigWatcher
|
||||
|
||||
path: "/etc/NIXOS"
|
||||
path: "/etc/pam.d/dankshell-u2f"
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: u2fConfigWatcher
|
||||
id: customU2fPamWatcher
|
||||
|
||||
path: "/etc/pam.d/dankshell-u2f"
|
||||
path: SettingsData.lockU2fPamPath !== "" ? SettingsData.lockU2fPamPath : ""
|
||||
printErrors: false
|
||||
}
|
||||
|
||||
@@ -145,26 +146,23 @@ Scope {
|
||||
}
|
||||
|
||||
function ensureUserPamConfig(): void {
|
||||
if (root.runningFromNixStore || resolveUserPam.running)
|
||||
if (SettingsData.lockPamExternallyManaged || resolveUserPam.running)
|
||||
return;
|
||||
resolveUserPam.running = true;
|
||||
}
|
||||
|
||||
Component.onCompleted: ensureUserPamConfig()
|
||||
|
||||
// Detects Nix-installed DMS on non-NixOS systems
|
||||
readonly property bool runningFromNixStore: Quickshell.shellDir.startsWith("/nix/store/")
|
||||
|
||||
PamContext {
|
||||
id: passwd
|
||||
|
||||
config: {
|
||||
if (root.customPamActive)
|
||||
return SettingsData.lockPamPath.slice(SettingsData.lockPamPath.lastIndexOf("/") + 1);
|
||||
if (SettingsData.lockPamExternallyManaged)
|
||||
return "login";
|
||||
if (dankshellConfigWatcher.loaded)
|
||||
return "dankshell";
|
||||
if (nixosMarker.loaded || root.runningFromNixStore)
|
||||
return "login";
|
||||
if (userPamWatcher.loaded)
|
||||
return "dankshell";
|
||||
return "login";
|
||||
@@ -174,9 +172,9 @@ Scope {
|
||||
const idx = SettingsData.lockPamPath.lastIndexOf("/");
|
||||
return idx > 0 ? SettingsData.lockPamPath.slice(0, idx) : "/";
|
||||
}
|
||||
if (dankshellConfigWatcher.loaded)
|
||||
if (SettingsData.lockPamExternallyManaged)
|
||||
return "/etc/pam.d";
|
||||
if (nixosMarker.loaded || root.runningFromNixStore)
|
||||
if (dankshellConfigWatcher.loaded)
|
||||
return "/etc/pam.d";
|
||||
if (userPamWatcher.loaded)
|
||||
return root.userPamDir;
|
||||
@@ -265,7 +263,7 @@ Scope {
|
||||
property int errorTries
|
||||
|
||||
function checkAvail(): void {
|
||||
if (!available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByCustomPam) {
|
||||
if (!available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByPrimaryPam) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
@@ -325,7 +323,7 @@ Scope {
|
||||
property bool available: SettingsData.lockU2fReady
|
||||
|
||||
function checkAvail(): void {
|
||||
if (!available || !SettingsData.enableU2f || !root.lockSecured || root.u2fSuppressedByCustomPam) {
|
||||
if (!available || !SettingsData.enableU2f || !root.lockSecured || root.u2fSuppressedByPrimaryPam) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
@@ -335,7 +333,7 @@ Scope {
|
||||
}
|
||||
|
||||
function startForSecondFactor(): void {
|
||||
if (!available || !SettingsData.enableU2f || root.u2fSuppressedByCustomPam) {
|
||||
if (!available || !SettingsData.enableU2f || root.u2fSuppressedByPrimaryPam) {
|
||||
root.completeUnlock();
|
||||
return;
|
||||
}
|
||||
@@ -348,7 +346,7 @@ Scope {
|
||||
}
|
||||
|
||||
function startForAlternativeAuth(): void {
|
||||
if (!available || !SettingsData.enableU2f || root.u2fSuppressedByCustomPam || SettingsData.u2fMode !== "or" || root.unlockInProgress || passwd.active || active)
|
||||
if (!available || !SettingsData.enableU2f || root.u2fSuppressedByPrimaryPam || SettingsData.u2fMode !== "or" || root.unlockInProgress || passwd.active || active)
|
||||
return;
|
||||
abort();
|
||||
root.u2fPending = true;
|
||||
@@ -358,8 +356,18 @@ Scope {
|
||||
start();
|
||||
}
|
||||
|
||||
config: u2fConfigWatcher.loaded ? "dankshell-u2f" : "u2f"
|
||||
configDirectory: u2fConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam"
|
||||
config: {
|
||||
if (root.customU2fPamActive)
|
||||
return SettingsData.lockU2fPamPath.slice(SettingsData.lockU2fPamPath.lastIndexOf("/") + 1);
|
||||
return u2fConfigWatcher.loaded ? "dankshell-u2f" : "u2f";
|
||||
}
|
||||
configDirectory: {
|
||||
if (root.customU2fPamActive) {
|
||||
const idx = SettingsData.lockU2fPamPath.lastIndexOf("/");
|
||||
return idx > 0 ? SettingsData.lockU2fPamPath.slice(0, idx) : "/";
|
||||
}
|
||||
return u2fConfigWatcher.loaded ? "/etc/pam.d" : Quickshell.shellDir + "/assets/pam";
|
||||
}
|
||||
|
||||
onMessageChanged: {
|
||||
if (message.toLowerCase().includes("touch"))
|
||||
@@ -478,7 +486,7 @@ Scope {
|
||||
root.attemptInfoMessages = [];
|
||||
root.lockoutAnnouncedThisAttempt = false;
|
||||
root.resetAuthFlows();
|
||||
if (!dankshellConfigWatcher.loaded && !nixosMarker.loaded && !userPamWatcher.loaded)
|
||||
if (!SettingsData.lockPamExternallyManaged && !dankshellConfigWatcher.loaded && !userPamWatcher.loaded)
|
||||
ensureUserPamConfig();
|
||||
fprint.checkAvail();
|
||||
u2f.checkAvail();
|
||||
@@ -516,6 +524,19 @@ Scope {
|
||||
u2f.checkAvail();
|
||||
}
|
||||
|
||||
function onLockPamExternallyManagedChanged(): void {
|
||||
root.resetAuthFlows();
|
||||
if (!SettingsData.lockPamExternallyManaged)
|
||||
root.ensureUserPamConfig();
|
||||
fprint.checkAvail();
|
||||
u2f.checkAvail();
|
||||
}
|
||||
|
||||
function onLockU2fPamPathChanged(): void {
|
||||
u2f.abort();
|
||||
u2f.checkAvail();
|
||||
}
|
||||
|
||||
function onU2fModeChanged(): void {
|
||||
if (root.lockSecured) {
|
||||
u2f.abort();
|
||||
|
||||
@@ -11,6 +11,10 @@ Item {
|
||||
|
||||
readonly property bool lockFprintToggleAvailable: SettingsData.lockFingerprintCanEnable || SettingsData.enableFprint
|
||||
readonly property bool lockU2fToggleAvailable: SettingsData.lockU2fCanEnable || SettingsData.enableU2f
|
||||
readonly property bool primaryPamHasFprint: SettingsData.lockPamPath !== "" && SettingsData.lockPamInlineFprint
|
||||
readonly property bool primaryPamHasU2f: SettingsData.lockPamPath !== "" && SettingsData.lockPamInlineU2f
|
||||
readonly property bool lockFprintControlledByPrimary: SettingsData.lockPamExternallyManaged || primaryPamHasFprint
|
||||
readonly property bool lockU2fControlledByPrimary: SettingsData.lockPamExternallyManaged || primaryPamHasU2f
|
||||
|
||||
property var authServices: []
|
||||
property bool authValidateRunning: false
|
||||
@@ -19,6 +23,12 @@ Item {
|
||||
property string authValidateMessage: ""
|
||||
property string authPendingApplyPath: ""
|
||||
property bool authShowCustom: false
|
||||
property bool u2fValidateRunning: false
|
||||
property bool u2fValidateOk: false
|
||||
property bool u2fValidateWarn: false
|
||||
property string u2fValidateMessage: ""
|
||||
property string u2fPendingApplyPath: ""
|
||||
property bool u2fShowCustom: false
|
||||
|
||||
readonly property string authAutoLabel: I18n.tr("Auto", "automatic PAM authentication source option")
|
||||
readonly property string authCustomLabel: I18n.tr("Custom...", "custom PAM authentication source option")
|
||||
@@ -37,6 +47,8 @@ Item {
|
||||
return svc ? authServiceLabel(svc) : authCustomLabel;
|
||||
}
|
||||
|
||||
readonly property string u2fAuthCurrentValue: SettingsData.lockU2fPamPath === "" ? authAutoLabel : authCustomLabel
|
||||
|
||||
function refreshAuthServices() {
|
||||
authListServicesProcess.running = true;
|
||||
}
|
||||
@@ -62,6 +74,25 @@ Item {
|
||||
authValidateProcess.running = true;
|
||||
}
|
||||
|
||||
function applyAutoU2fSource() {
|
||||
SettingsData.set("lockU2fPamPath", "");
|
||||
root.u2fValidateOk = false;
|
||||
root.u2fValidateWarn = false;
|
||||
root.u2fValidateMessage = "";
|
||||
}
|
||||
|
||||
function validateAndApplyU2fSource(path) {
|
||||
if (!path)
|
||||
return;
|
||||
root.u2fPendingApplyPath = path;
|
||||
root.u2fValidateMessage = "";
|
||||
root.u2fValidateOk = false;
|
||||
root.u2fValidateWarn = false;
|
||||
root.u2fValidateRunning = true;
|
||||
u2fValidateProcess.command = ["dms", "auth", "validate", "--purpose", "u2f", "--path", path, "--json"];
|
||||
u2fValidateProcess.running = true;
|
||||
}
|
||||
|
||||
function lockFingerprintDescription() {
|
||||
switch (SettingsData.lockFingerprintReason) {
|
||||
case "ready":
|
||||
@@ -161,12 +192,12 @@ Item {
|
||||
} catch (e) {}
|
||||
|
||||
if (!data) {
|
||||
root.authValidateMessage = "validation failed — is dms in PATH?";
|
||||
root.authValidateMessage = "Validation failed — is DMS in PATH?";
|
||||
return;
|
||||
}
|
||||
if (!data.valid) {
|
||||
const errs = Array.isArray(data.errors) ? data.errors : [];
|
||||
root.authValidateMessage = ["not applied:", ...errs].join("\n");
|
||||
root.authValidateMessage = ["Not applied.", ...errs].join("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -176,7 +207,46 @@ Item {
|
||||
const warns = Array.isArray(data.warnings) ? data.warnings : [];
|
||||
root.authValidateOk = true;
|
||||
root.authValidateWarn = warns.length > 0;
|
||||
root.authValidateMessage = warns.length > 0 ? ["applied with warnings:", ...warns].join("\n") : "applied";
|
||||
root.authValidateMessage = warns.length > 0 ? ["Applied with warnings.", ...warns].join("\n") : "Applied.";
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: u2fValidateProcess
|
||||
running: false
|
||||
|
||||
property string collected: ""
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: u2fValidateProcess.collected = text || ""
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
root.u2fValidateRunning = false;
|
||||
root.u2fValidateOk = false;
|
||||
root.u2fValidateWarn = false;
|
||||
|
||||
let data = null;
|
||||
try {
|
||||
data = JSON.parse(u2fValidateProcess.collected);
|
||||
} catch (e) {}
|
||||
|
||||
if (!data) {
|
||||
root.u2fValidateMessage = "Validation failed — is DMS in PATH?";
|
||||
return;
|
||||
}
|
||||
if (!data.valid) {
|
||||
const errs = Array.isArray(data.errors) ? data.errors : [];
|
||||
root.u2fValidateMessage = ["Not applied.", ...errs].join("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsData.set("lockU2fPamPath", root.u2fPendingApplyPath);
|
||||
const warns = Array.isArray(data.warnings) ? data.warnings : [];
|
||||
root.u2fValidateOk = true;
|
||||
root.u2fValidateWarn = warns.length > 0;
|
||||
root.u2fValidateMessage = warns.length > 0 ? ["Applied with warnings.", ...warns].join("\n") : "Applied.";
|
||||
root.refreshAuthDetection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,9 +396,17 @@ Item {
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "key"
|
||||
title: "Authentication Source"
|
||||
title: I18n.tr("Lock Screen Authentication")
|
||||
settingKey: "lockAuthSource"
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Changes apply automatically")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "lockPamPath"
|
||||
tags: ["lock", "screen", "pam", "authentication", "source", "service"]
|
||||
@@ -395,14 +473,120 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: (SettingsData.lockPamInlineFprint && SettingsData.enableFprint) || (SettingsData.lockPamInlineU2f && SettingsData.enableU2f)
|
||||
text: "The pinned PAM stack already prompts for fingerprint or security key, so DMS skips its own prompts"
|
||||
visible: !SettingsData.lockPamExternallyManaged && (root.primaryPamHasFprint || root.primaryPamHasU2f)
|
||||
text: I18n.tr("Selected PAM source already manages the detected factors.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.warning
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
topPadding: Theme.spacingS
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockPamExternallyManaged"
|
||||
tags: ["lock", "screen", "pam", "managed", "external", "authentication", "policy"]
|
||||
text: I18n.tr("Use system PAM authentication")
|
||||
description: SettingsData.lockPamExternallyManaged ? I18n.tr("System PAM sets the authentication policy.") : I18n.tr("DMS manages the factors below.")
|
||||
checked: SettingsData.lockPamExternallyManaged
|
||||
onToggled: checked => SettingsData.set("lockPamExternallyManaged", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "enableFprint"
|
||||
tags: ["lock", "screen", "fingerprint", "authentication", "biometric", "fprint"]
|
||||
text: I18n.tr("Enable fingerprint authentication")
|
||||
description: root.lockFprintControlledByPrimary ? I18n.tr("Managed by the primary PAM source.") : root.lockFingerprintDescription()
|
||||
descriptionColor: root.lockFprintControlledByPrimary || SettingsData.lockFingerprintReason === "ready" ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.enableFprint || root.primaryPamHasFprint
|
||||
enabled: root.lockFprintToggleAvailable && !root.lockFprintControlledByPrimary
|
||||
onToggled: checked => SettingsData.set("enableFprint", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
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: root.lockU2fControlledByPrimary ? I18n.tr("Managed by the primary PAM source.") : root.lockU2fDescription()
|
||||
descriptionColor: root.lockU2fControlledByPrimary || SettingsData.lockU2fReason === "ready" ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.enableU2f || root.primaryPamHasU2f
|
||||
enabled: root.lockU2fToggleAvailable && !root.lockU2fControlledByPrimary
|
||||
onToggled: checked => SettingsData.set("enableU2f", checked)
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "u2fMode"
|
||||
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "mode", "factor", "second"]
|
||||
text: I18n.tr("Security key mode", "lock screen U2F security key mode setting")
|
||||
description: I18n.tr("Alternative uses the passkey button. Second factor follows password or fingerprint.", "lock screen U2F security key mode setting")
|
||||
visible: SettingsData.enableU2f && !root.lockU2fControlledByPrimary
|
||||
options: [I18n.tr("Alternative (OR)", "U2F mode option: key works as standalone unlock method"), I18n.tr("Second Factor (AND)", "U2F mode option: key required after password or fingerprint")]
|
||||
currentValue: SettingsData.u2fMode === "and" ? I18n.tr("Second Factor (AND)", "U2F mode option: key required after password or fingerprint") : I18n.tr("Alternative (OR)", "U2F mode option: key works as standalone unlock method")
|
||||
onValueChanged: value => {
|
||||
if (value === I18n.tr("Second Factor (AND)", "U2F mode option: key required after password or fingerprint"))
|
||||
SettingsData.set("u2fMode", "and");
|
||||
else
|
||||
SettingsData.set("u2fMode", "or");
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "lockU2fPamPath"
|
||||
tags: ["lock", "screen", "pam", "u2f", "security", "key", "source", "service"]
|
||||
text: I18n.tr("Security Key PAM Source")
|
||||
description: SettingsData.lockU2fPamPath !== "" ? SettingsData.lockU2fPamPath : I18n.tr("Auto uses an installed or bundled key-only service.")
|
||||
visible: !root.lockU2fControlledByPrimary
|
||||
options: [root.authAutoLabel, root.authCustomLabel]
|
||||
currentValue: root.u2fAuthCurrentValue
|
||||
onValueChanged: value => {
|
||||
if (value === root.authAutoLabel) {
|
||||
root.u2fShowCustom = false;
|
||||
root.applyAutoU2fSource();
|
||||
return;
|
||||
}
|
||||
root.u2fShowCustom = true;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: !root.lockU2fControlledByPrimary && (root.u2fShowCustom || root.u2fAuthCurrentValue === root.authCustomLabel)
|
||||
|
||||
DankTextField {
|
||||
id: customU2fPamField
|
||||
width: parent.width - validateU2fPamButton.width - Theme.spacingS
|
||||
placeholderText: "/etc/pam.d/dankshell-u2f"
|
||||
text: SettingsData.lockU2fPamPath
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
}
|
||||
|
||||
DankButton {
|
||||
id: validateU2fPamButton
|
||||
text: I18n.tr("Apply Changes", "validate and apply custom U2F PAM authentication source")
|
||||
enabled: !root.u2fValidateRunning && customU2fPamField.text.trim() !== ""
|
||||
onClicked: root.validateAndApplyU2fSource(customU2fPamField.text.trim())
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: Math.min(160, u2fStatusText.implicitHeight + Theme.spacingM * 2)
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHighest
|
||||
visible: !root.lockU2fControlledByPrimary && root.u2fValidateMessage !== ""
|
||||
|
||||
StyledText {
|
||||
id: u2fStatusText
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
text: root.u2fValidateMessage
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: "monospace"
|
||||
color: !root.u2fValidateOk ? Theme.error : (root.u2fValidateWarn ? Theme.warning : Theme.surfaceVariantText)
|
||||
wrapMode: Text.Wrap
|
||||
verticalAlignment: Text.AlignTop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
@@ -461,53 +645,6 @@ Item {
|
||||
checked: SettingsData.lockAtStartup
|
||||
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"]
|
||||
text: I18n.tr("Enable fingerprint authentication")
|
||||
description: root.lockFingerprintDescription()
|
||||
descriptionColor: SettingsData.lockFingerprintReason === "ready" ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.enableFprint
|
||||
enabled: root.lockFprintToggleAvailable
|
||||
onToggled: checked => SettingsData.set("enableFprint", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
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: root.lockU2fDescription()
|
||||
descriptionColor: SettingsData.lockU2fReason === "ready" ? Theme.surfaceVariantText : Theme.warning
|
||||
checked: SettingsData.enableU2f
|
||||
enabled: root.lockU2fToggleAvailable
|
||||
onToggled: checked => SettingsData.set("enableU2f", checked)
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "u2fMode"
|
||||
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "mode", "factor", "second"]
|
||||
text: I18n.tr("Security key mode", "lock screen U2F security key mode setting")
|
||||
description: I18n.tr("'Alternative' lets the key unlock on its own. 'Second factor' requires password or fingerprint first, then the key.", "lock screen U2F security key mode setting")
|
||||
visible: SettingsData.enableU2f
|
||||
options: [I18n.tr("Alternative (OR)", "U2F mode option: key works as standalone unlock method"), I18n.tr("Second Factor (AND)", "U2F mode option: key required after password or fingerprint")]
|
||||
currentValue: SettingsData.u2fMode === "and" ? I18n.tr("Second Factor (AND)", "U2F mode option: key required after password or fingerprint") : I18n.tr("Alternative (OR)", "U2F mode option: key works as standalone unlock method")
|
||||
onValueChanged: value => {
|
||||
if (value === I18n.tr("Second Factor (AND)", "U2F mode option: key required after password or fingerprint"))
|
||||
SettingsData.set("u2fMode", "and");
|
||||
else
|
||||
SettingsData.set("u2fMode", "or");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
|
||||
@@ -4634,24 +4634,6 @@
|
||||
"zenbrowser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "lockAuthSource",
|
||||
"label": "Authentication Source",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock Screen",
|
||||
"keywords": [
|
||||
"authentication",
|
||||
"lock",
|
||||
"login",
|
||||
"pam",
|
||||
"password",
|
||||
"screen",
|
||||
"security",
|
||||
"service",
|
||||
"source"
|
||||
],
|
||||
"icon": "key"
|
||||
},
|
||||
{
|
||||
"section": "lockPamPath",
|
||||
"label": "Authentication Source",
|
||||
@@ -4728,10 +4710,14 @@
|
||||
"fprint",
|
||||
"lock",
|
||||
"login",
|
||||
"managed",
|
||||
"password",
|
||||
"primary",
|
||||
"screen",
|
||||
"security"
|
||||
]
|
||||
"security",
|
||||
"source"
|
||||
],
|
||||
"description": "Managed by the primary PAM source."
|
||||
},
|
||||
{
|
||||
"section": "loginctlLockIntegration",
|
||||
@@ -4769,12 +4755,16 @@
|
||||
"key",
|
||||
"lock",
|
||||
"login",
|
||||
"managed",
|
||||
"password",
|
||||
"primary",
|
||||
"screen",
|
||||
"security",
|
||||
"source",
|
||||
"u2f",
|
||||
"yubikey"
|
||||
]
|
||||
],
|
||||
"description": "Managed by the primary PAM source."
|
||||
},
|
||||
{
|
||||
"section": "_tab_11",
|
||||
@@ -4814,6 +4804,25 @@
|
||||
"icon": "palette",
|
||||
"description": "Font used for the clock and date on the lock screen"
|
||||
},
|
||||
{
|
||||
"section": "lockAuthSource",
|
||||
"label": "Lock Screen Authentication",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock Screen",
|
||||
"keywords": [
|
||||
"authentication",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"pam",
|
||||
"password",
|
||||
"screen",
|
||||
"security",
|
||||
"service",
|
||||
"source"
|
||||
],
|
||||
"icon": "key"
|
||||
},
|
||||
{
|
||||
"section": "lockDisplay",
|
||||
"label": "Lock Screen Display",
|
||||
@@ -5005,24 +5014,54 @@
|
||||
],
|
||||
"description": "Turn off all displays immediately when the lock screen activates"
|
||||
},
|
||||
{
|
||||
"section": "lockU2fPamPath",
|
||||
"label": "Security Key PAM Source",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock Screen",
|
||||
"keywords": [
|
||||
"auto",
|
||||
"bundled",
|
||||
"installed",
|
||||
"key",
|
||||
"lock",
|
||||
"login",
|
||||
"pam",
|
||||
"password",
|
||||
"screen",
|
||||
"security",
|
||||
"service",
|
||||
"source",
|
||||
"u2f",
|
||||
"uses"
|
||||
],
|
||||
"description": "Auto uses an installed or bundled key-only service."
|
||||
},
|
||||
{
|
||||
"section": "u2fMode",
|
||||
"label": "Security key mode",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock Screen",
|
||||
"keywords": [
|
||||
"alternative",
|
||||
"button",
|
||||
"factor",
|
||||
"fingerprint",
|
||||
"follows",
|
||||
"key",
|
||||
"lock",
|
||||
"login",
|
||||
"mode",
|
||||
"passkey",
|
||||
"password",
|
||||
"screen",
|
||||
"second",
|
||||
"security",
|
||||
"u2f",
|
||||
"uses",
|
||||
"yubikey"
|
||||
]
|
||||
],
|
||||
"description": "Alternative uses the passkey button. Second factor follows password or fingerprint."
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowMediaPlayer",
|
||||
@@ -5159,6 +5198,27 @@
|
||||
"time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"section": "lockPamExternallyManaged",
|
||||
"label": "Use system PAM authentication",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock Screen",
|
||||
"keywords": [
|
||||
"authentication",
|
||||
"external",
|
||||
"lock",
|
||||
"login",
|
||||
"managed",
|
||||
"pam",
|
||||
"password",
|
||||
"policy",
|
||||
"screen",
|
||||
"security",
|
||||
"sets",
|
||||
"system"
|
||||
],
|
||||
"description": "System PAM sets the authentication policy."
|
||||
},
|
||||
{
|
||||
"section": "videoScreensaver",
|
||||
"label": "Video Screensaver",
|
||||
|
||||
Reference in New Issue
Block a user