diff --git a/CHANGELOG.md b/CHANGELOG.md index e1919455..de149acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # [unreleased] +## Breaking Changes + +- ui: `CreateSession` takes the list of available transports (`as`) instead of placeholder devices (`a(ss)`). + ## Improvements - daemon: Fix UI service failing to launch when started manually instead of via D-Bus activation. @@ -8,6 +12,9 @@ - ui: Add Bulgarian translation (Thank you, @salif!) - webext: Try to read app ID from associated desktop entry (Thank you, @michaelbeaumont!) - ui: Basic recovery from failed attempts to use devices (e.g. flaky bluetooth) +- ui: Display restart reason in case of premature failure +- daemon: Stop retrying a transport after 5 consecutive failures before user interaction; other transports keep running. +- daemon/ui: Add `TransportUnavailable` restart reason, sent when a transport stops after repeated failures; the UI then stops offering it. # 0.3.1 [2026-09-05] diff --git a/credentialsd-common/src/model.rs b/credentialsd-common/src/model.rs index 1c741842..5428b96f 100644 --- a/credentialsd-common/src/model.rs +++ b/credentialsd-common/src/model.rs @@ -8,10 +8,53 @@ pub const BACKGROUND_EVENT_ERROR_INTERNAL: u32 = 0x80000001; pub const BACKGROUND_EVENT_ERROR_TIMED_OUT: u32 = 0x80000002; pub const BACKGROUND_EVENT_ERROR_CANCELLED: u32 = 0x80000003; pub const BACKGROUND_EVENT_ERROR_AUTHENTICATOR: u32 = 0x80000004; -pub const BACKGROUND_EVENT_ERROR_NO_CREDENTIALS: u32 = 0x80000005; pub const BACKGROUND_EVENT_ERROR_CREDENTIAL_EXCLUDED: u32 = 0x80000006; -pub const BACKGROUND_EVENT_ERROR_PIN_ATTEMPTS_EXHAUSTED: u32 = 0x80000007; -pub const BACKGROUND_EVENT_ERROR_PIN_NOT_SET: u32 = 0x80000008; + +/// Machine-readable reason for a transport restart. Shared across all transports. +/// Human-readable strings are built by the UI (gettext). +/// +/// The `#[repr(u8)]` layout doubles as the D-Bus wire format via `From for u8` +/// and `TryFrom for Self`. Discriminants must remain stable across releases. +/// Discriminant 0 is deliberately unused so that an all-zeroed message is recognisable +/// as invalid by `TryFrom`. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TransportRestartReason { + /// The ceremony was interrupted before completing — transport error, + /// user cancelled on their phone/device, or an unrecoverable authenticator + /// error. The user should follow the new prompts to retry. + Interrupted = 1, + /// No matching credentials were found on the device. The user should try + /// a different authenticator. + NoCredentials = 2, + /// Too many incorrect PIN attempts on this device — it is now locked. The + /// user should remove and reinsert the device, or use a different authenticator. + PinAttemptsExhausted = 3, + /// The transport failed repeatedly before the user interacted with it and + /// stopped for the rest of this request; it will not restart. The UI should + /// stop offering it. The other transports keep running. + TransportUnavailable = 4, +} + +impl From for u8 { + fn from(value: TransportRestartReason) -> Self { + value as u8 + } +} + +impl TryFrom for TransportRestartReason { + type Error = u8; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(Self::Interrupted), + 2 => Ok(Self::NoCredentials), + 3 => Ok(Self::PinAttemptsExhausted), + 4 => Ok(Self::TransportUnavailable), + other => Err(other), + } + } +} /// Credential service events intended to inform the UI. #[derive(Debug, PartialEq)] @@ -37,16 +80,22 @@ pub enum BackgroundEvent { HybridConnected, /// The hybrid ceremony was interrupted by a non-terminating error and a new /// QR code is about to be issued. The UI should navigate back to the start - /// page so the new QR becomes visible. - HybridRestarting, + /// page so the new QR becomes visible. With `TransportUnavailable`, hybrid + /// stopped instead and the UI should stop offering it. + HybridRestarting { + reason: TransportRestartReason, + }, NfcIdle, NfcWaiting, NfcConnected, /// The NFC ceremony was interrupted by a non-terminating error and the /// transport is polling for a new device tap. The UI should navigate back - /// to the start page. - NfcRestarting, + /// to the start page. With `TransportUnavailable`, NFC stopped instead and + /// the UI should stop offering it. + NfcRestarting { + reason: TransportRestartReason, + }, UsbIdle, UsbWaiting, @@ -54,17 +103,17 @@ pub enum BackgroundEvent { UsbConnected, /// The USB ceremony was interrupted by a non-terminating error and the /// transport is polling for a device. The UI should navigate back to the - /// start page. - UsbRestarting, + /// start page. With `TransportUnavailable`, USB stopped instead and the UI + /// should stop offering it. + UsbRestarting { + reason: TransportRestartReason, + }, ErrorInternal, ErrorTimedOut, ErrorCancelled, ErrorAuthenticator, - ErrorNoCredentials, ErrorCredentialExcluded, - ErrorPinAttemptsExhausted, - ErrorPinNotSet, } /// Emitted when a client enters a PIN for the selected authenticator. diff --git a/credentialsd-ui/data/resources/ui/window.blp b/credentialsd-ui/data/resources/ui/window.blp index 70f62def..67fdcdfa 100644 --- a/credentialsd-ui/data/resources/ui/window.blp +++ b/credentialsd-ui/data/resources/ui/window.blp @@ -127,6 +127,7 @@ template $CredentialsUiWindow: ApplicationWindow { Box { orientation: vertical; halign: start; + visible: bind template.view-model as <$CredentialManagerViewModel>.security_key_transport_available; styles [ "cred-source-header", @@ -154,6 +155,17 @@ template $CredentialsUiWindow: ApplicationWindow { wrap: true; } } + + Label { + label: bind template.view-model as <$CredentialManagerViewModel>.restart_message; + visible: bind template.view-model as <$CredentialManagerViewModel>.restart_message_visible; + margin-top: 16; + margin-start: 24; + margin-end: 24; + margin-bottom: 8; + wrap: true; + styles ["error"] + } }; } diff --git a/credentialsd-ui/po/bg_BG.po b/credentialsd-ui/po/bg_BG.po index 660aa024..90f761ca 100644 --- a/credentialsd-ui/po/bg_BG.po +++ b/credentialsd-ui/po/bg_BG.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: credentialsd-ui\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-06-18 07:18-0500\n" +"POT-Creation-Date: 2026-10-08 15:34+0200\n" "PO-Revision-Date: 2026-09-20 07:18-0500\n" "Last-Translator: Salif Mehmed \n" "Language-Team: none\n" @@ -33,8 +33,7 @@ msgid "Registering a credential" msgstr "Регистриране на данни за удостоверяване" #. developer_name tag deprecated with Appstream 1.0 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:36 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:39 +#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:38 msgid "Isaiah Inuwa" msgstr "Isaiah Inuwa" @@ -46,68 +45,124 @@ msgstr "Изберете устройство" msgid "Use a mobile device" msgstr "Използвайте мобилно устройство" -#: credentialsd-ui/data/resources/ui/window.blp:145 +#: credentialsd-ui/data/resources/ui/window.blp:146 msgid "Use your security key" msgstr "Използвайте своя ключ за сигурност" -#: credentialsd-ui/data/resources/ui/window.blp:162 +#: credentialsd-ui/data/resources/ui/window.blp:174 msgid "Connect a security key" msgstr "Свържете ключ за сигурност" -#: credentialsd-ui/data/resources/ui/window.blp:197 +#: credentialsd-ui/data/resources/ui/window.blp:209 msgid "Enter your device PIN" msgstr "Въведете ПИН кода на вашето устройство" -#: credentialsd-ui/data/resources/ui/window.blp:205 +#: credentialsd-ui/data/resources/ui/window.blp:218 msgid "Scan the QR code to connect your device" msgstr "Сканирайте QR кода, за да свържете устройството си" -#: credentialsd-ui/data/resources/ui/window.blp:247 -#: credentialsd-ui/data/resources/ui/window.blp:253 +#: credentialsd-ui/data/resources/ui/window.blp:260 +#: credentialsd-ui/data/resources/ui/window.blp:266 msgid "Choose credential" msgstr "Изберете данни за удостоверяване" -#: credentialsd-ui/data/resources/ui/window.blp:266 +#: credentialsd-ui/data/resources/ui/window.blp:279 +msgid "Set a PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:302 +msgid "Please choose a new PIN for your device." +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:315 +msgid "New PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:322 +msgid "Confirm PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:334 +#: credentialsd-ui/data/resources/ui/window.blp:405 +msgid "Close" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:339 +msgid "Continue" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:354 msgid "Complete" msgstr "Завършване" -#: credentialsd-ui/data/resources/ui/window.blp:288 +#: credentialsd-ui/data/resources/ui/window.blp:376 msgid "Done!" msgstr "Готово!" -#: credentialsd-ui/data/resources/ui/window.blp:299 +#: credentialsd-ui/data/resources/ui/window.blp:387 msgid "Something went wrong." msgstr "Възникна грешка." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:159 +#: credentialsd-ui/data/resources/ui/window.blp:410 +#, fuzzy +msgid "Set PIN on device" +msgstr "Използвайте мобилно устройство" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:181 msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "Въведете вашия ПИН. Остава 1 опит." msgstr[1] "Въведете вашия ПИН. Остават %d опита." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:165 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:187 msgid "Enter your PIN." msgstr "Въведете вашия ПИН." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:174 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:196 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "Докоснете устройството си отново. Остава 1 опит." msgstr[1] "Докоснете устройството си отново. Остават %d опита." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:180 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:202 msgid "Touch your device." msgstr "Докоснете устройството си." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:185 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:207 msgid "Touch your device" msgstr "Докоснете устройството си" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:188 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:214 +msgid "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." +msgstr "" +"Този сървър изисква устройството ви да има допълнителна защита, като " +"например ПИН, какъвто не е зададен. Моля, задайте ПИН за това устройство и " +"опитайте отново." + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:218 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:221 +msgid "" +"The entered PIN violates the PIN-policy of this device (PIN too long). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:224 +msgid "A PIN change is required by your device to continue." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:231 msgid "Scan the QR code with your device to begin authentication." -msgstr "Сканирайте QR кода с устройството си, за да започнете удостоверяването." +msgstr "" +"Сканирайте QR кода с устройството си, за да започнете удостоверяването." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:201 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:244 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." @@ -115,19 +170,19 @@ msgstr "" "Свързване с вашето устройство. Уверете се, че двете устройства са близо едно " "до друго и имат включен Bluetooth." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:209 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:252 msgid "Device connected. Follow the instructions on your device" msgstr "Устройството е свързано. Следвайте инструкциите на него" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:298 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:358 msgid "Insert your security key." msgstr "Поставете вашия ключ за сигурност." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:317 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:377 msgid "Multiple devices found. Please select with which to proceed." msgstr "Открити са няколко устройства. Моля, изберете с кое да продължите." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:362 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:426 msgid "Credential Manager" msgstr "Управление на данни за удостоверяване" @@ -155,28 +210,28 @@ msgstr "Ключ за сигурност или карта (NFC)" msgid "A security key (USB)" msgstr "Ключ за сигурност (USB)" -#: credentialsd-ui/src/gui/view_model/mod.rs:64 +#: credentialsd-ui/src/gui/view_model/mod.rs:62 msgid "unknown application" msgstr "неизвестно приложение" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:80 +#: credentialsd-ui/src/gui/view_model/mod.rs:76 msgid "Create a passkey for %s1" msgstr "Създаване на ключ за достъп за %s1" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:84 +#: credentialsd-ui/src/gui/view_model/mod.rs:80 msgid "Use a passkey for %s1" msgstr "Използване на ключ за достъп за %s1" #. TRANSLATORS: %s1 is the "relying party" (e.g.: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:96 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:91 +#, fuzzy msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " -"credential to register at \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to create a credential to register at " +"\"%s1\". Only proceed if you trust this process." msgstr "" "„%s2“ (идентификатор на процес: %i1, изпълним файл: %s3) иска да " "създаде данни за удостоверяване за регистрация в „%s1“. Продължете само ако " @@ -184,61 +239,46 @@ msgstr "" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:103 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:97 +#, fuzzy msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " -"to sign in to \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to use a credential to sign in to \"%s1\". " +"Only proceed if you trust this process." msgstr "" "„%s2“ (идентификатор на процес: %i1, изпълним файл: %s3) иска да " "използва данни за удостоверяване за влизане в „%s1“. Продължете само ако " "имате доверие на този процес." #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:115 +#: credentialsd-ui/src/gui/view_model/mod.rs:108 msgid "" "Scan the QR code using the camera on the device that has the passkey for %s1" msgstr "" -"Сканирайте QR кода с камерата на устройството, в което е ключът за достъп за %s1" +"Сканирайте QR кода с камерата на устройството, в което е ключът за достъп за " +"%s1" #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:118 +#: credentialsd-ui/src/gui/view_model/mod.rs:111 msgid "Insert and activate your security key to use it for %s1" msgstr "" "Поставете и активирайте вашия ключ за сигурност, за да го използвате за %s1" -#: credentialsd-ui/src/gui/view_model/mod.rs:181 -msgid "Failed to select credential from device." -msgstr "Неуспешен избор на данни за удостоверяване от устройството." - -#: credentialsd-ui/src/gui/view_model/mod.rs:237 -msgid "No matching credentials found on this authenticator." -msgstr "В този удостоверител не са открити съвпадащи данни за удостоверяване." - -#: credentialsd-ui/src/gui/view_model/mod.rs:245 +#: credentialsd-ui/src/gui/view_model/mod.rs:138 msgid "" -"No more PIN attempts allowed. Try removing your device and plugging it back " -"in." +"None of the available methods are working right now. Please try again later." msgstr "" -"Няма повече разрешени опити за ПИН. Опитайте да извадите устройството и да го " -"включите отново." -#: credentialsd-ui/src/gui/view_model/mod.rs:254 -msgid "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." -msgstr "" -"Този сървър изисква устройството ви да има допълнителна защита, като например " -"ПИН, какъвто не е зададен. Моля, задайте ПИН за това устройство и опитайте " -"отново." +#: credentialsd-ui/src/gui/view_model/mod.rs:206 +msgid "Failed to select credential from device." +msgstr "Неуспешен избор на данни за удостоверяване от устройството." -#: credentialsd-ui/src/gui/view_model/mod.rs:262 +#: credentialsd-ui/src/gui/view_model/mod.rs:270 msgid "The credential request timed out. Please try again." msgstr "" "Времето за заявката за данни за удостоверяване изтече. Моля, опитайте отново." -#: credentialsd-ui/src/gui/view_model/mod.rs:272 +#: credentialsd-ui/src/gui/view_model/mod.rs:280 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -246,6 +286,32 @@ msgstr "" "Възникна грешка при извличането на данни за удостоверяване. Моля, опитайте " "по-късно или използвайте друг удостоверител." -#: credentialsd-ui/src/gui/view_model/mod.rs:281 +#: credentialsd-ui/src/gui/view_model/mod.rs:289 msgid "This credential is already registered on this authenticator." -msgstr "Тези данни за удостоверяване вече са регистрирани в този удостоверител." +msgstr "" +"Тези данни за удостоверяване вече са регистрирани в този удостоверител." + +#: credentialsd-ui/src/gui/view_model/mod.rs:406 +msgid "" +"The previous attempt was interrupted. Please follow the new prompts to try " +"again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/mod.rs:409 +#, fuzzy +msgid "" +"No matching credentials on this authenticator. Please try a different one." +msgstr "В този удостоверител не са открити съвпадащи данни за удостоверяване." + +#: credentialsd-ui/src/gui/view_model/mod.rs:412 +#, fuzzy +msgid "" +"No more PIN attempts allowed. Remove and reinsert your device, or use a " +"different authenticator." +msgstr "" +"Няма повече разрешени опити за ПИН. Опитайте да извадите устройството и да " +"го включите отново." + +#: credentialsd-ui/src/gui/view_model/mod.rs:415 +msgid "This method isn't working right now. Please choose another one." +msgstr "" diff --git a/credentialsd-ui/po/credentialsd-ui.pot b/credentialsd-ui/po/credentialsd-ui.pot index 5ed55b70..e1e966e7 100644 --- a/credentialsd-ui/po/credentialsd-ui.pot +++ b/credentialsd-ui/po/credentialsd-ui.pot @@ -9,7 +9,7 @@ msgstr "" "Project-Id-Version: credentialsd-ui\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-06-18 07:18-0500\n" +"POT-Creation-Date: 2026-10-08 15:34+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -37,8 +37,7 @@ msgid "Registering a credential" msgstr "" #. developer_name tag deprecated with Appstream 1.0 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:36 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:39 +#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:38 msgid "Isaiah Inuwa" msgstr "" @@ -50,86 +49,137 @@ msgstr "" msgid "Use a mobile device" msgstr "" -#: credentialsd-ui/data/resources/ui/window.blp:145 +#: credentialsd-ui/data/resources/ui/window.blp:146 msgid "Use your security key" msgstr "" -#: credentialsd-ui/data/resources/ui/window.blp:162 +#: credentialsd-ui/data/resources/ui/window.blp:174 msgid "Connect a security key" msgstr "" -#: credentialsd-ui/data/resources/ui/window.blp:197 +#: credentialsd-ui/data/resources/ui/window.blp:209 msgid "Enter your device PIN" msgstr "" -#: credentialsd-ui/data/resources/ui/window.blp:205 +#: credentialsd-ui/data/resources/ui/window.blp:218 msgid "Scan the QR code to connect your device" msgstr "" -#: credentialsd-ui/data/resources/ui/window.blp:247 -#: credentialsd-ui/data/resources/ui/window.blp:253 +#: credentialsd-ui/data/resources/ui/window.blp:260 +#: credentialsd-ui/data/resources/ui/window.blp:266 msgid "Choose credential" msgstr "" -#: credentialsd-ui/data/resources/ui/window.blp:266 +#: credentialsd-ui/data/resources/ui/window.blp:279 +msgid "Set a PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:302 +msgid "Please choose a new PIN for your device." +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:315 +msgid "New PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:322 +msgid "Confirm PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:334 +#: credentialsd-ui/data/resources/ui/window.blp:405 +msgid "Close" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:339 +msgid "Continue" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:354 msgid "Complete" msgstr "" -#: credentialsd-ui/data/resources/ui/window.blp:288 +#: credentialsd-ui/data/resources/ui/window.blp:376 msgid "Done!" msgstr "" -#: credentialsd-ui/data/resources/ui/window.blp:299 +#: credentialsd-ui/data/resources/ui/window.blp:387 msgid "Something went wrong." msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:159 +#: credentialsd-ui/data/resources/ui/window.blp:410 +msgid "Set PIN on device" +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:181 msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "" msgstr[1] "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:165 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:187 msgid "Enter your PIN." msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:174 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:196 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "" msgstr[1] "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:180 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:202 msgid "Touch your device." msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:185 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:207 msgid "Touch your device" msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:188 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:214 +msgid "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:218 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:221 +msgid "" +"The entered PIN violates the PIN-policy of this device (PIN too long). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:224 +msgid "A PIN change is required by your device to continue." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:231 msgid "Scan the QR code with your device to begin authentication." msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:201 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:244 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:209 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:252 msgid "Device connected. Follow the instructions on your device" msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:298 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:358 msgid "Insert your security key." msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:317 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:377 msgid "Multiple devices found. Please select with which to proceed." msgstr "" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:362 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:426 msgid "Credential Manager" msgstr "" @@ -157,81 +207,89 @@ msgstr "" msgid "A security key (USB)" msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:64 +#: credentialsd-ui/src/gui/view_model/mod.rs:62 msgid "unknown application" msgstr "" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:80 +#: credentialsd-ui/src/gui/view_model/mod.rs:76 msgid "Create a passkey for %s1" msgstr "" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:84 +#: credentialsd-ui/src/gui/view_model/mod.rs:80 msgid "Use a passkey for %s1" msgstr "" #. TRANSLATORS: %s1 is the "relying party" (e.g.: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:96 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:91 msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " -"credential to register at \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to create a credential to register at " +"\"%s1\". Only proceed if you trust this process." msgstr "" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:103 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:97 msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " -"to sign in to \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to use a credential to sign in to \"%s1\". " +"Only proceed if you trust this process." msgstr "" #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:115 +#: credentialsd-ui/src/gui/view_model/mod.rs:108 msgid "" "Scan the QR code using the camera on the device that has the passkey for %s1" msgstr "" #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:118 +#: credentialsd-ui/src/gui/view_model/mod.rs:111 msgid "Insert and activate your security key to use it for %s1" msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:181 +#: credentialsd-ui/src/gui/view_model/mod.rs:138 +msgid "" +"None of the available methods are working right now. Please try again later." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/mod.rs:206 msgid "Failed to select credential from device." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:237 -msgid "No matching credentials found on this authenticator." +#: credentialsd-ui/src/gui/view_model/mod.rs:270 +msgid "The credential request timed out. Please try again." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:245 +#: credentialsd-ui/src/gui/view_model/mod.rs:280 msgid "" -"No more PIN attempts allowed. Try removing your device and plugging it back " -"in." +"Something went wrong while retrieving a credential. Please try again later " +"or use a different authenticator." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/mod.rs:289 +msgid "This credential is already registered on this authenticator." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:254 +#: credentialsd-ui/src/gui/view_model/mod.rs:406 msgid "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." +"The previous attempt was interrupted. Please follow the new prompts to try " +"again." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:262 -msgid "The credential request timed out. Please try again." +#: credentialsd-ui/src/gui/view_model/mod.rs:409 +msgid "" +"No matching credentials on this authenticator. Please try a different one." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:272 +#: credentialsd-ui/src/gui/view_model/mod.rs:412 msgid "" -"Something went wrong while retrieving a credential. Please try again later " -"or use a different authenticator." +"No more PIN attempts allowed. Remove and reinsert your device, or use a " +"different authenticator." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:281 -msgid "This credential is already registered on this authenticator." +#: credentialsd-ui/src/gui/view_model/mod.rs:415 +msgid "This method isn't working right now. Please choose another one." msgstr "" diff --git a/credentialsd-ui/po/de_DE.po b/credentialsd-ui/po/de_DE.po index 7243f73d..91d4e708 100644 --- a/credentialsd-ui/po/de_DE.po +++ b/credentialsd-ui/po/de_DE.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-06-18 07:18-0500\n" +"POT-Creation-Date: 2026-10-08 15:34+0200\n" "PO-Revision-Date: 2025-10-10 14:45+0200\n" "Last-Translator: Martin Sirringhaus \n" "Language: de_DE\n" @@ -32,8 +32,7 @@ msgid "Registering a credential" msgstr "Zugangsdaten registrieren" #. developer_name tag deprecated with Appstream 1.0 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:36 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:39 +#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:38 msgid "Isaiah Inuwa" msgstr "" @@ -46,71 +45,127 @@ msgstr "Gerät auswählen" msgid "Use a mobile device" msgstr "Ein mobiles Gerät" -#: credentialsd-ui/data/resources/ui/window.blp:145 +#: credentialsd-ui/data/resources/ui/window.blp:146 #, fuzzy msgid "Use your security key" msgstr "Stecken Sie Ihren Security-Token ein." -#: credentialsd-ui/data/resources/ui/window.blp:162 +#: credentialsd-ui/data/resources/ui/window.blp:174 msgid "Connect a security key" msgstr "Stecken Sie Ihren Security-Token ein." -#: credentialsd-ui/data/resources/ui/window.blp:197 +#: credentialsd-ui/data/resources/ui/window.blp:209 #, fuzzy msgid "Enter your device PIN" msgstr "Geben Sie Ihren PIN ein." -#: credentialsd-ui/data/resources/ui/window.blp:205 +#: credentialsd-ui/data/resources/ui/window.blp:218 msgid "Scan the QR code to connect your device" msgstr "Scannen Sie den QR-Code, um Ihr Gerät zu verbinden" -#: credentialsd-ui/data/resources/ui/window.blp:247 -#: credentialsd-ui/data/resources/ui/window.blp:253 +#: credentialsd-ui/data/resources/ui/window.blp:260 +#: credentialsd-ui/data/resources/ui/window.blp:266 msgid "Choose credential" msgstr "Wählen Sie Zugangsdaten aus" -#: credentialsd-ui/data/resources/ui/window.blp:266 + +#: credentialsd-ui/data/resources/ui/window.blp:279 +msgid "Set a PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:302 +msgid "Please choose a new PIN for your device." +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:315 +msgid "New PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:322 +msgid "Confirm PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:334 +#: credentialsd-ui/data/resources/ui/window.blp:405 +msgid "Close" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:339 +msgid "Continue" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:354 msgid "Complete" msgstr "Abgeschlossen" -#: credentialsd-ui/data/resources/ui/window.blp:288 +#: credentialsd-ui/data/resources/ui/window.blp:376 msgid "Done!" msgstr "Fertig!" -#: credentialsd-ui/data/resources/ui/window.blp:299 +#: credentialsd-ui/data/resources/ui/window.blp:387 msgid "Something went wrong." msgstr "Etwas ist schief gegangen." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:159 +#: credentialsd-ui/data/resources/ui/window.blp:410 +#, fuzzy +msgid "Set PIN on device" +msgstr "Ein mobiles Gerät" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:181 #, fuzzy msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "Geben Sie Ihren PIN ein. Sie haben nur noch einen Versuch." msgstr[1] "Geben Sie Ihren PIN ein. Sie haben noch %d Versuche." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:165 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:187 msgid "Enter your PIN." msgstr "Geben Sie Ihren PIN ein." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:174 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:196 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "Berühren Sie Ihr Gerät. Sie haben nur noch einen Versuch." msgstr[1] "Berühren Sie nochmal Ihr Gerät. Sie haben nur noch %d Versuche." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:180 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:202 msgid "Touch your device." msgstr "Berühren Sie Ihr Gerät." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:185 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:207 msgid "Touch your device" msgstr "Berühren Sie Ihr Gerät." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:188 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:214 +msgid "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." +msgstr "" +"Für diesen Server benötigt ihr Gerät eine zusätzliche Absicherung, z.B. " +"einen PIN. Bitte setzen Sie einen PIN für ihr Gerät und versuchen Sie es " +"erneut." + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:218 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:221 +msgid "" +"The entered PIN violates the PIN-policy of this device (PIN too long). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:224 +msgid "A PIN change is required by your device to continue." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:231 msgid "Scan the QR code with your device to begin authentication." msgstr "" "Scannen Sie den QR code mit ihrem Gerät um die Authentifizierung zu beginnen." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:201 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:244 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." @@ -118,19 +173,19 @@ msgstr "" "Verbindung zu Ihrem Gerät wird aufgebaut. Stellen Sie sicher, dass beide " "Geräte nah beieinander sind und Bluetooth aktiviert haben." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:209 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:252 msgid "Device connected. Follow the instructions on your device" msgstr "Verbindung hergestellt. Folgen Sie den Anweisungen auf Ihrem Gerät." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:298 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:358 msgid "Insert your security key." msgstr "Stecken Sie Ihren Security-Token ein." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:317 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:377 msgid "Multiple devices found. Please select with which to proceed." msgstr "Mehrere Geräte gefunden. Bitte wählen Sie einen aus, um fortzufahren." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:362 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:426 msgid "Credential Manager" msgstr "Zugangsdatenmanager" @@ -158,28 +213,28 @@ msgstr "Ein NFC-Gerät" msgid "A security key (USB)" msgstr "Ein Security-Token" -#: credentialsd-ui/src/gui/view_model/mod.rs:64 +#: credentialsd-ui/src/gui/view_model/mod.rs:62 msgid "unknown application" msgstr "unbekannter Applikation" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:80 +#: credentialsd-ui/src/gui/view_model/mod.rs:76 msgid "Create a passkey for %s1" msgstr "Neuen Passkey für %s1 erstellen" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:84 +#: credentialsd-ui/src/gui/view_model/mod.rs:80 msgid "Use a passkey for %s1" msgstr "Passkey für %s1 abrufen" #. TRANSLATORS: %s1 is the "relying party" (e.g.: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:96 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:91 +#, fuzzy msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " -"credential to register at \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to create a credential to register at " +"\"%s1\". Only proceed if you trust this process." msgstr "" "\"%s2\" (Prozess-ID: %i1, ausführbare Datei: %s3) möchte neue " "Zugangsdaten erstellen, um Sie bei \"%s1\" zu registrieren. Fahren Sie nur " @@ -187,59 +242,45 @@ msgstr "" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:103 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:97 +#, fuzzy msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " -"to sign in to \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to use a credential to sign in to \"%s1\". " +"Only proceed if you trust this process." msgstr "" "\"%s2\" (Prozess-ID: %i1, ausführbare Datei: %s3) möchte Zugangsdaten " "abrufen, um Sie bei \"%s1\" anzumelden. Fahren Sie nur fort, wenn Sie diesem " "Prozess vertrauen." #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:115 +#: credentialsd-ui/src/gui/view_model/mod.rs:108 msgid "" "Scan the QR code using the camera on the device that has the passkey for %s1" msgstr "" #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:118 +#: credentialsd-ui/src/gui/view_model/mod.rs:111 #, fuzzy msgid "Insert and activate your security key to use it for %s1" msgstr "Stecken Sie Ihren Security-Token ein." -#: credentialsd-ui/src/gui/view_model/mod.rs:181 -msgid "Failed to select credential from device." -msgstr "Zugangsdaten vom Gerät konnten nicht ausgewählt werden." - -#: credentialsd-ui/src/gui/view_model/mod.rs:237 -msgid "No matching credentials found on this authenticator." -msgstr "Keine passenden Zugangsdaten auf diesem Gerät gefunden." - -#: credentialsd-ui/src/gui/view_model/mod.rs:245 +#: credentialsd-ui/src/gui/view_model/mod.rs:138 msgid "" -"No more PIN attempts allowed. Try removing your device and plugging it back " -"in." +"None of the available methods are working right now. Please try again later." msgstr "" -"Keine weiteren PIN-Eingaben erlaubt. Versuchen Sie ihr Gerät aus- und wieder " -"einzustecken." +"Keine der verfügbaren Methoden funktioniert gerade. Bitte versuchen Sie es " +"später noch einmal." -#: credentialsd-ui/src/gui/view_model/mod.rs:254 -msgid "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." -msgstr "" -"Für diesen Server benötigt ihr Gerät eine zusätzliche Absicherung, z.B. " -"einen PIN. Bitte setzen Sie einen PIN für ihr Gerät und versuchen Sie es " -"erneut." +#: credentialsd-ui/src/gui/view_model/mod.rs:206 +msgid "Failed to select credential from device." +msgstr "Zugangsdaten vom Gerät konnten nicht ausgewählt werden." -#: credentialsd-ui/src/gui/view_model/mod.rs:262 +#: credentialsd-ui/src/gui/view_model/mod.rs:270 msgid "The credential request timed out. Please try again." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:272 +#: credentialsd-ui/src/gui/view_model/mod.rs:280 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -247,10 +288,35 @@ msgstr "" "Beim Abrufen Ihrer Zugangsdaten ist ein Fehler aufgetreten. Versuchen Sie es " "später wieder, oder verwenden Sie einen anderen Security-Token." -#: credentialsd-ui/src/gui/view_model/mod.rs:281 +#: credentialsd-ui/src/gui/view_model/mod.rs:289 msgid "This credential is already registered on this authenticator." msgstr "Diese Zugangsdaten sind bereits auf diesem Gerät registriert." +#: credentialsd-ui/src/gui/view_model/mod.rs:406 +msgid "" +"The previous attempt was interrupted. Please follow the new prompts to try " +"again." +msgstr "Der Versuch wurde abgebrochen. Bitte versuchen Sie es erneut." + +#: credentialsd-ui/src/gui/view_model/mod.rs:409 +msgid "" +"No matching credentials on this authenticator. Please try a different one." +msgstr "" +"Keine passenden Zugangsdaten auf diesem Gerät. Bitte versuchen Sie es mit " +"einem anderen." + +#: credentialsd-ui/src/gui/view_model/mod.rs:412 +msgid "" +"No more PIN attempts allowed. Remove and reinsert your device, or use a " +"different authenticator." +msgstr "" +"Keine weiteren PIN-Eingaben erlaubt. Versuchen Sie ihr Gerät aus- und wieder " +"einzustecken, oder verwenden Sie ein anderes Gerät." + +#: credentialsd-ui/src/gui/view_model/mod.rs:415 +msgid "This method isn't working right now. Please choose another one." +msgstr "Diese Methode funktioniert gerade nicht. Bitte wählen Sie eine andere." + #~ msgid "Devices" #~ msgstr "Geräte" diff --git a/credentialsd-ui/po/en_US.po b/credentialsd-ui/po/en_US.po index f9eda4a1..7dd9c6d3 100644 --- a/credentialsd-ui/po/en_US.po +++ b/credentialsd-ui/po/en_US.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-06-18 07:18-0500\n" +"POT-Creation-Date: 2026-10-08 15:34+0200\n" "PO-Revision-Date: 2025-10-10 14:45+0200\n" "Last-Translator: Martin Sirringhaus \n" "Language: en_US\n" @@ -31,8 +31,7 @@ msgid "Registering a credential" msgstr "Registering a credential" #. developer_name tag deprecated with Appstream 1.0 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:36 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:39 +#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:38 msgid "Isaiah Inuwa" msgstr "" @@ -45,70 +44,124 @@ msgstr "Choose device" msgid "Use a mobile device" msgstr "A mobile device" -#: credentialsd-ui/data/resources/ui/window.blp:145 +#: credentialsd-ui/data/resources/ui/window.blp:146 #, fuzzy msgid "Use your security key" msgstr "Insert your security key." -#: credentialsd-ui/data/resources/ui/window.blp:162 +#: credentialsd-ui/data/resources/ui/window.blp:174 msgid "Connect a security key" msgstr "Connect a security key" -#: credentialsd-ui/data/resources/ui/window.blp:197 +#: credentialsd-ui/data/resources/ui/window.blp:209 #, fuzzy msgid "Enter your device PIN" msgstr "Enter your PIN." -#: credentialsd-ui/data/resources/ui/window.blp:205 +#: credentialsd-ui/data/resources/ui/window.blp:218 msgid "Scan the QR code to connect your device" msgstr "Scan the QR code to connect your device" -#: credentialsd-ui/data/resources/ui/window.blp:247 -#: credentialsd-ui/data/resources/ui/window.blp:253 +#: credentialsd-ui/data/resources/ui/window.blp:260 +#: credentialsd-ui/data/resources/ui/window.blp:266 msgid "Choose credential" msgstr "Choose credential" -#: credentialsd-ui/data/resources/ui/window.blp:266 +#: credentialsd-ui/data/resources/ui/window.blp:279 +msgid "Set a PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:302 +msgid "Please choose a new PIN for your device." +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:315 +msgid "New PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:322 +msgid "Confirm PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:334 +#: credentialsd-ui/data/resources/ui/window.blp:405 +msgid "Close" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:339 +msgid "Continue" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:354 msgid "Complete" msgstr "Complete" -#: credentialsd-ui/data/resources/ui/window.blp:288 +#: credentialsd-ui/data/resources/ui/window.blp:376 msgid "Done!" msgstr "Done!" -#: credentialsd-ui/data/resources/ui/window.blp:299 +#: credentialsd-ui/data/resources/ui/window.blp:387 msgid "Something went wrong." msgstr "Something went wrong." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:159 +#: credentialsd-ui/data/resources/ui/window.blp:410 +#, fuzzy +msgid "Set PIN on device" +msgstr "A mobile device" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:181 msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "Enter your PIN. One attempt remaining." msgstr[1] "Enter your PIN. %d attempts remaining." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:165 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:187 msgid "Enter your PIN." msgstr "Enter your PIN." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:174 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:196 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "Touch your device again. One attempt remaining." msgstr[1] "Touch your device again. %d attempts remaining." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:180 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:202 msgid "Touch your device." msgstr "Touch your device." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:185 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:207 msgid "Touch your device" msgstr "Touch your device" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:188 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:214 +msgid "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." +msgstr "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:218 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:221 +msgid "" +"The entered PIN violates the PIN-policy of this device (PIN too long). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:224 +msgid "A PIN change is required by your device to continue." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:231 msgid "Scan the QR code with your device to begin authentication." msgstr "Scan the QR code with your device to begin authentication." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:201 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:244 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." @@ -116,19 +169,19 @@ msgstr "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:209 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:252 msgid "Device connected. Follow the instructions on your device" msgstr "Device connected. Follow the instructions on your device" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:298 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:358 msgid "Insert your security key." msgstr "Insert your security key." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:317 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:377 msgid "Multiple devices found. Please select with which to proceed." msgstr "Multiple devices found. Please select with which to proceed." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:362 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:426 msgid "Credential Manager" msgstr "Credential Manager" @@ -156,85 +209,71 @@ msgstr "A security key or card (NFC)" msgid "A security key (USB)" msgstr "A security key (USB)" -#: credentialsd-ui/src/gui/view_model/mod.rs:64 +#: credentialsd-ui/src/gui/view_model/mod.rs:62 msgid "unknown application" msgstr "unknown application" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:80 +#: credentialsd-ui/src/gui/view_model/mod.rs:76 msgid "Create a passkey for %s1" msgstr "Create a passkey for %s1" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:84 +#: credentialsd-ui/src/gui/view_model/mod.rs:80 msgid "Use a passkey for %s1" msgstr "Use a passkey for %s1" #. TRANSLATORS: %s1 is the "relying party" (e.g.: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:96 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:91 +#, fuzzy msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " -"credential to register at \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to create a credential to register at " +"\"%s1\". Only proceed if you trust this process." msgstr "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " "credential to register at \"%s1\". Only proceed if you trust this process." #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:103 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:97 +#, fuzzy msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " -"to sign in to \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to use a credential to sign in to \"%s1\". " +"Only proceed if you trust this process." msgstr "" "\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " "to sign in to \"%s1\". Only proceed if you trust this process." #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:115 +#: credentialsd-ui/src/gui/view_model/mod.rs:108 msgid "" "Scan the QR code using the camera on the device that has the passkey for %s1" msgstr "" #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:118 +#: credentialsd-ui/src/gui/view_model/mod.rs:111 #, fuzzy msgid "Insert and activate your security key to use it for %s1" msgstr "Insert your security key." -#: credentialsd-ui/src/gui/view_model/mod.rs:181 -msgid "Failed to select credential from device." -msgstr "Failed to select credential from device." - -#: credentialsd-ui/src/gui/view_model/mod.rs:237 -msgid "No matching credentials found on this authenticator." -msgstr "No matching credentials found on this authenticator." - -#: credentialsd-ui/src/gui/view_model/mod.rs:245 +#: credentialsd-ui/src/gui/view_model/mod.rs:138 msgid "" -"No more PIN attempts allowed. Try removing your device and plugging it back " -"in." +"None of the available methods are working right now. Please try again later." msgstr "" -"No more PIN attempts allowed. Try removing your device and plugging it back " -"in." +"None of the available methods are working right now. Please try again later." -#: credentialsd-ui/src/gui/view_model/mod.rs:254 -msgid "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." -msgstr "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." +#: credentialsd-ui/src/gui/view_model/mod.rs:206 +msgid "Failed to select credential from device." +msgstr "Failed to select credential from device." -#: credentialsd-ui/src/gui/view_model/mod.rs:262 +#: credentialsd-ui/src/gui/view_model/mod.rs:270 msgid "The credential request timed out. Please try again." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:272 +#: credentialsd-ui/src/gui/view_model/mod.rs:280 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -242,10 +281,36 @@ msgstr "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." -#: credentialsd-ui/src/gui/view_model/mod.rs:281 +#: credentialsd-ui/src/gui/view_model/mod.rs:289 msgid "This credential is already registered on this authenticator." msgstr "This credential is already registered on this authenticator." +#: credentialsd-ui/src/gui/view_model/mod.rs:406 +msgid "" +"The previous attempt was interrupted. Please follow the new prompts to try " +"again." +msgstr "" +"The previous attempt was interrupted. Please follow the new prompts to try " +"again." + +#: credentialsd-ui/src/gui/view_model/mod.rs:409 +msgid "" +"No matching credentials on this authenticator. Please try a different one." +msgstr "" +"No matching credentials on this authenticator. Please try a different one." + +#: credentialsd-ui/src/gui/view_model/mod.rs:412 +msgid "" +"No more PIN attempts allowed. Remove and reinsert your device, or use a " +"different authenticator." +msgstr "" +"No more PIN attempts allowed. Remove and reinsert your device, or use a " +"different authenticator." + +#: credentialsd-ui/src/gui/view_model/mod.rs:415 +msgid "This method isn't working right now. Please choose another one." +msgstr "This method isn't working right now. Please choose another one." + #~ msgid "Devices" #~ msgstr "Devices" diff --git a/credentialsd-ui/po/ka_GE.po b/credentialsd-ui/po/ka_GE.po index 8fcf6764..2f05d54f 100644 --- a/credentialsd-ui/po/ka_GE.po +++ b/credentialsd-ui/po/ka_GE.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: credentialsd-ui\n" "Report-Msgid-Bugs-To: \"https://github.com/linux-credentials/credentialsd/" "issues\"\n" -"POT-Creation-Date: 2026-06-18 07:18-0500\n" +"POT-Creation-Date: 2026-10-08 15:34+0200\n" "PO-Revision-Date: 2026-04-20 07:16+0200\n" "Last-Translator: Ekaterine Papava \n" "Language-Team: \n" @@ -39,8 +39,7 @@ msgid "Registering a credential" msgstr "ავტორიზაციის დეტალების რეგისტრაცია" #. developer_name tag deprecated with Appstream 1.0 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:36 -#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:39 +#: credentialsd-ui/data/xyz.iinuwa.credentialsd.CredentialsUi.metainfo.xml.in.in:38 msgid "Isaiah Inuwa" msgstr "Isaiah Inuwa" @@ -53,70 +52,125 @@ msgstr "აირჩიეთ მოწყობილობა" msgid "Use a mobile device" msgstr "მობილური მოწყობილობა" -#: credentialsd-ui/data/resources/ui/window.blp:145 +#: credentialsd-ui/data/resources/ui/window.blp:146 #, fuzzy msgid "Use your security key" msgstr "შეერთეთ თქვენი უსაფრთხოების გასაღები." -#: credentialsd-ui/data/resources/ui/window.blp:162 +#: credentialsd-ui/data/resources/ui/window.blp:174 msgid "Connect a security key" msgstr "უსაფრთხოების გასაღების დაკავშირება" -#: credentialsd-ui/data/resources/ui/window.blp:197 +#: credentialsd-ui/data/resources/ui/window.blp:209 #, fuzzy msgid "Enter your device PIN" msgstr "შეიყვანეთ PIN-კოდი." -#: credentialsd-ui/data/resources/ui/window.blp:205 +#: credentialsd-ui/data/resources/ui/window.blp:218 msgid "Scan the QR code to connect your device" msgstr "დაასკანირეთ QR კოდი თქვენი მოწყობილობის დასაკავშირებლად" -#: credentialsd-ui/data/resources/ui/window.blp:247 -#: credentialsd-ui/data/resources/ui/window.blp:253 +#: credentialsd-ui/data/resources/ui/window.blp:260 +#: credentialsd-ui/data/resources/ui/window.blp:266 msgid "Choose credential" msgstr "ავტორიზაციის დეტალების არჩევა" -#: credentialsd-ui/data/resources/ui/window.blp:266 +#: credentialsd-ui/data/resources/ui/window.blp:279 +msgid "Set a PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:302 +msgid "Please choose a new PIN for your device." +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:315 +msgid "New PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:322 +msgid "Confirm PIN" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:334 +#: credentialsd-ui/data/resources/ui/window.blp:405 +msgid "Close" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:339 +msgid "Continue" +msgstr "" + +#: credentialsd-ui/data/resources/ui/window.blp:354 msgid "Complete" msgstr "დასრულება" -#: credentialsd-ui/data/resources/ui/window.blp:288 +#: credentialsd-ui/data/resources/ui/window.blp:376 msgid "Done!" msgstr "მზადაა!" -#: credentialsd-ui/data/resources/ui/window.blp:299 +#: credentialsd-ui/data/resources/ui/window.blp:387 msgid "Something went wrong." msgstr "რაღაც მოხდა." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:159 +#: credentialsd-ui/data/resources/ui/window.blp:410 +#, fuzzy +msgid "Set PIN on device" +msgstr "მობილური მოწყობილობა" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:181 msgid "Enter your PIN. One attempt remaining." msgid_plural "Enter your PIN. %d attempts remaining." msgstr[0] "შეიყვანეთ თქვენი PIN-კოდი. დარჩენილია ერთი მცდელობა." msgstr[1] "შეიყვანეთ თქვენი PIN-კოდი. დარჩენილია %d მცდელობა." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:165 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:187 msgid "Enter your PIN." msgstr "შეიყვანეთ PIN-კოდი." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:174 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:196 msgid "Touch your device again. One attempt remaining." msgid_plural "Touch your device again. %d attempts remaining." msgstr[0] "შეეხეთ თქვენს მოწყობილობას კიდევ ერთხელ. დარჩენილია ერთი მცდელობა." msgstr[1] "შეეხეთ თქვენს მოწყობილობას კიდევ ერთხელ. დარჩენილია %d მცდელობა." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:180 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:202 msgid "Touch your device." msgstr "შეეხეთ თქვენს მოწყობილობას." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:185 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:207 msgid "Touch your device" msgstr "შეეხეთ თქვენს მოწყობილობას" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:188 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:214 +msgid "" +"This server requires your device to have additional protection like a PIN, " +"which is not set. Please set a PIN for this device and try again." +msgstr "" +"ეს სერვერი ითხოვს, რომ თქვენს მოწყობილობას ჰქონდეს დამატებითი დაცვა, " +"როგორიცაა PIN-კოდი, რომელიც დაყენებული არაა. დააყენეთ PIN-კოდი ამ " +"მოწყობილობისთვის და თავიდან სცადეთ." + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:218 +msgid "" +"The entered PIN violates the PIN-policy of this device (likely too short). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:221 +msgid "" +"The entered PIN violates the PIN-policy of this device (PIN too long). " +"Please try again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:224 +msgid "A PIN change is required by your device to continue." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:231 msgid "Scan the QR code with your device to begin authentication." msgstr "ავთენტიკაციის დასაწყებად დაასკანერეთ QR კოდი თქვენი მოწყობილობით." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:201 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:244 msgid "" "Connecting to your device. Make sure both devices are near each other and " "have Bluetooth enabled." @@ -124,20 +178,20 @@ msgstr "" "მიმდინარეობს თქვენს მოწყობილობასთან დაკავშირება. დარწმუნდით, რომ ორივე " "მოწყობილობა ახლოსაა ერთმანეთთან და რომ ბლუთუზი ჩართულია." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:209 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:252 msgid "Device connected. Follow the instructions on your device" msgstr "მოწყობილობა დაკავშირებულია. მიჰყევით ინსტრუქციებს თქვენს მოწყობილობაზე" -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:298 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:358 msgid "Insert your security key." msgstr "შეერთეთ თქვენი უსაფრთხოების გასაღები." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:317 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:377 msgid "Multiple devices found. Please select with which to proceed." msgstr "" "აღმოჩენილია ერთზე მეტი მოწყობილობა. აირჩიეთ, რომელი გნებავთ, გამოიყენოთ." -#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:362 +#: credentialsd-ui/src/gui/view_model/gtk/mod.rs:426 msgid "Credential Manager" msgstr "ავტორიზაციის დეტალების მმართველი" @@ -165,28 +219,28 @@ msgstr "უსაფრთხოების გასაღები, ან msgid "A security key (USB)" msgstr "უსაფრთხოების გასაღები (USB)" -#: credentialsd-ui/src/gui/view_model/mod.rs:64 +#: credentialsd-ui/src/gui/view_model/mod.rs:62 msgid "unknown application" msgstr "უცნობი აპლიკაცია" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:80 +#: credentialsd-ui/src/gui/view_model/mod.rs:76 msgid "Create a passkey for %s1" msgstr "საკვანძო გასაღების შექმნა %1-ისთვის" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:84 +#: credentialsd-ui/src/gui/view_model/mod.rs:80 msgid "Use a passkey for %s1" msgstr "საკვანძო გასაღების გამოყენება %1-ისთვის" #. TRANSLATORS: %s1 is the "relying party" (e.g.: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:96 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:91 +#, fuzzy msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to create a " -"credential to register at \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to create a credential to register at " +"\"%s1\". Only proceed if you trust this process." msgstr "" "\"%s2\" (პროცესის ID: %i1, გამშვები ფაილი: %s3) ითხოვს ავტორიზაციის " "დეტალების შექმნას \"%s1\"-ზე რეგისტრაციისთვის. გააგრძელეთ, მხოლოდ, მაშინ, თუ " @@ -194,59 +248,43 @@ msgstr "" #. TRANSLATORS: %s1 is the "relying party" (think: domain name) where the request is coming from #. TRANSLATORS: %s2 is the application name (e.g.: firefox) where the request is coming from, must be left untouched to make the name bold -#. TRANSLATORS: %i1 is the process ID of the requesting application -#. TRANSLATORS: %s3 is the absolute path (think: /usr/bin/firefox) of the requesting application -#: credentialsd-ui/src/gui/view_model/mod.rs:103 +#. TRANSLATORS: %s3 is the app ID (think: org.mozilla.firefox) of the requesting application +#: credentialsd-ui/src/gui/view_model/mod.rs:97 +#, fuzzy msgid "" -"\"%s2\" (process ID: %i1, binary: %s3) is asking to use a credential " -"to sign in to \"%s1\". Only proceed if you trust this process." +"\"%s2\" (%s3) is asking to use a credential to sign in to \"%s1\". " +"Only proceed if you trust this process." msgstr "" "\"%s2\" (პროცესის ID: %i1, გამშვები ფაილი: %s3) ითხოვს ავტორიზაციის " "დეტალების გამოყენებას \"%s1\"-ზე შესასვლელად. გააგრძელეთ, მხოლოდ, მაშინ, თუ " "ენდობით ამ პროცესს." #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:115 +#: credentialsd-ui/src/gui/view_model/mod.rs:108 msgid "" "Scan the QR code using the camera on the device that has the passkey for %s1" msgstr "" #. TRANSLATORS: %s1 is the relying party (think: domain name) where the request is coming from -#: credentialsd-ui/src/gui/view_model/mod.rs:118 +#: credentialsd-ui/src/gui/view_model/mod.rs:111 #, fuzzy msgid "Insert and activate your security key to use it for %s1" msgstr "შეერთეთ თქვენი უსაფრთხოების გასაღები." -#: credentialsd-ui/src/gui/view_model/mod.rs:181 -msgid "Failed to select credential from device." -msgstr "ავტორიზაციის დეტალების არჩევა მოწყობილობიდან ჩავარდა." - -#: credentialsd-ui/src/gui/view_model/mod.rs:237 -msgid "No matching credentials found on this authenticator." -msgstr "ამ ავთენტიკატორში შესაბამისი ავტორიზაციის დეტალები აღმოჩენილი არაა." - -#: credentialsd-ui/src/gui/view_model/mod.rs:245 +#: credentialsd-ui/src/gui/view_model/mod.rs:138 msgid "" -"No more PIN attempts allowed. Try removing your device and plugging it back " -"in." +"None of the available methods are working right now. Please try again later." msgstr "" -"მეტი PIN-კოდი დაშვებული აღარაა. სცადეთ, გამოაძროთ თქვენი მოწყობილობა და ისევ " -"შეაერთოთ." -#: credentialsd-ui/src/gui/view_model/mod.rs:254 -msgid "" -"This server requires your device to have additional protection like a PIN, " -"which is not set. Please set a PIN for this device and try again." -msgstr "" -"ეს სერვერი ითხოვს, რომ თქვენს მოწყობილობას ჰქონდეს დამატებითი დაცვა, " -"როგორიცაა PIN-კოდი, რომელიც დაყენებული არაა. დააყენეთ PIN-კოდი ამ " -"მოწყობილობისთვის და თავიდან სცადეთ." +#: credentialsd-ui/src/gui/view_model/mod.rs:206 +msgid "Failed to select credential from device." +msgstr "ავტორიზაციის დეტალების არჩევა მოწყობილობიდან ჩავარდა." -#: credentialsd-ui/src/gui/view_model/mod.rs:262 +#: credentialsd-ui/src/gui/view_model/mod.rs:270 msgid "The credential request timed out. Please try again." msgstr "" -#: credentialsd-ui/src/gui/view_model/mod.rs:272 +#: credentialsd-ui/src/gui/view_model/mod.rs:280 msgid "" "Something went wrong while retrieving a credential. Please try again later " "or use a different authenticator." @@ -254,10 +292,35 @@ msgstr "" "ავტორიზაციის დეტალების მიღებისას რაღაც არასწორად წავიდა. სცადეთ თავიდან " "მოგვიანებით, ან გამოიყენეთ სხვა ავთენტიკატორი." -#: credentialsd-ui/src/gui/view_model/mod.rs:281 +#: credentialsd-ui/src/gui/view_model/mod.rs:289 msgid "This credential is already registered on this authenticator." msgstr "ეს ავტორიზაციის დეტალი უკვე რეგისტრირებულია ამ ავთენტიკატორზე." +#: credentialsd-ui/src/gui/view_model/mod.rs:406 +msgid "" +"The previous attempt was interrupted. Please follow the new prompts to try " +"again." +msgstr "" + +#: credentialsd-ui/src/gui/view_model/mod.rs:409 +#, fuzzy +msgid "" +"No matching credentials on this authenticator. Please try a different one." +msgstr "ამ ავთენტიკატორში შესაბამისი ავტორიზაციის დეტალები აღმოჩენილი არაა." + +#: credentialsd-ui/src/gui/view_model/mod.rs:412 +#, fuzzy +msgid "" +"No more PIN attempts allowed. Remove and reinsert your device, or use a " +"different authenticator." +msgstr "" +"მეტი PIN-კოდი დაშვებული აღარაა. სცადეთ, გამოაძროთ თქვენი მოწყობილობა და ისევ " +"შეაერთოთ." + +#: credentialsd-ui/src/gui/view_model/mod.rs:415 +msgid "This method isn't working right now. Please choose another one." +msgstr "" + #~ msgid "Devices" #~ msgstr "მოწყობილობები" diff --git a/credentialsd-ui/src/dbus.rs b/credentialsd-ui/src/dbus.rs index 7f10823d..396d3b23 100644 --- a/credentialsd-ui/src/dbus.rs +++ b/credentialsd-ui/src/dbus.rs @@ -25,15 +25,14 @@ use zbus::{ use credentialsd_common::model::{ BACKGROUND_EVENT_ERROR_AUTHENTICATOR, BACKGROUND_EVENT_ERROR_CANCELLED, BACKGROUND_EVENT_ERROR_CREDENTIAL_EXCLUDED, BACKGROUND_EVENT_ERROR_INTERNAL, - BACKGROUND_EVENT_ERROR_NO_CREDENTIALS, BACKGROUND_EVENT_ERROR_PIN_ATTEMPTS_EXHAUSTED, - BACKGROUND_EVENT_ERROR_PIN_NOT_SET, BACKGROUND_EVENT_ERROR_TIMED_OUT, BackgroundEvent, - ClientPinEnteredOptions, Credential, CredentialSelectedOptions, Device, - DiscoveryRequestedOptions, NotifyHybridConnectedOptions, NotifyHybridConnectingOptions, - NotifyHybridRestartingOptions, NotifyHybridStartedOptions, NotifyNeedsPinOptions, - NotifyNeedsUserPresenceOptions, NotifyNeedsUserVerificationOptions, NotifyNfcConnectedOptions, - NotifyNfcRestartingOptions, NotifyPinNotSetOptions, NotifySelectingCredentialOptions, - NotifyUsbConnectedOptions, NotifyUsbRestartingOptions, Operation, PinNotSetError, - PortalBackendOptions, SetDevicePinOptions, UserInteractedEvent, WindowHandle, + BACKGROUND_EVENT_ERROR_TIMED_OUT, BackgroundEvent, ClientPinEnteredOptions, Credential, + CredentialSelectedOptions, DiscoveryRequestedOptions, NotifyHybridConnectedOptions, + NotifyHybridConnectingOptions, NotifyHybridRestartingOptions, NotifyHybridStartedOptions, + NotifyNeedsPinOptions, NotifyNeedsUserPresenceOptions, NotifyNeedsUserVerificationOptions, + NotifyNfcConnectedOptions, NotifyNfcRestartingOptions, NotifyPinNotSetOptions, + NotifySelectingCredentialOptions, NotifyUsbConnectedOptions, NotifyUsbRestartingOptions, + Operation, PinNotSetError, PortalBackendOptions, SetDevicePinOptions, Transport, + TransportRestartReason, UserInteractedEvent, WindowHandle, }; use crate::{RequestingApplication, ViewRequest, client::FlowControlClient}; @@ -50,7 +49,7 @@ pub struct CredentialPortalBackend { pub(crate) struct UiContext { parent_window: Option, r#type: Operation, - devices: Vec, + transports: Vec, app_id: String, app_display_name: String, app_pid: u32, @@ -72,7 +71,7 @@ impl CredentialPortalBackend { parent_window: Optional, _origin: String, r#type: Operation, - devices: Vec, + transports: Vec, app_id: String, app_pid: u32, options: PortalBackendOptions, @@ -107,7 +106,7 @@ impl CredentialPortalBackend { let ui_context = UiContext { parent_window: parent_window.into(), r#type, - devices, + transports, app_id, app_display_name, app_pid, @@ -312,12 +311,15 @@ impl CredentialPortalBackend { &self, #[zbus(object_server)] object_server: &ObjectServer, session_handle: ObjectPath<'_>, + reason: u8, _options: NotifyHybridRestartingOptions, ) -> fdo::Result<()> { + let reason = + TransportRestartReason::try_from(reason).unwrap_or(TransportRestartReason::Interrupted); self.notify_state_changed( object_server, session_handle, - BackgroundEvent::HybridRestarting, + BackgroundEvent::HybridRestarting { reason }, ) .await } @@ -326,12 +328,15 @@ impl CredentialPortalBackend { &self, #[zbus(object_server)] object_server: &ObjectServer, session_handle: ObjectPath<'_>, + reason: u8, _options: NotifyUsbRestartingOptions, ) -> fdo::Result<()> { + let reason = + TransportRestartReason::try_from(reason).unwrap_or(TransportRestartReason::Interrupted); self.notify_state_changed( object_server, session_handle, - BackgroundEvent::UsbRestarting, + BackgroundEvent::UsbRestarting { reason }, ) .await } @@ -340,12 +345,15 @@ impl CredentialPortalBackend { &self, #[zbus(object_server)] object_server: &ObjectServer, session_handle: ObjectPath<'_>, + reason: u8, _options: NotifyNfcRestartingOptions, ) -> fdo::Result<()> { + let reason = + TransportRestartReason::try_from(reason).unwrap_or(TransportRestartReason::Interrupted); self.notify_state_changed( object_server, session_handle, - BackgroundEvent::NfcRestarting, + BackgroundEvent::NfcRestarting { reason }, ) .await } @@ -376,14 +384,9 @@ impl CredentialPortalBackend { BACKGROUND_EVENT_ERROR_TIMED_OUT => Ok(BackgroundEvent::ErrorTimedOut), BACKGROUND_EVENT_ERROR_CANCELLED => Ok(BackgroundEvent::ErrorCancelled), BACKGROUND_EVENT_ERROR_AUTHENTICATOR => Ok(BackgroundEvent::ErrorAuthenticator), - BACKGROUND_EVENT_ERROR_NO_CREDENTIALS => Ok(BackgroundEvent::ErrorNoCredentials), BACKGROUND_EVENT_ERROR_CREDENTIAL_EXCLUDED => { Ok(BackgroundEvent::ErrorCredentialExcluded) } - BACKGROUND_EVENT_ERROR_PIN_ATTEMPTS_EXHAUSTED => { - Ok(BackgroundEvent::ErrorPinAttemptsExhausted) - } - BACKGROUND_EVENT_ERROR_PIN_NOT_SET => Ok(BackgroundEvent::ErrorPinNotSet), _ => Err(fdo::Error::Failed("Unknown error code".to_string())), }?; self.notify_state_changed(object_server, session_handle, error_event) @@ -645,7 +648,7 @@ impl CeremonyObject { name: self.ui_context.app_display_name.clone(), pid: self.ui_context.app_pid, }, - initial_devices: self.ui_context.devices.clone(), + initial_transports: self.ui_context.transports.clone(), window_handle: self.ui_context.parent_window.clone(), }, Arc::new(AsyncMutex::new(flow_control_client)), diff --git a/credentialsd-ui/src/gui/mod.rs b/credentialsd-ui/src/gui/mod.rs index e55ada9c..4863d323 100644 --- a/credentialsd-ui/src/gui/mod.rs +++ b/credentialsd-ui/src/gui/mod.rs @@ -5,8 +5,8 @@ use std::{sync::Arc, thread::JoinHandle}; use async_std::{channel::Receiver, sync::Mutex as AsyncMutex}; -use credentialsd_common::model::Device; use credentialsd_common::model::{Credential, PinNotSetError, WindowHandle}; +use credentialsd_common::model::{Device, Transport}; use crate::{ViewRequest, client::FlowControlClient}; @@ -73,7 +73,7 @@ pub enum ViewUpdate { qr_prompt: String, usb_prompt: String, }, - SetDevices(Vec), + SetTransports(Vec), // TODO: Fix this SetCredentials(Vec), @@ -96,8 +96,11 @@ pub enum ViewUpdate { HybridConnected, /// A transport ceremony was interrupted by a non-terminating error and - /// is restarting. The UI should navigate back to the start page. - TransportRestarting, + /// is restarting. The UI should navigate back to the start page and display + /// the provided localized message. + TransportRestarting { + message: String, + }, Completed, Cancelled, diff --git a/credentialsd-ui/src/gui/view_model/gtk/mod.rs b/credentialsd-ui/src/gui/view_model/gtk/mod.rs index 76afc9e1..992a5351 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/mod.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/mod.rs @@ -19,8 +19,8 @@ use tracing::debug; use crate::config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE}; use application::CredentialsUi; -use super::Transport; use super::{Credential, Device}; +use super::{Transport, TransportRestartReason}; use super::{ViewEvent, ViewUpdate}; use self::credential::CredentialObject; @@ -49,6 +49,10 @@ mod imp { #[property(get, set)] pub hybrid_transport_available: RefCell, + /// USB or NFC; both live in the "Use your security key" section. + #[property(get, set)] + pub security_key_transport_available: RefCell, + #[property(get, set)] pub credentials: RefCell, @@ -87,6 +91,12 @@ mod imp { #[property(get, set)] pub transport_restarting: RefCell, + #[property(get, set)] + pub restart_message: RefCell, + + #[property(get, set)] + pub restart_message_visible: RefCell, + #[property(get, set)] pub start_setting_new_pin_visible: RefCell, @@ -155,8 +165,8 @@ impl ViewModel { view_model.set_scan_qr_prompt(qr_prompt); view_model.set_activate_usb_prompt(usb_prompt); } - ViewUpdate::SetDevices(devices) => { - view_model.update_devices(&devices) + ViewUpdate::SetTransports(transports) => { + view_model.update_transports(&transports) } ViewUpdate::SetCredentials(credentials) => { view_model.update_credentials(&credentials) @@ -243,12 +253,14 @@ impl ViewModel { )); view_model.set_qr_spinner_visible(false); } - ViewUpdate::TransportRestarting => { + ViewUpdate::TransportRestarting { message } => { // Signal the window to navigate back to start_page. // The transport will emit a fresh Init/Connected state // next, which will update the prompt and show the new // QR code or device-waiting UI from start_page. view_model.set_qr_spinner_visible(false); + view_model.set_restart_message(message); + view_model.set_restart_message_visible(true); view_model.set_transport_restarting(true); } ViewUpdate::Completed => { @@ -277,15 +289,22 @@ impl ViewModel { )); } - fn update_devices(&self, devices: &[Device]) { - let hybrid_available = devices.iter().any(|dev| { - dev.transport == Transport::HybridLinked || dev.transport == Transport::HybridQr - }); + fn update_transports(&self, transports: &[Transport]) { + let hybrid_available = transports.contains(&Transport::HybridQr) + || transports.contains(&Transport::HybridLinked); + let security_key_available = + transports.contains(&Transport::Usb) || transports.contains(&Transport::Nfc); + // A section that was shown is going away: its transport gave up mid-request. + if (self.hybrid_transport_available() && !hybrid_available) + || (self.security_key_transport_available() && !security_key_available) + { + self.set_restart_message(super::localized_transport_restart_reason( + &TransportRestartReason::TransportUnavailable, + )); + self.set_restart_message_visible(true); + } self.set_hybrid_transport_available(hybrid_available); - // TODO: This is called when a new credential source is available to show it in the UI. - // However, I believe in the current paradigm, we will know all available - // credential sources at the beginning of the request, so we won't need - // to update these during the request. We may be able to reomve this method altogether. + self.set_security_key_transport_available(security_key_available); } fn update_credentials(&self, credentials: &[Credential]) { diff --git a/credentialsd-ui/src/gui/view_model/gtk/window.rs b/credentialsd-ui/src/gui/view_model/gtk/window.rs index dceb7183..e948b20e 100644 --- a/credentialsd-ui/src/gui/view_model/gtk/window.rs +++ b/credentialsd-ui/src/gui/view_model/gtk/window.rs @@ -240,6 +240,31 @@ impl CredentialsUiWindow { } )); + // A transport gave up mid-request. If the user is on its page, go back to + // start_page, where the remaining transports are. + view_model.connect_hybrid_transport_available_notify(clone!( + #[weak] + stack, + move |vm| { + if !vm.hybrid_transport_available() + && stack.visible_child_name().as_deref() == Some("hybrid_qr") + { + stack.set_visible_child_name("start_page"); + } + } + )); + view_model.connect_security_key_transport_available_notify(clone!( + #[weak] + stack, + move |vm| { + if !vm.security_key_transport_available() + && stack.visible_child_name().as_deref() == Some("usb_or_nfc") + { + stack.set_visible_child_name("start_page"); + } + } + )); + view_model.connect_completed_notify(clone!( #[weak] stack, diff --git a/credentialsd-ui/src/gui/view_model/mod.rs b/credentialsd-ui/src/gui/view_model/mod.rs index 6cf331bd..959727f0 100644 --- a/credentialsd-ui/src/gui/view_model/mod.rs +++ b/credentialsd-ui/src/gui/view_model/mod.rs @@ -8,7 +8,7 @@ use async_std::{ sync::Mutex as AsyncMutex, }; use credentialsd_common::memfd::read_secret; -use credentialsd_common::model::{BackgroundEvent, Credential}; +use credentialsd_common::model::{BackgroundEvent, Credential, TransportRestartReason}; use gettextrs::gettext; use serde::{Deserialize, Serialize}; use tracing::{error, info}; @@ -31,8 +31,8 @@ pub(crate) struct ViewModel { app_name: String, app_path_or_id: String, - // This includes devices like platform authenticator, USB, hybrid - devices: Vec, + // Transports offered to the user, e.g. USB and hybrid + transports: Vec, selected_device: Option, hybrid_qr_code_data: Option>, @@ -52,7 +52,7 @@ impl ViewModel { } = request.requesting_app; let app_name: Option = app_name.into(); - let devices = request.initial_devices; + let transports = request.initial_transports; Self { flow_controller, rx_event, @@ -63,7 +63,7 @@ impl ViewModel { app_path_or_id: path, title: String::default(), subtitle: String::default(), - devices, + transports, selected_device: None, hybrid_qr_code_data: None, } @@ -121,14 +121,28 @@ impl ViewModel { .unwrap(); } - async fn update_devices(&mut self, devices: Vec) { - self.devices = devices; + async fn send_transports(&self) { self.tx_update - .send(ViewUpdate::SetDevices(self.devices.to_owned())) + .send(ViewUpdate::SetTransports(self.transports.to_owned())) .await .unwrap(); } + /// Stops offering transports that gave up for this request. + async fn drop_transports(&mut self, gone: &[Transport]) { + self.transports + .retain(|transport| !gone.contains(transport)); + self.send_transports().await; + if self.transports.is_empty() { + self.tx_update + .send(ViewUpdate::Failed(gettext( + "None of the available methods are working right now. Please try again later.", + ))) + .await + .unwrap(); + } + } + pub(crate) async fn start_discovery(&self) { let cred_service = self.flow_controller.lock().await; (*cred_service).discover_authenticators().await.unwrap(); @@ -146,7 +160,7 @@ impl ViewModel { match event { Event::View(ViewEvent::Initiated) => { self.update_title().await; - self.update_devices(self.devices.clone()).await; + self.send_transports().await; self.start_discovery().await; } Event::View(ViewEvent::PinEntered(pin)) => { @@ -252,31 +266,7 @@ impl ViewModel { .await .unwrap(); } - Event::Background(BackgroundEvent::ErrorNoCredentials) => { - let error_msg = gettext("No matching credentials found on this authenticator."); - self.tx_update - .send(ViewUpdate::Failed(error_msg)) - .await - .unwrap() - } - Event::Background(BackgroundEvent::ErrorPinAttemptsExhausted) => { - let error_msg = gettext( - "No more PIN attempts allowed. Try removing your device and plugging it back in.", - ); - self.tx_update - .send(ViewUpdate::Failed(error_msg)) - .await - .unwrap() - } - Event::Background(BackgroundEvent::ErrorPinNotSet) => { - let error_msg = gettext( - "This server requires your device to have additional protection like a PIN, which is not set. Please set a PIN for this device and try again.", - ); - self.tx_update - .send(ViewUpdate::Failed(error_msg)) - .await - .unwrap() - } + Event::Background(BackgroundEvent::ErrorTimedOut) => { let error_msg = gettext("The credential request timed out. Please try again."); self.tx_update @@ -348,14 +338,34 @@ impl ViewModel { .await .unwrap(); } + // A transport gave up for this request: stop offering it. No + // TransportRestarting here, as that would pull the user off the page + // of a transport that still works. + Event::Background(BackgroundEvent::HybridRestarting { + reason: TransportRestartReason::TransportUnavailable, + }) => { + self.drop_transports(&[Transport::HybridQr, Transport::HybridLinked]) + .await; + } + Event::Background(BackgroundEvent::UsbRestarting { + reason: TransportRestartReason::TransportUnavailable, + }) => { + self.drop_transports(&[Transport::Usb]).await; + } + Event::Background(BackgroundEvent::NfcRestarting { + reason: TransportRestartReason::TransportUnavailable, + }) => { + self.drop_transports(&[Transport::Nfc]).await; + } Event::Background( - BackgroundEvent::HybridRestarting - | BackgroundEvent::UsbRestarting - | BackgroundEvent::NfcRestarting, + BackgroundEvent::HybridRestarting { reason } + | BackgroundEvent::UsbRestarting { reason } + | BackgroundEvent::NfcRestarting { reason }, ) => { self.hybrid_qr_code_data = None; + let message = localized_transport_restart_reason(&reason); self.tx_update - .send(ViewUpdate::TransportRestarting) + .send(ViewUpdate::TransportRestarting { message }) .await .unwrap(); } @@ -391,6 +401,23 @@ impl Debug for ViewEvent { } } +fn localized_transport_restart_reason(reason: &TransportRestartReason) -> String { + match reason { + TransportRestartReason::Interrupted => gettext( + "The previous attempt was interrupted. Please follow the new prompts to try again.", + ), + TransportRestartReason::NoCredentials => { + gettext("No matching credentials on this authenticator. Please try a different one.") + } + TransportRestartReason::PinAttemptsExhausted => gettext( + "No more PIN attempts allowed. Remove and reinsert your device, or use a different authenticator.", + ), + TransportRestartReason::TransportUnavailable => { + gettext("This method isn't working right now. Please choose another one.") + } + } +} + #[derive(Debug)] pub enum Event { Background(BackgroundEvent), diff --git a/credentialsd-ui/src/main.rs b/credentialsd-ui/src/main.rs index 4265f4fe..5f8544af 100644 --- a/credentialsd-ui/src/main.rs +++ b/credentialsd-ui/src/main.rs @@ -7,7 +7,7 @@ mod gui; use std::error::Error; use credentialsd_common::model::WindowHandle; -use credentialsd_common::model::{Device, Operation}; +use credentialsd_common::model::{Operation, Transport}; use crate::dbus::CredentialPortalBackend; @@ -68,8 +68,8 @@ pub struct ViewRequest { /// Details about the application requesting credentials. pub requesting_app: RequestingApplication, - /// Initial list of device interfaces that may provide credentials. - pub initial_devices: Vec, + /// Initial list of transports that may provide credentials. + pub initial_transports: Vec, /// Client window handle. pub window_handle: Option, diff --git a/credentialsd/src/credential_service/hybrid.rs b/credentialsd/src/credential_service/hybrid.rs index c5094e99..c03ad6f4 100644 --- a/credentialsd/src/credential_service/hybrid.rs +++ b/credentialsd/src/credential_service/hybrid.rs @@ -22,7 +22,10 @@ use tokio_util::sync::CancellationToken; use tracing::{debug, error}; use super::CredentialServiceError; -use credentialsd_common::{memfd::write_secret, model::BackgroundEvent}; +use credentialsd_common::{ + memfd::write_secret, + model::{BackgroundEvent, TransportRestartReason}, +}; use crate::model::{CredentialRequest, CredentialResponse}; @@ -72,18 +75,23 @@ impl HybridHandler for InternalHybridHandler { // Outer retry loop: re-issues QR on non-terminating failures. // Each iteration creates a fresh CableQrCodeDevice (the previous one // is consumed by channel()), so the old QR secret is discarded. + // Consecutive failures before the phone connected; reset once it does. + let mut pre_active_failures = 0; loop { + // Reset the active flag for each ceremony attempt. run_hybrid_ceremony + // sets it if libwebauthn reported the phone's BLE advert + // (`CableUpdate::Connecting`), i.e. the QR code was scanned. + let mut active = false; + let mut device = match CableQrCodeDevice::new_transient(hint, hybrid_transports) { Ok(device) => device, Err(err) => { tracing::error!("Failed to create caBLE QR code device: {:?}", err); // Device creation failure cannot be retried meaningfully — - // break to avoid a tight error loop. + // give up on hybrid for this request; other transports go on. let _ = tx .send(HybridStateInternal::Failed( - CredentialServiceError::Internal(format!( - "Failed to create caBLE device: {err:?}" - )), + CredentialServiceError::UnrecoverableTransportError, )) .await; break; @@ -98,31 +106,66 @@ impl HybridHandler for InternalHybridHandler { // Run the ceremony awaited directly (not in a nested spawn) so that // the retry loop is sequential and no orphaned tasks can arise. - let response = - run_hybrid_ceremony(&mut device, &request, &tx, cancellation.clone()).await; + let response = run_hybrid_ceremony( + &mut device, + &request, + &tx, + cancellation.clone(), + &mut active, + ) + .await; match response { Ok(auth_response) => { let _ = tx.send(HybridStateInternal::Completed(auth_response)).await; break; } + // Must come before is_ceremony_terminating(), which returns false for + // cancellation (poll_next must not complete_request() twice). + Err(CredentialServiceError::NonTerminatingCancellation) => { + tracing::debug!("Hybrid handler cancelled, exiting silently"); + break; + } Err(err) if super::is_ceremony_terminating(&err) => { - // Terminating errors (CredentialExcluded, NonTerminatingCancellation - // from a winning transport, etc.) stop the loop. - // NonTerminatingCancellation exits silently; others surface as Failed. - if !matches!(err, CredentialServiceError::NonTerminatingCancellation) { - let _ = tx.send(HybridStateInternal::Failed(err)).await; - } else { - tracing::debug!("Hybrid handler cancelled, exiting silently"); - } + let _ = tx.send(HybridStateInternal::Failed(err)).await; break; } Err(err) => { - // Non-terminating: notify the UI that a restart is in progress - // so it can navigate back to the start page, then reissue a - // fresh QR on the next iteration. - tracing::warn!(?err, "Hybrid error, reissuing QR"); - let _ = tx.send(HybridStateInternal::Restarting).await; + if active { + // Post-active: the phone was engaged — surface the error + // via the Restarting signal so the UI navigates back to + // start_page, then reissue a fresh QR. + let reason = match err { + CredentialServiceError::NoCredentials => { + TransportRestartReason::NoCredentials + } + CredentialServiceError::PinAttemptsExhausted => { + TransportRestartReason::PinAttemptsExhausted + } + _ => TransportRestartReason::Interrupted, + }; + tracing::warn!(?err, "Hybrid post-active error, reissuing QR"); + let _ = tx.send(HybridStateInternal::Restarting(reason)).await; + pre_active_failures = 0; + } else { + // Pre-active: the attempt failed before the phone's BLE advert + // arrived (e.g. the advert scan itself failed). Reissue + // silently, but give up after MAX_PRE_ACTIVE_FAILURES in a row. + pre_active_failures += 1; + if pre_active_failures >= super::MAX_PRE_ACTIVE_FAILURES { + tracing::warn!( + ?err, + "Hybrid failed {pre_active_failures} times before the phone connected, giving up" + ); + let _ = tx + .send(HybridStateInternal::Failed( + CredentialServiceError::UnrecoverableTransportError, + )) + .await; + break; + } + tracing::debug!(?err, "Hybrid pre-active error, reissuing QR silently"); + } continue; } } @@ -156,7 +199,7 @@ pub(super) enum HybridStateInternal { /// The ceremony was interrupted by a non-terminating error. A fresh QR code /// is about to be issued on the next iteration. - Restarting, + Restarting(TransportRestartReason), } // this is here to prevent making HybridStateInternal public to the whole crate. @@ -186,7 +229,7 @@ pub enum HybridState { /// The ceremony was interrupted by a non-terminating error and a new QR /// code is being issued. The UI should navigate back to the start page. - Restarting, + Restarting(TransportRestartReason), } impl From for HybridState { @@ -197,7 +240,7 @@ impl From for HybridState { HybridStateInternal::Connected => HybridState::Connected, HybridStateInternal::Completed(_) => HybridState::Completed, HybridStateInternal::Failed(err) => HybridState::Failed(err), - HybridStateInternal::Restarting => HybridState::Restarting, + HybridStateInternal::Restarting(reason) => HybridState::Restarting(reason), } } } @@ -219,26 +262,30 @@ impl From<&HybridState> for BackgroundEvent { HybridState::Connecting => BackgroundEvent::HybridConnecting, HybridState::Connected => BackgroundEvent::HybridConnected, HybridState::Completed => BackgroundEvent::CeremonyCompleted, - HybridState::Restarting => BackgroundEvent::HybridRestarting, + HybridState::Restarting(reason) => { + BackgroundEvent::HybridRestarting { reason: *reason } + } HybridState::Failed(CredentialServiceError::AuthenticatorError) => { BackgroundEvent::ErrorAuthenticator } - HybridState::Failed(CredentialServiceError::NoCredentials) => { - BackgroundEvent::ErrorNoCredentials - } HybridState::Failed(CredentialServiceError::CredentialExcluded) => { BackgroundEvent::ErrorCredentialExcluded } - HybridState::Failed(CredentialServiceError::PinAttemptsExhausted) => { - BackgroundEvent::ErrorAuthenticator - } // This should currently never be reached, but we'll likely use it in future refactoring HybridState::Failed(CredentialServiceError::NonTerminatingCancellation) => { BackgroundEvent::ErrorCancelled } - HybridState::Failed(CredentialServiceError::Internal(_)) => { + HybridState::Failed(CredentialServiceError::Internal(_)) + | HybridState::Failed(CredentialServiceError::NoCredentials) + | HybridState::Failed(CredentialServiceError::PinAttemptsExhausted) => { BackgroundEvent::ErrorInternal } + // Hybrid stopped for this request; the UI stops offering it. + HybridState::Failed(CredentialServiceError::UnrecoverableTransportError) => { + BackgroundEvent::HybridRestarting { + reason: TransportRestartReason::TransportUnavailable, + } + } } } } @@ -254,8 +301,16 @@ async fn run_hybrid_ceremony( request: &CredentialRequest, tx: &Sender, cancellation: CancellationToken, + active: &mut bool, ) -> Result { - let mut channel = match device.channel(ChannelSettings::default()).await { + let Some(result) = cancellation + .run_until_cancelled(device.channel(ChannelSettings::default())) + .await + else { + tracing::debug!("Hybrid handler cancelled while opening channel"); + return Err(CredentialServiceError::NonTerminatingCancellation); + }; + let mut channel = match result { Ok(channel) => channel, Err(e) => { tracing::error!("Failed to open hybrid channel: {:?}", e); @@ -263,6 +318,11 @@ async fn run_hybrid_ceremony( } }; + // `channel()` returns right away: the proximity check (waiting for the phone's + // BLE advert), tunnel connection and handshake run in the background. This + // receiver is drained after the attempt to learn whether the phone engaged. + let mut progress_rx = channel.get_ux_update_receiver(); + let state_sender_clone = tx.clone(); let ux_updates_rx = channel.get_ux_update_receiver(); tokio::spawn(async move { @@ -328,7 +388,7 @@ async fn run_hybrid_ceremony( }; tracing::debug!("Polling hybrid channel for updates."); - match cancellation + let result = match cancellation .run_until_cancelled(wait_for_response_fut) .await { @@ -337,7 +397,25 @@ async fn run_hybrid_ceremony( tracing::debug!("Hybrid handler cancelled, stopping processing"); Err(CredentialServiceError::NonTerminatingCancellation) } + }; + *active = phone_engaged(&mut progress_rx); + result +} + +/// Whether the phone got past the QR scan in this attempt. libwebauthn reports +/// `Connecting` once it has received the phone's BLE advert, which only happens +/// after the QR code was scanned. Every update sent before the attempt ended is +/// already queued in `rx` when this is called. +fn phone_engaged(rx: &mut broadcast::Receiver) -> bool { + while let Ok(update) = rx.try_recv() { + match update { + // Before (or without) the phone's BLE advert, e.g. the advert scan failed. + CableUxUpdate::CableUpdate(CableUpdate::ProximityCheck | CableUpdate::Error(_)) => {} + // Everything else only happens once the phone's advert arrived. + _ => return true, + } } + false } async fn handle_hybrid_updates( @@ -360,10 +438,10 @@ async fn handle_hybrid_updates( CableUpdate::Authenticating => Some(HybridStateInternal::Connecting), CableUpdate::Connected => Some(HybridStateInternal::Connected), CableUpdate::Error(transport_error) => { + // Not forwarded: the failed attempt also ends the webauthn call + // with an error, which the retry loop handles (restart or give up). error!(?transport_error, "Hybrid transport error"); - Some(HybridStateInternal::Failed( - CredentialServiceError::AuthenticatorError, - )) + None } }, }; @@ -374,3 +452,32 @@ async fn handle_hybrid_updates( } } } + +#[cfg(test)] +mod tests { + use libwebauthn::transport::cable::error::CableError; + + use super::*; + + fn cable(update: CableUpdate) -> CableUxUpdate { + CableUxUpdate::CableUpdate(update) + } + + #[test] + fn test_phone_engaged() { + let (tx, mut rx) = broadcast::channel(16); + tx.send(cable(CableUpdate::ProximityCheck)).unwrap(); + assert!(!phone_engaged(&mut rx), "QR shown, never scanned"); + + tx.send(cable(CableUpdate::ProximityCheck)).unwrap(); + tx.send(cable(CableUpdate::Error(CableError::ConnectionFailed))) + .unwrap(); + assert!(!phone_engaged(&mut rx), "advert scan failed before a scan"); + + tx.send(cable(CableUpdate::ProximityCheck)).unwrap(); + tx.send(cable(CableUpdate::Connecting)).unwrap(); + tx.send(cable(CableUpdate::Error(CableError::ConnectionFailed))) + .unwrap(); + assert!(phone_engaged(&mut rx), "failed after the phone's advert"); + } +} diff --git a/credentialsd/src/credential_service/mod.rs b/credentialsd/src/credential_service/mod.rs index fd8189b6..97ca7db1 100644 --- a/credentialsd/src/credential_service/mod.rs +++ b/credentialsd/src/credential_service/mod.rs @@ -23,7 +23,7 @@ use nfc::{NfcEvent, NfcHandler, NfcState, NfcStateInternal}; use tokio::sync::oneshot; use tokio_util::sync::CancellationToken; -use credentialsd_common::model::{BackgroundEvent, Device, Transport}; +use credentialsd_common::model::{BackgroundEvent, Transport}; use zbus::zvariant::{self, Value}; use crate::{ @@ -41,6 +41,11 @@ pub use usb::UsbState; /// Identifier for a request to be used for cancellation. pub type RequestId = u32; +/// Consecutive failures a transport tolerates before the user has interacted with +/// it (phone connected, or a PIN/UV/presence prompt). At this limit the transport +/// gives up with `UnrecoverableTransportError`. User interaction resets the count. +pub(super) const MAX_PRE_ACTIVE_FAILURES: usize = 5; + /// Helper function to sleep with cancellation support. async fn cancellable_sleep( duration: std::time::Duration, @@ -81,6 +86,10 @@ pub enum CredentialServiceError { /// This is distinct from user- or client-issued cancellation: the response channel /// has already been consumed elsewhere, so `complete_request` must NOT be called. NonTerminatingCancellation, + /// The transport failed `MAX_PRE_ACTIVE_FAILURES` times in a row before the user + /// interacted with it, and gave up for the rest of this request. The ceremony + /// continues on the remaining transports. + UnrecoverableTransportError, // TODO: We may want to hide the details on this variant from the public API. /// Something went wrong with the credential service itself, not the authenticator. Internal(String), @@ -96,6 +105,7 @@ impl Display for CredentialServiceError { Self::CredentialExcluded => f.write_str("CredentialExcluded"), Self::PinAttemptsExhausted => f.write_str("PinAttemptsExhausted"), Self::NonTerminatingCancellation => f.write_str("NonTerminatingCancellation"), + Self::UnrecoverableTransportError => f.write_str("UnrecoverableTransportError"), Self::Internal(s) => write!(f, "InternalError: {s}"), } } @@ -112,6 +122,7 @@ impl TryFrom<&Value<'_>> for CredentialServiceError { "CredentialExcluded" => Self::CredentialExcluded, "PinAttemptsExhausted" => Self::PinAttemptsExhausted, "NonTerminatingCancellation" => Self::NonTerminatingCancellation, + "UnrecoverableTransportError" => Self::UnrecoverableTransportError, s => Self::Internal(String::from(s)), }; Ok(err) @@ -124,6 +135,9 @@ struct RequestContext { response_channel: oneshot::Sender>, request_id: RequestId, cancellation: CancellationToken, + /// Transports found by `detect_transports` for this request. The same list + /// goes to the UI and is what `start_discovery` starts. + transports: Vec, } impl RequestContext { @@ -145,7 +159,9 @@ pub trait ManageDevice { tx: oneshot::Sender>, ) -> Result<(RequestId, CancellationToken), CredentialServiceError>; async fn cancel_request(&self, request_id: RequestId); - async fn get_available_public_key_devices(&self) -> Result, ()>; + /// Detects the transports usable for the current request and remembers them, + /// so `start_discovery` starts exactly what was reported to the UI. + async fn detect_transports(&self) -> Vec; async fn start_discovery( &self, ) -> Pin + Send + 'static>>; @@ -157,7 +173,7 @@ pub struct CredentialService { ctx: Arc>>, hybrid_handler: Mutex, - _nfc_handler: Mutex, + nfc_handler: Mutex, usb_handler: Mutex, } @@ -169,7 +185,7 @@ impl ctx: Arc::new(Mutex::new(None)), hybrid_handler: Mutex::new(hybrid_handler), - _nfc_handler: Mutex::new(nfc_handler), + nfc_handler: Mutex::new(nfc_handler), usb_handler: Mutex::new(usb_handler), } } @@ -234,7 +250,7 @@ impl } } - async fn _get_nfc_credential(&self) -> Pin + Send + 'static>> { + async fn get_nfc_credential(&self) -> Pin + Send + 'static>> { let guard = self.ctx.lock().unwrap(); if let Some(RequestContext { ref request, @@ -243,7 +259,7 @@ impl }) = *guard { let stream = self - ._nfc_handler + .nfc_handler .lock() .unwrap() .start(request, cancellation.clone()); @@ -287,6 +303,7 @@ impl Manage response_channel: tx, request_id, cancellation: cancellation.clone(), + transports: Vec::new(), }; _ = cred_request.insert(ctx); Ok((request_id, cancellation)) @@ -311,34 +328,27 @@ impl Manage } } - async fn get_available_public_key_devices(&self) -> Result, ()> { - // We create the list new for each call, in case someone plugs in - // an NFC-reader in the middle of an auth-flow - let mut devices = vec![Device { - id: String::from("0"), - transport: Transport::Usb, - }]; - if libwebauthn::transport::cable::is_available().await { - devices.push(Device { - id: String::from("1"), - transport: Transport::HybridQr, - }); - } - if libwebauthn::transport::nfc::is_nfc_available() { - devices.push(Device { - id: String::from("2"), - transport: Transport::Nfc, - }); + async fn detect_transports(&self) -> Vec { + let transports = supported_transports(&available_transports().await); + if let Some(ctx) = self.ctx.lock().unwrap().as_mut() { + ctx.transports = transports.clone(); } - Ok(devices) + transports } async fn start_discovery( &self, ) -> Pin + Send + 'static>> { - let available_transports = available_transports().await; + // Start exactly what detect_transports() reported to the UI. + let transports = self + .ctx + .lock() + .unwrap() + .as_ref() + .map(|ctx| ctx.transports.clone()) + .unwrap_or_default(); let mut selected_transports = Vec::new(); - if available_transports.contains(&libwebauthn::Transport::Usb) { + if transports.contains(&Transport::Usb) { let usb = self .get_usb_credential() .await @@ -346,15 +356,7 @@ impl Manage .boxed(); selected_transports.push(usb); } - /* - TODO: Some cards that support NFC but not CCID (SoloKey Solo 2 NFC) - cause a framing error immediately after establishing a libwebauthn - Channel, which causes the whole ceremony to abort without the user - intending to. We need to determine some way of working around buggy - security keys, while at the same time supporting actual NFC cards and CCID. - Maybe we can defer sending "Connected" to the UI until a user presence - or verification message is sent. - if available_transports.contains(&libwebauthn::Transport::Nfc) { + if transports.contains(&Transport::Nfc) { let nfc = self .get_nfc_credential() .await @@ -362,8 +364,7 @@ impl Manage .boxed(); selected_transports.push(nfc); } - */ - if available_transports.contains(&libwebauthn::Transport::Hybrid) { + if transports.contains(&Transport::HybridQr) { let hybrid = self .get_hybrid_credential() .await @@ -467,7 +468,6 @@ where } } -#[expect(unused)] struct NfcStateStream { inner: H, ctx: Arc>>, @@ -569,8 +569,6 @@ pub(super) fn is_ceremony_terminating(err: &CredentialServiceError) -> bool { // cancelled by *another* path — the winning transports `complete_request()`, // or `cancel_request()` sending its own response. The response channel is // already consumed, so this arm must NOT invoke `complete_request()` again. - // A future `TerminatingCancellation` variant will handle user-initiated - // cancellation from the trusted UI, which is ceremony-terminating. CredentialServiceError::NonTerminatingCancellation => false, // Per-authenticator errors: keep the ceremony alive. The user may succeed @@ -580,11 +578,36 @@ pub(super) fn is_ceremony_terminating(err: &CredentialServiceError) -> bool { CredentialServiceError::NoCredentials => false, CredentialServiceError::PinAttemptsExhausted => false, + // The transport gave up for this request; the others keep running. + CredentialServiceError::UnrecoverableTransportError => false, + // Transient internal errors: do not kill the ceremony. CredentialServiceError::Internal(_) => false, } } +/// Maps the transports libwebauthn found to the ones this service runs. +fn supported_transports(found: &[libwebauthn::Transport]) -> Vec { + found + .iter() + .filter_map(|transport| match transport { + libwebauthn::Transport::Usb => Some(Transport::Usb), + libwebauthn::Transport::Hybrid => Some(Transport::HybridQr), + // There is no BLE handler. + libwebauthn::Transport::Ble => None, + // TODO: Some cards that support NFC but not CCID (SoloKey Solo 2 NFC) + // cause a framing error immediately after establishing a libwebauthn + // Channel, which causes the whole ceremony to abort without the user + // intending to. We need to determine some way of working around buggy + // security keys, while at the same time supporting actual NFC cards and CCID. + // Maybe we can defer sending "Connected" to the UI until a user presence + // or verification message is sent. + // This is the only switch: map to `Some(Transport::Nfc)` to enable NFC. + libwebauthn::Transport::Nfc => None, + }) + .collect() +} + fn complete_request( ctx: &Mutex>, response: Result, @@ -621,6 +644,7 @@ impl From for AuthenticatorResponse { #[cfg(test)] mod tests { use super::*; + use credentialsd_common::model::TransportRestartReason; use std::time::Duration; // Mock handlers for testing @@ -1502,6 +1526,60 @@ mod tests { )); } + #[test] + fn test_classifier_unrecoverable_transport_error() { + // The transport gave up; the remaining transports keep the ceremony going. + assert!(!is_ceremony_terminating( + &CredentialServiceError::UnrecoverableTransportError + )); + } + + /// A transport that gave up is forwarded without ending the ceremony, and + /// reaches the UI as `UsbRestarting { TransportUnavailable }`. + #[tokio::test] + async fn test_unrecoverable_transport_error_signals_transport_unavailable() { + let usb_handler = CancellationTrackingHandler::::new(); + let usb_ref = usb_handler.get_handler_ref(); + + let service = CredentialService::new(MockHybridHandler, MockNfcHandler, usb_handler); + let request = create_test_request().await; + let (tx, _rx) = oneshot::channel(); + let (id, token) = service.init_request(&request, tx).await.unwrap(); + let mut usb_stream = service.get_usb_credential().await; + + usb_ref.shift_state(UsbStateInternal::Failed( + CredentialServiceError::UnrecoverableTransportError, + )); + let update = usb_stream + .next() + .await + .expect("Failed state to be forwarded"); + assert!( + !token.is_cancelled(), + "ceremony must stay alive when one transport gives up" + ); + assert_eq!( + BackgroundEvent::from(&update), + BackgroundEvent::UsbRestarting { + reason: TransportRestartReason::TransportUnavailable + } + ); + + service.cancel_request(id).await; + } + + #[test] + fn test_supported_transports() { + use libwebauthn::Transport as Found; + + let found = [Found::Usb, Found::Ble, Found::Hybrid, Found::Nfc]; + // BLE has no handler and NFC discovery is disabled. + assert_eq!( + supported_transports(&found), + vec![Transport::Usb, Transport::HybridQr] + ); + } + /// After a non-terminating USB failure, the mock stream remains live and /// continues to emit subsequent states. #[tokio::test] @@ -1590,9 +1668,14 @@ mod tests { let (id, token) = service.init_request(&request, tx).await.unwrap(); let mut hybrid_stream = service.get_hybrid_credential().await; - hybrid_ref.shift_state(HybridStateInternal::Restarting); + hybrid_ref.shift_state(HybridStateInternal::Restarting( + TransportRestartReason::Interrupted, + )); assert!( - matches!(hybrid_stream.next().await, Some(HybridState::Restarting)), + matches!( + hybrid_stream.next().await, + Some(HybridState::Restarting(TransportRestartReason::Interrupted)) + ), "Restarting state must be forwarded to the UI stream" ); assert!( @@ -1616,9 +1699,14 @@ mod tests { let (id, token) = service.init_request(&request, tx).await.unwrap(); let mut usb_stream = service.get_usb_credential().await; - usb_ref.shift_state(UsbStateInternal::Restarting); + usb_ref.shift_state(UsbStateInternal::Restarting( + TransportRestartReason::Interrupted, + )); assert!( - matches!(usb_stream.next().await, Some(UsbState::Restarting)), + matches!( + usb_stream.next().await, + Some(UsbState::Restarting(TransportRestartReason::Interrupted)) + ), "Restarting state must be forwarded to the UI stream" ); assert!( @@ -1628,4 +1716,84 @@ mod tests { service.cancel_request(id).await; } + + /// A non-terminating USB failure after a user-input state (post-active) + /// must surface to the UI. + #[tokio::test] + async fn test_usb_post_active_error_surfaces() { + let usb_handler = CancellationTrackingHandler::::new(); + let usb_ref = usb_handler.get_handler_ref(); + + let service = CredentialService::new(MockHybridHandler, MockNfcHandler, usb_handler); + let request = create_test_request().await; + let (tx, _rx) = oneshot::channel(); + let (id, token) = service.init_request(&request, tx).await.unwrap(); + let mut usb_stream = service.get_usb_credential().await; + + // Activate: user-input required + usb_ref.shift_state(UsbStateInternal::NeedsUserPresence); + assert!(matches!( + usb_stream.next().await, + Some(UsbState::NeedsUserPresence) + )); + + // Non-terminating error after activation — must surface + usb_ref.shift_state(UsbStateInternal::Failed( + CredentialServiceError::AuthenticatorError, + )); + assert!( + matches!( + usb_stream.next().await, + Some(UsbState::Failed(CredentialServiceError::AuthenticatorError)) + ), + "post-active Failed must be forwarded to UI" + ); + assert!(!token.is_cancelled()); + + service.cancel_request(id).await; + } + + /// A non-terminating hybrid failure after Connecting (post-active, phone has + /// consumed the QR) must surface to the UI. + #[tokio::test] + async fn test_hybrid_post_active_error_surfaces() { + let hybrid_handler = CancellationTrackingHandler::::new(); + let hybrid_ref = hybrid_handler.get_handler_ref(); + + let service = CredentialService::new(hybrid_handler, MockNfcHandler, MockUsbHandler); + let request = create_test_request().await; + let (tx, _rx) = oneshot::channel(); + let (id, token) = service.init_request(&request, tx).await.unwrap(); + let mut hybrid_stream = service.get_hybrid_credential().await; + + hybrid_ref.shift_state(HybridStateInternal::Init("qr".to_string())); + assert!(matches!( + hybrid_stream.next().await, + Some(HybridState::Init(_)) + )); + + // Connecting = phone consumed the QR = active + hybrid_ref.shift_state(HybridStateInternal::Connecting); + assert!(matches!( + hybrid_stream.next().await, + Some(HybridState::Connecting) + )); + + // Post-active failure must surface + hybrid_ref.shift_state(HybridStateInternal::Failed( + CredentialServiceError::AuthenticatorError, + )); + assert!( + matches!( + hybrid_stream.next().await, + Some(HybridState::Failed( + CredentialServiceError::AuthenticatorError + )) + ), + "post-active Failed must be forwarded to UI" + ); + assert!(!token.is_cancelled()); + + service.cancel_request(id).await; + } } diff --git a/credentialsd/src/credential_service/nfc.rs b/credentialsd/src/credential_service/nfc.rs index f3ae8214..658c78cc 100644 --- a/credentialsd/src/credential_service/nfc.rs +++ b/credentialsd/src/credential_service/nfc.rs @@ -16,14 +16,15 @@ use tokio::sync::mpsc::{self, Receiver, Sender, WeakSender}; use tokio_util::sync::CancellationToken; use tracing::{debug, warn}; -use credentialsd_common::model::{BackgroundEvent, Credential, PinNotSetError}; +use credentialsd_common::model::{ + BackgroundEvent, Credential, PinNotSetError, TransportRestartReason, +}; use crate::model::{CredentialRequest, GetAssertionResponseInternal}; use super::{AuthenticatorResponse, CredentialResponse, CredentialServiceError}; pub(crate) trait NfcHandler { - #[expect(unused)] fn start( &self, request: &CredentialRequest, @@ -36,8 +37,6 @@ pub struct InProcessNfcHandler {} impl InProcessNfcHandler { async fn process_idle_waiting( - failures: &mut usize, - prev_nfc_state: &NfcStateInternal, cancellation: &CancellationToken, ) -> Result { let list_device_fut = libwebauthn::transport::nfc::get_nfc_device(); @@ -52,20 +51,12 @@ impl InProcessNfcHandler { Ok(state) } Err(err) => { - *failures += 1; - if *failures == 5 { - Err(CredentialServiceError::Internal(format!( - "Failed to list NFC authenticators: {:?}. Cancelling NFC state updates.", - err - ))) - } else { - tracing::warn!( - "Failed to list NFC authenticators: {:?}. Throttling NFC state updates", - err - ); - super::cancellable_sleep(Duration::from_secs(1), cancellation).await?; - Ok(prev_nfc_state.clone()) - } + // Throttle, then let the main loop count this as a pre-active failure. + tracing::warn!("Failed to list NFC authenticators: {:?}", err); + super::cancellable_sleep(Duration::from_secs(1), cancellation).await?; + Err(CredentialServiceError::Internal(format!( + "Failed to list NFC authenticators: {err:?}" + ))) } } } @@ -171,10 +162,12 @@ impl InProcessNfcHandler { cancellation: CancellationToken, ) -> Result<(), CredentialServiceError> { let mut state = NfcStateInternal::Idle; + let mut active = false; let (signal_tx, mut signal_rx) = mpsc::channel(256); let (cred_tx, mut cred_rx) = mpsc::channel(1); debug!("polling for NFC status"); - let mut failures = 0; + // Consecutive failures before the user interacted; reset when `active` turns on. + let mut pre_active_failures = 0; // act on current NFC NFC state, send state changes to the stream, and // loop until a credential or error is returned. loop { @@ -184,8 +177,7 @@ impl InProcessNfcHandler { let select_next_nfc_state_fut = async { match prev_nfc_state { NfcStateInternal::Idle | NfcStateInternal::Waiting => { - Self::process_idle_waiting(&mut failures, &prev_nfc_state, &cancellation) - .await + Self::process_idle_waiting(&cancellation).await } NfcStateInternal::Connected(ref device) => { let device = device.clone(); @@ -212,7 +204,7 @@ impl InProcessNfcHandler { // state, but we cover it here for exhaustiveness. NfcStateInternal::Completed(_) | NfcStateInternal::Failed(_) - | NfcStateInternal::Restarting => Ok(prev_nfc_state.clone()), + | NfcStateInternal::Restarting(_) => Ok(prev_nfc_state.clone()), } }; @@ -246,35 +238,102 @@ impl InProcessNfcHandler { std::mem::discriminant(new_state) != std::mem::discriminant(old_state) } }; - // Suppress forwarding a non-terminating Failed state to the UI directly: - // the Restarting state emitted below takes its place with cleaner semantics. - let is_non_terminating_failure = matches!( - &state, - NfcStateInternal::Failed(err) if !super::is_ceremony_terminating(err) - ); - if state_changed && !is_non_terminating_failure { - tracing::debug!("NFC current state: {state:?}"); - tx.send(state.clone()).await.map_err(|_| { - CredentialServiceError::Internal( - "NFC state channel receiver closed prematurely".to_string(), - ) - })?; + // Activate when libwebauthn signals it is waiting for user input. + // The flag is monotonic within a single ceremony attempt; it is reset + // to false when the transport restarts below. + match &state { + NfcStateInternal::NeedsPin { .. } + | NfcStateInternal::PinNotSet { .. } + | NfcStateInternal::NeedsUserVerification { .. } => { + active = true; + pre_active_failures = 0; + } + _ => {} } - // Check for terminal states AFTER sending match state { - NfcStateInternal::Completed(_) => break Ok(()), - NfcStateInternal::Failed(ref err) => { - if super::is_ceremony_terminating(err) { - break Err(err.clone()); + NfcStateInternal::Completed(_) => { + tracing::debug!("NFC current state: {state:?}"); + tx.send(state.clone()).await.map_err(|_| { + CredentialServiceError::Internal( + "NFC state channel receiver closed prematurely".to_string(), + ) + })?; + break Ok(()); + } + + // Catch terminating errors first. Doesn't matter if the user was already engaged + // or not. We have to terminate either way. + NfcStateInternal::Failed(ref err) if super::is_ceremony_terminating(err) => { + if state_changed { + tracing::debug!("NFC current state: {state:?}"); + tx.send(NfcStateInternal::Failed(err.clone())) + .await + .map_err(|_| { + CredentialServiceError::Internal( + "NFC state channel receiver closed prematurely".to_string(), + ) + })?; } - // Non-terminating: notify the UI that a restart is in progress so - // it can navigate back to the start page, then restart polling. + break Err(err.clone()); + } + + // Active non-terminating failure: the user was already engaged, surface + // the error via the Restarting signal so the UI navigates back to + // start_page. + // Reset active here only: the other Failed arms either break + // (so active is moot) or reach this arm with active already false. + NfcStateInternal::Failed(err) if active => { + let reason = match err { + CredentialServiceError::NoCredentials => { + TransportRestartReason::NoCredentials + } + CredentialServiceError::PinAttemptsExhausted => { + TransportRestartReason::PinAttemptsExhausted + } + _ => TransportRestartReason::Interrupted, + }; tracing::warn!(?err, "NFC authenticator error, restarting transport"); - let _ = tx.send(NfcStateInternal::Restarting).await; + let _ = tx.send(NfcStateInternal::Restarting(reason)).await; + active = false; state = NfcStateInternal::Idle; } - _ => {} + + // Pre-active non-terminating failure: the device errored before the user + // tapped it. Restart silently without any UI notification, but give up + // after MAX_PRE_ACTIVE_FAILURES in a row. + NfcStateInternal::Failed(err) => { + pre_active_failures += 1; + if pre_active_failures >= super::MAX_PRE_ACTIVE_FAILURES { + tracing::warn!( + ?err, + "NFC failed {pre_active_failures} times before user interaction, giving up" + ); + let err = CredentialServiceError::UnrecoverableTransportError; + tx.send(NfcStateInternal::Failed(err.clone())) + .await + .map_err(|_| { + CredentialServiceError::Internal( + "NFC state channel receiver closed prematurely".to_string(), + ) + })?; + break Err(err); + } + tracing::debug!(?err, "NFC pre-active error, restarting silently"); + state = NfcStateInternal::Idle; + } + + // All other state changes are sent, if they are 'new' + _ => { + if state_changed { + tracing::debug!("NFC current state: {state:?}"); + tx.send(state.clone()).await.map_err(|_| { + CredentialServiceError::Internal( + "NFC state channel receiver closed prematurely".to_string(), + ) + })?; + } + } } } } @@ -413,13 +472,11 @@ impl NfcHandler for InProcessNfcHandler { // this exists to prevent making NfcStateInternal type public to the whole crate. /// A message between NFC handler and credential service -#[expect(unused)] pub struct NfcEvent { pub(super) state: NfcStateInternal, } /// Used to share internal state between handler and credential service -#[expect(unused)] #[derive(Clone, Debug, Default)] pub(super) enum NfcStateInternal { /// Not polling for FIDO NFC device. @@ -461,7 +518,7 @@ pub(super) enum NfcStateInternal { /// The ceremony was interrupted by a non-terminating error and the transport /// is restarting. The UI should navigate back to the start page. - Restarting, + Restarting(TransportRestartReason), } /// Used to share public state between credential service and UI. @@ -507,7 +564,7 @@ pub enum NfcState { /// The ceremony was interrupted by a non-terminating error and the transport /// is restarting. The UI should navigate back to the start page. - Restarting, + Restarting(TransportRestartReason), } impl From for NfcState { @@ -530,7 +587,7 @@ impl From for NfcState { NfcState::NeedsUserVerification { attempts_left } } NfcStateInternal::Completed(_) => NfcState::Completed, - NfcStateInternal::Restarting => NfcState::Restarting, + NfcStateInternal::Restarting(reason) => NfcState::Restarting(reason), NfcStateInternal::SelectCredential { response, cred_tx } => { NfcState::SelectingCredential { creds: response @@ -599,20 +656,24 @@ impl From<&NfcState> for BackgroundEvent { NfcState::Failed(CredentialServiceError::AuthenticatorError) => { BackgroundEvent::ErrorAuthenticator } - NfcState::Failed(CredentialServiceError::NoCredentials) => { - BackgroundEvent::ErrorNoCredentials - } NfcState::Failed(CredentialServiceError::CredentialExcluded) => { BackgroundEvent::ErrorCredentialExcluded } - NfcState::Failed(CredentialServiceError::PinAttemptsExhausted) => { - BackgroundEvent::ErrorAuthenticator - } NfcState::Failed(CredentialServiceError::NonTerminatingCancellation) => { BackgroundEvent::ErrorCancelled } - NfcState::Failed(CredentialServiceError::Internal(_)) => BackgroundEvent::ErrorInternal, - NfcState::Restarting => BackgroundEvent::NfcRestarting, + NfcState::Failed(CredentialServiceError::Internal(_)) + | NfcState::Failed(CredentialServiceError::NoCredentials) + | NfcState::Failed(CredentialServiceError::PinAttemptsExhausted) => { + BackgroundEvent::ErrorInternal + } + // NFC stopped for this request; the UI stops offering it. + NfcState::Failed(CredentialServiceError::UnrecoverableTransportError) => { + BackgroundEvent::NfcRestarting { + reason: TransportRestartReason::TransportUnavailable, + } + } + NfcState::Restarting(reason) => BackgroundEvent::NfcRestarting { reason: *reason }, } } } diff --git a/credentialsd/src/credential_service/usb.rs b/credentialsd/src/credential_service/usb.rs index 12078337..f07d8141 100644 --- a/credentialsd/src/credential_service/usb.rs +++ b/credentialsd/src/credential_service/usb.rs @@ -21,7 +21,9 @@ use tokio::sync::{ use tokio_util::sync::CancellationToken; use tracing::{debug, warn}; -use credentialsd_common::model::{BackgroundEvent, Credential, PinNotSetError}; +use credentialsd_common::model::{ + BackgroundEvent, Credential, PinNotSetError, TransportRestartReason, +}; use crate::model::{CredentialRequest, GetAssertionResponseInternal}; @@ -40,8 +42,6 @@ pub struct InProcessUsbHandler {} impl InProcessUsbHandler { async fn process_idle_waiting( - failures: &mut usize, - prev_usb_state: &UsbStateInternal, cancellation: &CancellationToken, ) -> Result { let list_device_fut = libwebauthn::transport::hid::list_devices(); @@ -60,20 +60,12 @@ impl InProcessUsbHandler { } } Err(err) => { - *failures += 1; - if *failures == 5 { - Err(CredentialServiceError::Internal(format!( - "Failed to list USB authenticators: {:?}. Cancelling USB state updates.", - err - ))) - } else { - tracing::warn!( - "Failed to list USB authenticators: {:?}. Throttling USB state updates", - err - ); - super::cancellable_sleep(Duration::from_secs(1), cancellation).await?; - Ok(prev_usb_state.clone()) - } + // Throttle, then let the main loop count this as a pre-active failure. + tracing::warn!("Failed to list USB authenticators: {:?}", err); + super::cancellable_sleep(Duration::from_secs(1), cancellation).await?; + Err(CredentialServiceError::Internal(format!( + "Failed to list USB authenticators: {err:?}" + ))) } } } @@ -84,7 +76,7 @@ impl InProcessUsbHandler { ) -> Result { let expected_answers = hid_devices.len(); let (blinking_tx, mut blinking_rx) = - tokio::sync::mpsc::channel::>(expected_answers); + tokio::sync::mpsc::channel::, String>>(expected_answers); let mut channel_map = HashMap::new(); let (setup_tx, mut setup_rx) = tokio::sync::mpsc::channel::<(usize, HidDevice, HidChannelHandle)>(expected_answers); @@ -118,8 +110,7 @@ impl InProcessUsbHandler { err )), } - .inspect_err(|err| tracing::warn!(err)) - .unwrap_or_default(); // In case of error, we also send `None` + .inspect_err(|err| tracing::warn!(err)); if let Err(err) = tx.send(res).await { tracing::error!("Failed to send notification of wink response: {:?}", err,); } @@ -134,6 +125,9 @@ impl InProcessUsbHandler { tracing::info!("Waiting for user interaction"); drop(blinking_tx); let mut state = UsbStateInternal::Idle; + // Whether any device got as far as waiting for a touch. If none did, every + // device failed to open or blink, which counts as a pre-active failure. + let mut any_device_responded = false; loop { let maybe_msg_fut = blinking_rx.recv(); @@ -155,7 +149,7 @@ impl InProcessUsbHandler { break; }; match msg { - Some(idx) => { + Ok(Some(idx)) => { let (device, _handle) = channel_map.remove(&idx).unwrap(); tracing::info!("User selected device {device:?}."); for (_key, (device, handle)) in channel_map.into_iter() { @@ -165,11 +159,20 @@ impl InProcessUsbHandler { state = UsbStateInternal::Connected(Arc::new(AsyncMutex::new(device))); break; } - None => { + // Not selected (e.g. no touch before the blink timed out). + Ok(None) => { + any_device_responded = true; + continue; + } + // Already logged by the device task. + Err(_) => { continue; } } } + if matches!(state, UsbStateInternal::Idle) && !any_device_responded { + return Err(CredentialServiceError::AuthenticatorError); + } Ok(state) } @@ -275,10 +278,12 @@ impl InProcessUsbHandler { cancellation: CancellationToken, ) -> Result<(), CredentialServiceError> { let mut state = UsbStateInternal::Idle; + let mut active = false; let (signal_tx, mut signal_rx) = mpsc::channel(256); let (cred_tx, mut cred_rx) = mpsc::channel(1); debug!("polling for USB status"); - let mut failures = 0; + // Consecutive failures before the user interacted; reset when `active` turns on. + let mut pre_active_failures = 0; // act on current USB USB state, send state changes to the stream, and // loop until a credential or error is returned. loop { @@ -287,8 +292,7 @@ impl InProcessUsbHandler { let select_next_usb_state_fut = async { match prev_usb_state { UsbStateInternal::Idle | UsbStateInternal::Waiting => { - Self::process_idle_waiting(&mut failures, &prev_usb_state, &cancellation) - .await + Self::process_idle_waiting(&cancellation).await } UsbStateInternal::SelectingDevice(ref hid_devices) => { Self::process_selecting_device(hid_devices.as_slice(), &cancellation).await @@ -320,7 +324,7 @@ impl InProcessUsbHandler { // state, but we cover it here for exhaustiveness. UsbStateInternal::Completed(_) | UsbStateInternal::Failed(_) - | UsbStateInternal::Restarting => Ok(prev_usb_state.clone()), + | UsbStateInternal::Restarting(_) => Ok(prev_usb_state.clone()), } }; @@ -355,35 +359,103 @@ impl InProcessUsbHandler { std::mem::discriminant(new_state) != std::mem::discriminant(old_state) } }; - // Suppress forwarding a non-terminating Failed state to the UI directly: - // the Restarting state emitted below takes its place with cleaner semantics. - let is_non_terminating_failure = matches!( - &state, - UsbStateInternal::Failed(err) if !super::is_ceremony_terminating(err) - ); - if state_changed && !is_non_terminating_failure { - tracing::debug!("USB current state: {state:?}"); - tx.send(state.clone()).await.map_err(|_| { - CredentialServiceError::Internal( - "USB state channel receiver closed prematurely".to_string(), - ) - })?; + // Activate when libwebauthn signals it is waiting for user input. + // The flag is monotonic within a single ceremony attempt; it is reset + // to false when the transport restarts below. + match &state { + UsbStateInternal::NeedsPin { .. } + | UsbStateInternal::PinNotSet { .. } + | UsbStateInternal::NeedsUserVerification { .. } + | UsbStateInternal::NeedsUserPresence => { + active = true; + pre_active_failures = 0; + } + _ => {} } - // Check for terminal states AFTER sending match state { - UsbStateInternal::Completed(_) => break Ok(()), - UsbStateInternal::Failed(ref err) => { - if super::is_ceremony_terminating(err) { - break Err(err.clone()); + UsbStateInternal::Completed(_) => { + tracing::debug!("USB current state: {state:?}"); + tx.send(state.clone()).await.map_err(|_| { + CredentialServiceError::Internal( + "USB state channel receiver closed prematurely".to_string(), + ) + })?; + break Ok(()); + } + + // Catch terminating errors first. Doesn't matter if the user was already engaged + // or not. We have to terminate either way. + UsbStateInternal::Failed(ref err) if super::is_ceremony_terminating(err) => { + if state_changed { + tracing::debug!("USB current state: {state:?}"); + tx.send(UsbStateInternal::Failed(err.clone())) + .await + .map_err(|_| { + CredentialServiceError::Internal( + "USB state channel receiver closed prematurely".to_string(), + ) + })?; } - // Non-terminating: notify the UI that a restart is in progress so - // it can navigate back to the start page, then restart polling. + break Err(err.clone()); + } + + // Active non-terminating failure: the user was already engaged, surface + // the error via the Restarting signal so the UI navigates back to + // start_page. + // Reset active here only: the other Failed arms either break + // (so active is moot) or reach this arm with active already false. + UsbStateInternal::Failed(err) if active => { + let reason = match err { + CredentialServiceError::NoCredentials => { + TransportRestartReason::NoCredentials + } + CredentialServiceError::PinAttemptsExhausted => { + TransportRestartReason::PinAttemptsExhausted + } + _ => TransportRestartReason::Interrupted, + }; tracing::warn!(?err, "USB authenticator error, restarting transport"); - let _ = tx.send(UsbStateInternal::Restarting).await; + let _ = tx.send(UsbStateInternal::Restarting(reason)).await; + active = false; state = UsbStateInternal::Idle; } - _ => {} + + // Pre-active non-terminating failure: the device errored before the user + // touched it. Restart silently without any UI notification, but give up + // after MAX_PRE_ACTIVE_FAILURES in a row. + UsbStateInternal::Failed(err) => { + pre_active_failures += 1; + if pre_active_failures >= super::MAX_PRE_ACTIVE_FAILURES { + tracing::warn!( + ?err, + "USB failed {pre_active_failures} times before user interaction, giving up" + ); + let err = CredentialServiceError::UnrecoverableTransportError; + tx.send(UsbStateInternal::Failed(err.clone())) + .await + .map_err(|_| { + CredentialServiceError::Internal( + "USB state channel receiver closed prematurely".to_string(), + ) + })?; + break Err(err); + } + tracing::debug!(?err, "USB pre-active error, restarting silently"); + state = UsbStateInternal::Idle; + } + + // All other state changes are sent, if they are 'new' + _ => { + if state_changed { + tracing::debug!("USB current state: {state:?}"); + tx.send(state.clone()).await.map_err(|_| { + CredentialServiceError::Internal( + "USB state channel receiver closed prematurely".to_string(), + ) + })?; + } + } } } } @@ -572,7 +644,7 @@ pub(super) enum UsbStateInternal { /// The ceremony was interrupted by a non-terminating error and the transport /// is restarting. The UI should navigate back to the start page. - Restarting, + Restarting(TransportRestartReason), } /// Used to share public state between credential service and UI. @@ -627,7 +699,7 @@ pub enum UsbState { /// The ceremony was interrupted by a non-terminating error and the transport /// is restarting. The UI should navigate back to the start page. - Restarting, + Restarting(TransportRestartReason), } impl From for UsbState { @@ -685,7 +757,7 @@ impl From for UsbState { } } UsbStateInternal::Failed(err) => UsbState::Failed(err), - UsbStateInternal::Restarting => UsbState::Restarting, + UsbStateInternal::Restarting(reason) => UsbState::Restarting(reason), } } } @@ -723,20 +795,24 @@ impl From<&UsbState> for BackgroundEvent { UsbState::Failed(CredentialServiceError::AuthenticatorError) => { BackgroundEvent::ErrorAuthenticator } - UsbState::Failed(CredentialServiceError::NoCredentials) => { - BackgroundEvent::ErrorNoCredentials - } UsbState::Failed(CredentialServiceError::CredentialExcluded) => { BackgroundEvent::ErrorCredentialExcluded } - UsbState::Failed(CredentialServiceError::PinAttemptsExhausted) => { - BackgroundEvent::ErrorAuthenticator - } UsbState::Failed(CredentialServiceError::NonTerminatingCancellation) => { BackgroundEvent::ErrorCancelled } - UsbState::Failed(CredentialServiceError::Internal(_)) => BackgroundEvent::ErrorInternal, - UsbState::Restarting => BackgroundEvent::UsbRestarting, + UsbState::Failed(CredentialServiceError::Internal(_)) + | UsbState::Failed(CredentialServiceError::NoCredentials) + | UsbState::Failed(CredentialServiceError::PinAttemptsExhausted) => { + BackgroundEvent::ErrorInternal + } + // USB stopped for this request; the UI stops offering it. + UsbState::Failed(CredentialServiceError::UnrecoverableTransportError) => { + BackgroundEvent::UsbRestarting { + reason: TransportRestartReason::TransportUnavailable, + } + } + UsbState::Restarting(reason) => BackgroundEvent::UsbRestarting { reason: *reason }, } } } diff --git a/credentialsd/src/dbus/flow_control.rs b/credentialsd/src/dbus/flow_control.rs index cbdc9c5e..62590202 100644 --- a/credentialsd/src/dbus/flow_control.rs +++ b/credentialsd/src/dbus/flow_control.rs @@ -99,12 +99,7 @@ async fn handle Result, ()> { - Ok(Vec::new()) + async fn detect_transports(&self) -> Vec { + Vec::new() } async fn start_discovery( @@ -414,7 +409,7 @@ mod tests { _parent_window: Option, _origin: String, _type: Operation, - _devices: Vec, + _transports: Vec, _app_id: String, _app_pid: u32, _options: PortalBackendOptions, diff --git a/credentialsd/src/dbus/ui_control.rs b/credentialsd/src/dbus/ui_control.rs index 921ef61b..57f6c242 100644 --- a/credentialsd/src/dbus/ui_control.rs +++ b/credentialsd/src/dbus/ui_control.rs @@ -18,15 +18,14 @@ use zbus::{ use credentialsd_common::model::{ BACKGROUND_EVENT_ERROR_AUTHENTICATOR, BACKGROUND_EVENT_ERROR_CANCELLED, BACKGROUND_EVENT_ERROR_CREDENTIAL_EXCLUDED, BACKGROUND_EVENT_ERROR_INTERNAL, - BACKGROUND_EVENT_ERROR_NO_CREDENTIALS, BACKGROUND_EVENT_ERROR_PIN_ATTEMPTS_EXHAUSTED, - BACKGROUND_EVENT_ERROR_PIN_NOT_SET, BACKGROUND_EVENT_ERROR_TIMED_OUT, BackgroundEvent, - ClientPinEnteredOptions, Credential, CredentialSelectedOptions, Device, - DiscoveryRequestedOptions, NotifyHybridConnectedOptions, NotifyHybridConnectingOptions, - NotifyHybridRestartingOptions, NotifyHybridStartedOptions, NotifyNeedsPinOptions, - NotifyNeedsUserPresenceOptions, NotifyNeedsUserVerificationOptions, NotifyNfcConnectedOptions, - NotifyNfcRestartingOptions, NotifyPinNotSetOptions, NotifySelectingCredentialOptions, - NotifyUsbConnectedOptions, NotifyUsbRestartingOptions, Operation, PinNotSetError, - PortalBackendOptions, SetDevicePinOptions, UserInteractedEvent, WindowHandle, + BACKGROUND_EVENT_ERROR_TIMED_OUT, BackgroundEvent, ClientPinEnteredOptions, Credential, + CredentialSelectedOptions, DiscoveryRequestedOptions, NotifyHybridConnectedOptions, + NotifyHybridConnectingOptions, NotifyHybridRestartingOptions, NotifyHybridStartedOptions, + NotifyNeedsPinOptions, NotifyNeedsUserPresenceOptions, NotifyNeedsUserVerificationOptions, + NotifyNfcConnectedOptions, NotifyNfcRestartingOptions, NotifyPinNotSetOptions, + NotifySelectingCredentialOptions, NotifyUsbConnectedOptions, NotifyUsbRestartingOptions, + Operation, PinNotSetError, PortalBackendOptions, SetDevicePinOptions, Transport, + UserInteractedEvent, WindowHandle, }; /// Used by the credential service to control the UI. @@ -39,7 +38,7 @@ pub trait UiController { parent_window: Option, origin: String, r#type: Operation, - devices: Vec, + transports: Vec, app_id: String, app_pid: u32, options: PortalBackendOptions, @@ -61,7 +60,7 @@ trait UiControlService { parent_window: Optional, origin: String, r#type: Operation, - devices: Vec, + transports: Vec, app_id: String, app_pid: u32, options: PortalBackendOptions, @@ -153,6 +152,7 @@ trait UiControlService { async fn notify_hybrid_restarting( &self, session_handle: ObjectPath<'_>, + reason: u8, _options: NotifyHybridRestartingOptions, ) -> fdo::Result<()>; @@ -160,6 +160,7 @@ trait UiControlService { async fn notify_usb_restarting( &self, session_handle: ObjectPath<'_>, + reason: u8, _options: NotifyUsbRestartingOptions, ) -> fdo::Result<()>; @@ -167,6 +168,7 @@ trait UiControlService { async fn notify_nfc_restarting( &self, session_handle: ObjectPath<'_>, + reason: u8, _options: NotifyNfcRestartingOptions, ) -> fdo::Result<()>; @@ -321,26 +323,29 @@ impl Ceremony { ) .await } - BackgroundEvent::HybridRestarting => { + BackgroundEvent::HybridRestarting { reason } => { self.proxy .notify_hybrid_restarting( self.session_handle.as_ref(), + u8::from(reason), NotifyHybridRestartingOptions {}, ) .await } - BackgroundEvent::UsbRestarting => { + BackgroundEvent::UsbRestarting { reason } => { self.proxy .notify_usb_restarting( self.session_handle.as_ref(), + u8::from(reason), NotifyUsbRestartingOptions {}, ) .await } - BackgroundEvent::NfcRestarting => { + BackgroundEvent::NfcRestarting { reason } => { self.proxy .notify_nfc_restarting( self.session_handle.as_ref(), + u8::from(reason), NotifyNfcRestartingOptions {}, ) .await @@ -370,30 +375,12 @@ impl Ceremony { .notify_error_occurred(self.session_handle.as_ref(), error) .await } - BackgroundEvent::ErrorNoCredentials => { - let error = BACKGROUND_EVENT_ERROR_NO_CREDENTIALS; - self.proxy - .notify_error_occurred(self.session_handle.as_ref(), error) - .await - } BackgroundEvent::ErrorCredentialExcluded => { let error = BACKGROUND_EVENT_ERROR_CREDENTIAL_EXCLUDED; self.proxy .notify_error_occurred(self.session_handle.as_ref(), error) .await } - BackgroundEvent::ErrorPinAttemptsExhausted => { - let error = BACKGROUND_EVENT_ERROR_PIN_ATTEMPTS_EXHAUSTED; - self.proxy - .notify_error_occurred(self.session_handle.as_ref(), error) - .await - } - BackgroundEvent::ErrorPinNotSet => { - let error = BACKGROUND_EVENT_ERROR_PIN_NOT_SET; - self.proxy - .notify_error_occurred(self.session_handle.as_ref(), error) - .await - } BackgroundEvent::CeremonyCompleted => { self.proxy .notify_ceremony_completed(self.session_handle.as_ref()) @@ -438,7 +425,7 @@ impl UiController for UiControlServiceClient { parent_window: Option, origin: String, r#type: Operation, - devices: Vec, + transports: Vec, app_id: String, app_pid: u32, options: PortalBackendOptions, @@ -479,7 +466,7 @@ impl UiController for UiControlServiceClient { parent_window.into(), origin, r#type, - devices, + transports, app_id, app_pid, options,