mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-19 17:54:01 -04:00
* lock: keep the fingerprint reader usable across suspend
The reader stopped responding after the lid was closed and opened, after a long
idle period, and after suspend.
The shell held a PAM session for as long as the screen was locked, so a suspend
always landed on a running verify. fprintd then suspends the device under the
verify, pam_fprintd's ReleaseDevice fails with "device is still busy", and the
claim is never released, so every later Claim fails until fprintd restarts. The
reader is now released when the monitors blank and armed again when they come
back, so a suspend has nothing left to wedge.
The PAM profile drops to max-tries=1 and sets an explicit timeout. pam_fprintd
only counts a wrong finger, so one finished session is now one wrong scan and
maxFprintTries means the number of scans it names rather than five times that.
A timeout expiry arrives as PamResult.Error, exactly like a device fault, so a
session that ran at least as long as the configured timeout is treated as a
planned renewal instead of spending the error budget and painting an error.
A suspend that still lands while the screen is on wedges as before. Recovery now
waits out fprintd's idle timeout instead of climbing to it, which brings the
reader back about 37 seconds after resume instead of about 98.
SessionService gains lidOpened from UPower, because logind can ignore the lid on
AC and resume alone is not enough. The lock PAM state machine is fixed alongside:
PamResult.Failed was dropped, a pending retry outlived the contexts that owned
it, and an availability check reset the wrong-scan counter.
* lock: time the fingerprint session from the verify, not the claim
pam_fprintd's timeout runs from the verify, but the session age was measured
from start(), which folds the claim into it. A slow claim plus a verify that
failed before its own timeout could add up past the timeout and be taken for a
planned renewal, skipping the backoff. A session that never reached a verify had
the same problem.
The age now starts at the message pam_fprintd sends when the verify begins, so a
session that never verified can never qualify as an expiry, and a slow claim no
longer ages a fault. The message is translated and quickshell clears it before
completed, so only its timing is used, never its text.
That message arrives after pam_fprintd has already armed its deadline, because
VerifyStart is asynchronous, so the age read here runs short of the real timeout
and a slack makes it up. The bias is deliberate. A fault misread as a timeout
costs one extra retry, while a timeout misread as a fault parks the reader behind
a backoff that grows to a minute, which on an idle lock screen would leave it
unusable most of the time.
Also corrects the comment that claimed a timeout is reported without a message.
---------
(cherry picked from commit d7074a53eb)
681 lines
21 KiB
QML
681 lines
21 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Services.Pam
|
|
import qs.Common
|
|
import qs.Services
|
|
|
|
Scope {
|
|
id: root
|
|
|
|
property bool lockSecured: false
|
|
property bool unlockInProgress: false
|
|
|
|
readonly property alias passwd: passwd
|
|
readonly property alias fprint: fprint
|
|
readonly property alias u2f: u2f
|
|
property string lockMessage
|
|
property string state
|
|
property string fprintState
|
|
property string u2fState
|
|
property bool u2fPending: false
|
|
property string u2fPendingMode
|
|
property string buffer
|
|
|
|
property var attemptInfoMessages: []
|
|
property bool lockoutAnnouncedThisAttempt: false
|
|
|
|
signal flashMsg
|
|
signal unlockRequested
|
|
|
|
function resetAuthFlows(): void {
|
|
passwd.abort();
|
|
fprint.stop();
|
|
u2f.abort();
|
|
u2fErrorRetry.running = false;
|
|
u2fPendingTimeout.running = false;
|
|
passwdActiveTimeout.running = false;
|
|
unlockRequestTimeout.running = false;
|
|
root.u2fPending = false;
|
|
root.u2fPendingMode = "";
|
|
root.u2fState = "";
|
|
root.unlockInProgress = false;
|
|
}
|
|
|
|
function recoverFromAuthStall(newState: string): void {
|
|
resetAuthFlows();
|
|
root.state = newState;
|
|
flashMsg();
|
|
stateReset.restart();
|
|
fprint.checkAvail();
|
|
u2f.checkAvail();
|
|
}
|
|
|
|
function completeUnlock(): void {
|
|
if (!root.unlockInProgress) {
|
|
root.unlockInProgress = true;
|
|
passwd.abort();
|
|
fprint.stop();
|
|
u2f.abort();
|
|
u2fErrorRetry.running = false;
|
|
u2fPendingTimeout.running = false;
|
|
root.u2fPending = false;
|
|
root.u2fPendingMode = "";
|
|
root.u2fState = "";
|
|
unlockRequestTimeout.restart();
|
|
unlockRequested();
|
|
}
|
|
}
|
|
|
|
function proceedAfterPrimaryAuth(): void {
|
|
if (!root.u2fSuppressedByPrimaryPam && SettingsData.enableU2f && SettingsData.u2fMode === "and" && u2f.available) {
|
|
u2f.startForSecondFactor();
|
|
} else {
|
|
completeUnlock();
|
|
}
|
|
}
|
|
|
|
function cancelU2fPending(): void {
|
|
if (!root.u2fPending)
|
|
return;
|
|
u2f.abort();
|
|
u2fErrorRetry.running = false;
|
|
u2fPendingTimeout.running = false;
|
|
root.u2fPending = false;
|
|
root.u2fPendingMode = "";
|
|
root.u2fState = "";
|
|
fprint.checkAvail();
|
|
}
|
|
|
|
readonly property bool customPamActive: SettingsData.lockPamPath !== "" && customPamWatcher.loaded
|
|
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
|
|
|
|
path: SettingsData.lockPamPath !== "" ? SettingsData.lockPamPath : ""
|
|
printErrors: false
|
|
}
|
|
|
|
FileView {
|
|
id: dankshellConfigWatcher
|
|
|
|
path: "/etc/pam.d/dankshell"
|
|
printErrors: false
|
|
}
|
|
|
|
FileView {
|
|
id: u2fConfigWatcher
|
|
|
|
path: "/etc/pam.d/dankshell-u2f"
|
|
watchChanges: true
|
|
printErrors: false
|
|
}
|
|
|
|
FileView {
|
|
id: customU2fPamWatcher
|
|
|
|
path: SettingsData.lockU2fPamPath !== "" ? SettingsData.lockU2fPamPath : ""
|
|
printErrors: false
|
|
}
|
|
|
|
// Fallback stack written by `dms auth resolve-lock` when no managed
|
|
// /etc/pam.d/dankshell exists. See #2789.
|
|
readonly property string userPamDir: Paths.strip(Paths.state) + "/pam"
|
|
|
|
FileView {
|
|
id: userPamWatcher
|
|
|
|
path: root.userPamDir + "/dankshell"
|
|
printErrors: false
|
|
}
|
|
|
|
Process {
|
|
id: resolveUserPam
|
|
|
|
command: ["dms", "auth", "resolve-lock", "--quiet"]
|
|
running: false
|
|
onExited: exitCode => {
|
|
if (exitCode === 0)
|
|
userPamWatcher.reload();
|
|
}
|
|
}
|
|
|
|
function ensureUserPamConfig(): void {
|
|
if (SettingsData.lockPamExternallyManaged || resolveUserPam.running)
|
|
return;
|
|
resolveUserPam.running = true;
|
|
}
|
|
|
|
Component.onCompleted: ensureUserPamConfig()
|
|
|
|
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 (userPamWatcher.loaded)
|
|
return "dankshell";
|
|
return "login";
|
|
}
|
|
configDirectory: {
|
|
if (root.customPamActive) {
|
|
const idx = SettingsData.lockPamPath.lastIndexOf("/");
|
|
return idx > 0 ? SettingsData.lockPamPath.slice(0, idx) : "/";
|
|
}
|
|
if (SettingsData.lockPamExternallyManaged)
|
|
return "/etc/pam.d";
|
|
if (dankshellConfigWatcher.loaded)
|
|
return "/etc/pam.d";
|
|
if (userPamWatcher.loaded)
|
|
return root.userPamDir;
|
|
return Quickshell.shellDir + "/assets/pam";
|
|
}
|
|
|
|
onMessageChanged: {
|
|
// collected by position, not text, so it works in any locale
|
|
if (message.length > 0 && !responseRequired)
|
|
root.attemptInfoMessages = root.attemptInfoMessages.concat([message]);
|
|
}
|
|
|
|
onResponseRequiredChanged: {
|
|
if (!responseRequired)
|
|
return;
|
|
|
|
const notice = root.attemptInfoMessages.filter(m => m !== message);
|
|
if (notice.length > 0) {
|
|
root.lockMessage = notice.join("\n");
|
|
root.lockoutAnnouncedThisAttempt = true;
|
|
}
|
|
root.attemptInfoMessages = [];
|
|
|
|
respond(root.buffer);
|
|
}
|
|
|
|
onCompleted: res => {
|
|
// requisite preauth can lock without ever prompting; surface it here too
|
|
if (!root.lockoutAnnouncedThisAttempt) {
|
|
if (root.attemptInfoMessages.length > 0) {
|
|
root.lockMessage = root.attemptInfoMessages.join("\n");
|
|
root.lockoutAnnouncedThisAttempt = true;
|
|
} else {
|
|
root.lockMessage = "";
|
|
}
|
|
root.attemptInfoMessages = [];
|
|
}
|
|
|
|
if (res === PamResult.Success) {
|
|
if (!root.unlockInProgress) {
|
|
fprint.stop();
|
|
root.proceedAfterPrimaryAuth();
|
|
}
|
|
return;
|
|
}
|
|
|
|
unlockRequestTimeout.running = false;
|
|
root.unlockInProgress = false;
|
|
root.u2fPending = false;
|
|
root.u2fPendingMode = "";
|
|
root.u2fState = "";
|
|
u2fPendingTimeout.running = false;
|
|
u2f.abort();
|
|
|
|
if (res === PamResult.Error)
|
|
root.state = "error";
|
|
else if (res === PamResult.MaxTries)
|
|
root.state = "max";
|
|
else if (res === PamResult.Failed)
|
|
root.state = "fail";
|
|
|
|
root.flashMsg();
|
|
stateReset.restart();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: passwd
|
|
|
|
function onActiveChanged() {
|
|
if (passwd.active) {
|
|
root.attemptInfoMessages = [];
|
|
root.lockoutAnnouncedThisAttempt = false;
|
|
passwdActiveTimeout.restart();
|
|
} else {
|
|
passwdActiveTimeout.running = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
PamContext {
|
|
id: fprint
|
|
|
|
readonly property bool available: SettingsData.lockFingerprintReady
|
|
property int tries: 0
|
|
property int errorTries: 0
|
|
property double attemptStartedAt: 0
|
|
property bool completedDuringStart: false
|
|
readonly property int maxErrorTries: 200
|
|
readonly property int daemonIdleExitMs: 30000
|
|
// sessionTimeoutMs mirrors `timeout=` in assets/pam/fprint
|
|
readonly property int sessionTimeoutMs: 90000
|
|
readonly property int verifyStartSlackMs: 10000
|
|
readonly property bool retrying: errorRetry.running
|
|
readonly property int retryInterval: errorRetry.interval
|
|
readonly property bool allowed: available && SettingsData.enableFprint && root.lockSecured && !root.fprintSuppressedByPrimaryPam && !root.unlockInProgress && !root.u2fPending && !SessionService.preparingForSleep && !IdleService.monitorsOff && tries < SettingsData.maxFprintTries
|
|
readonly property string status: {
|
|
if (!available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByPrimaryPam)
|
|
return "disabled";
|
|
if (tries >= SettingsData.maxFprintTries)
|
|
return "max";
|
|
if (!allowed)
|
|
return "paused";
|
|
if (active)
|
|
return "active";
|
|
if (retrying)
|
|
return "retrying";
|
|
return errorTries >= maxErrorTries ? "stopped" : "idle";
|
|
}
|
|
|
|
function stop(): void {
|
|
errorRetry.stop();
|
|
attemptStartedAt = 0;
|
|
abort();
|
|
}
|
|
|
|
// pam_fprintd reports an expired timeout as PAM_AUTHINFO_UNAVAIL, the same
|
|
// result a device fault gives, and its "Verification timed out" message is
|
|
// translated, so age is the signal we can rely on. Its timeout runs from the
|
|
// verify rather than from the claim, which is why the age is measured from
|
|
// the message announcing the verify: a slow claim then cannot make a fault
|
|
// look old enough, and a session that never reached a verify never qualifies.
|
|
// That message lands after pam_fprintd has already armed its deadline, so the
|
|
// age read here runs short of the real timeout and the slack makes it up. The
|
|
// bias is deliberate: a timeout misread as a fault parks the reader behind the
|
|
// backoff, while a fault misread as a timeout costs one extra retry.
|
|
function attemptSettled(): bool {
|
|
return attemptStartedAt > 0 && Date.now() - attemptStartedAt >= sessionTimeoutMs - verifyStartSlackMs;
|
|
}
|
|
|
|
function checkAvail(): void {
|
|
if (!allowed) {
|
|
stop();
|
|
return;
|
|
}
|
|
if (!active && !retrying)
|
|
startAttempt();
|
|
}
|
|
|
|
function startAttempt(): void {
|
|
if (!allowed || errorTries >= maxErrorTries) {
|
|
stop();
|
|
return;
|
|
}
|
|
if (active)
|
|
return;
|
|
errorRetry.stop();
|
|
if (root.fprintState === "error")
|
|
root.fprintState = "";
|
|
attemptStartedAt = 0;
|
|
completedDuringStart = false;
|
|
if (start())
|
|
return;
|
|
if (!completedDuringStart)
|
|
scheduleErrorRetry();
|
|
}
|
|
|
|
function scheduleErrorRetry(): void {
|
|
stop();
|
|
if (!allowed)
|
|
return;
|
|
errorTries++;
|
|
root.fprintState = "error";
|
|
if (errorTries === 1)
|
|
root.flashMsg();
|
|
fprintStateReset.restart();
|
|
if (errorTries < maxErrorTries)
|
|
errorRetry.restart();
|
|
}
|
|
|
|
function recover(): void {
|
|
if (!allowed)
|
|
return;
|
|
errorTries = 0;
|
|
if (!active)
|
|
startAttempt();
|
|
}
|
|
|
|
onAllowedChanged: {
|
|
if (!allowed)
|
|
stop();
|
|
else
|
|
Qt.callLater(checkAvail);
|
|
}
|
|
|
|
config: "fprint"
|
|
configDirectory: Quickshell.shellDir + "/assets/pam"
|
|
|
|
onPamMessage: {
|
|
if (attemptStartedAt === 0)
|
|
attemptStartedAt = Date.now();
|
|
}
|
|
|
|
onCompleted: res => {
|
|
completedDuringStart = true;
|
|
if (!allowed)
|
|
return;
|
|
|
|
switch (res) {
|
|
case PamResult.Success:
|
|
stop();
|
|
passwd.abort();
|
|
root.proceedAfterPrimaryAuth();
|
|
return;
|
|
case PamResult.Error:
|
|
if (attemptSettled()) {
|
|
stop();
|
|
errorTries = 0;
|
|
startAttempt();
|
|
return;
|
|
}
|
|
scheduleErrorRetry();
|
|
return;
|
|
case PamResult.Failed:
|
|
case PamResult.MaxTries:
|
|
stop();
|
|
tries++;
|
|
errorTries = 0;
|
|
root.fprintState = tries < SettingsData.maxFprintTries ? "fail" : "max";
|
|
root.flashMsg();
|
|
fprintStateReset.restart();
|
|
startAttempt();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
PamContext {
|
|
id: u2f
|
|
|
|
property bool available: SettingsData.lockU2fReady
|
|
|
|
function checkAvail(): void {
|
|
if (!available || !SettingsData.enableU2f || !root.lockSecured || root.u2fSuppressedByPrimaryPam) {
|
|
abort();
|
|
return;
|
|
}
|
|
|
|
if (SettingsData.u2fMode === "or")
|
|
abort();
|
|
}
|
|
|
|
function startForSecondFactor(): void {
|
|
if (!available || !SettingsData.enableU2f || root.u2fSuppressedByPrimaryPam) {
|
|
root.completeUnlock();
|
|
return;
|
|
}
|
|
abort();
|
|
root.u2fPending = true;
|
|
root.u2fPendingMode = "and";
|
|
root.u2fState = "";
|
|
u2fPendingTimeout.restart();
|
|
start();
|
|
}
|
|
|
|
function startForAlternativeAuth(): void {
|
|
if (!available || !SettingsData.enableU2f || root.u2fSuppressedByPrimaryPam || SettingsData.u2fMode !== "or" || root.unlockInProgress || passwd.active || active)
|
|
return;
|
|
abort();
|
|
root.u2fPending = true;
|
|
root.u2fPendingMode = "or";
|
|
root.u2fState = "";
|
|
u2fPendingTimeout.restart();
|
|
start();
|
|
}
|
|
|
|
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"))
|
|
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.u2fPendingMode === "or") {
|
|
root.u2fPending = false;
|
|
root.u2fPendingMode = "";
|
|
root.u2fState = root.u2fState === "waiting" ? "" : "insert";
|
|
u2fPendingTimeout.running = false;
|
|
fprint.checkAvail();
|
|
return;
|
|
}
|
|
|
|
if (root.u2fState === "waiting") {
|
|
// AND mode: device was found but auth failed → back to password
|
|
root.u2fPending = false;
|
|
root.u2fPendingMode = "";
|
|
root.u2fState = "";
|
|
fprint.checkAvail();
|
|
} else {
|
|
// AND mode: no device found → keep pending, show "Insert...", retry
|
|
root.u2fState = "insert";
|
|
u2fErrorRetry.restart();
|
|
}
|
|
} else {
|
|
root.u2fState = "insert";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: errorRetry
|
|
|
|
// A device wedged by a suspend mid-verify only comes back when fprintd
|
|
// restarts, and fprintd only exits after its idle timeout with no client
|
|
// attached, so the second retry outlasts that instead of climbing to it.
|
|
interval: {
|
|
if (fprint.errorTries <= 1)
|
|
return 1500;
|
|
if (fprint.errorTries === 2)
|
|
return fprint.daemonIdleExitMs + 5000;
|
|
return 60000;
|
|
}
|
|
onTriggered: fprint.startAttempt()
|
|
}
|
|
|
|
readonly property bool awaitingActivityRetry: fprint.errorTries > 0 && !fprint.retrying && !fprint.active
|
|
|
|
function retryFprintOnActivity(): void {
|
|
if (root.awaitingActivityRetry)
|
|
fprint.recover();
|
|
}
|
|
|
|
Connections {
|
|
target: SessionService
|
|
|
|
function onSessionResumed() {
|
|
fprint.recover();
|
|
}
|
|
|
|
function onLidOpened() {
|
|
fprint.recover();
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: u2fErrorRetry
|
|
|
|
interval: 800
|
|
onTriggered: u2f.start()
|
|
}
|
|
|
|
Timer {
|
|
id: u2fPendingTimeout
|
|
|
|
interval: 30000
|
|
onTriggered: root.cancelU2fPending()
|
|
}
|
|
|
|
Timer {
|
|
id: passwdActiveTimeout
|
|
|
|
interval: 15000
|
|
onTriggered: {
|
|
if (passwd.active)
|
|
root.recoverFromAuthStall("error");
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: unlockRequestTimeout
|
|
|
|
interval: 8000
|
|
onTriggered: {
|
|
if (root.unlockInProgress)
|
|
root.recoverFromAuthStall("error");
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: stateReset
|
|
|
|
interval: 4000
|
|
onTriggered: {
|
|
if (root.state !== "max")
|
|
root.state = "";
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: fprintStateReset
|
|
|
|
interval: 4000
|
|
onTriggered: {
|
|
if (root.fprintState !== "max")
|
|
root.fprintState = "";
|
|
}
|
|
}
|
|
|
|
onLockSecuredChanged: {
|
|
if (!lockSecured) {
|
|
root.resetAuthFlows();
|
|
return;
|
|
}
|
|
root.state = "";
|
|
root.fprintState = "";
|
|
root.u2fState = "";
|
|
root.u2fPending = false;
|
|
root.u2fPendingMode = "";
|
|
root.lockMessage = "";
|
|
root.attemptInfoMessages = [];
|
|
root.lockoutAnnouncedThisAttempt = false;
|
|
root.resetAuthFlows();
|
|
fprint.tries = 0;
|
|
fprint.errorTries = 0;
|
|
if (!SettingsData.lockPamExternallyManaged && !dankshellConfigWatcher.loaded && !userPamWatcher.loaded)
|
|
ensureUserPamConfig();
|
|
// FileView cannot watch a path that does not exist yet; re-read so a
|
|
// dedicated service created after startup is used on the next lock.
|
|
u2fConfigWatcher.reload();
|
|
fprint.checkAvail();
|
|
u2f.checkAvail();
|
|
}
|
|
|
|
Connections {
|
|
target: SettingsData
|
|
|
|
function onEnableFprintChanged(): void {
|
|
if (SettingsData.enableFprint)
|
|
fprint.recover();
|
|
else
|
|
fprint.checkAvail();
|
|
}
|
|
|
|
function onLockFingerprintReadyChanged(): void {
|
|
if (SettingsData.lockFingerprintReady)
|
|
fprint.recover();
|
|
else
|
|
fprint.checkAvail();
|
|
}
|
|
|
|
function onEnableU2fChanged(): void {
|
|
u2f.checkAvail();
|
|
}
|
|
|
|
function onLockU2fReadyChanged(): void {
|
|
u2f.checkAvail();
|
|
}
|
|
|
|
function onLockPamPathChanged(): void {
|
|
fprint.checkAvail();
|
|
u2f.checkAvail();
|
|
}
|
|
|
|
function onLockPamInlineFprintChanged(): void {
|
|
fprint.checkAvail();
|
|
}
|
|
|
|
function onLockPamInlineU2fChanged(): void {
|
|
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();
|
|
u2fErrorRetry.running = false;
|
|
u2fPendingTimeout.running = false;
|
|
unlockRequestTimeout.running = false;
|
|
root.u2fPending = false;
|
|
root.u2fPendingMode = "";
|
|
root.u2fState = "";
|
|
u2f.checkAvail();
|
|
}
|
|
}
|
|
}
|
|
}
|