diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index e82b490f81..8ac65d257c 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -174,21 +174,20 @@ jobs: with: path: | apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* ${{ env.RUNNER_TEMP }}/.cargo/registry ${{ env.RUNNER_TEMP }}/.cargo/git key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native + working-directory: apps/desktop/desktop_native/napi env: PKG_CONFIG_ALLOW_CROSS: true PKG_CONFIG_ALL_STATIC: true TARGET: musl run: | rustup target add x86_64-unknown-linux-musl - node build.js cross-platform + npm run build:cross-platform - name: Build application run: npm run dist:lin @@ -302,15 +301,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build & Sign (dev) env: @@ -587,15 +584,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build application (dev) run: npm run build @@ -753,15 +748,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -972,15 +965,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -1177,15 +1168,13 @@ jobs: uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* + path: apps/desktop/desktop_native/napi/*.node key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + working-directory: apps/desktop/desktop_native/napi + run: npm run build:cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' diff --git a/apps/browser/package.json b/apps/browser/package.json index c5332a0801..b76a1e8412 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2024.8.2", + "version": "2024.9.0", "scripts": { "build": "cross-env MANIFEST_VERSION=3 webpack", "build:mv2": "webpack", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 8f7a71d086..0090391fdb 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "لا توجد كلمات مرور للعرض." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "إزالة" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 788a7f05d0..341406772e 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Sadalanacaq heç bir parol yoxdur." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Çıxart" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Müəssisə siyasət tələbləri bu ayara tətbiq edildi" }, - "additionalContentAvailable": { - "message": "Əlavə məzmun əlçatandır" + "showCharacterCount": { + "message": "Xarakter sayını göstər" + }, + "hideCharacterCount": { + "message": "Xarakter sayını gizlət" }, "itemsInTrash": { "message": "Tullantıdakı elementlər" diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index d38d44e1d9..1a00659bbf 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "У спісе адсутнічаюць паролі." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Выдаліць" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 5ac827b964..bfc4fc04a1 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Няма пароли за показване." }, + "clearHistory": { + "message": "Изчистване на историята" + }, + "noPasswordsToShow": { + "message": "Няма пароли за показване" + }, + "noRecentlyGeneratedPassword": { + "message": "Скоро не сте генерирали пароли" + }, "remove": { "message": "Премахване" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Изискванията на политиката за големи компании бяха приложени към тази настройка" }, - "additionalContentAvailable": { - "message": "Има налично допълнително съдържание" + "showCharacterCount": { + "message": "Показване на броя знаци" + }, + "hideCharacterCount": { + "message": "Скриване на броя знаци" }, "itemsInTrash": { "message": "Елементи в кошчето" diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index ec97da7dac..fd084ed359 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "তালিকার জন্য কোনও পাসওয়ার্ড নেই।" }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "সরান" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 1e7feb5e96..b80293fd04 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 50e5f6673e..2b7bc2198d 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "No hi ha cap contrasenya a llistar." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Suprimeix" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 6de62c8135..c5d80fc8b3 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Nejsou k dispozici žádná hesla." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Odebrat" }, @@ -4125,7 +4134,7 @@ "message": "Použijte zaškrtávací políčka, pokud chcete automaticky vyplnit zaškrtávací políčko formuláře (např. pro zapamatování e-mailu)" }, "linkedHelpText": { - "message": "Použijte propojené pole, pokud máte problémy s automatickým vyplňováním na konkrétní webové stránce" + "message": "Použijte propojené pole, pokud máte problémy s automatickým vyplňováním na konkrétní webové stránce." }, "linkedLabelHelpText": { "message": "Zadejte ID pole z HTML, název, popisek nebo zástupný znak pole." @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Na toto nastavení byly uplatněny požadavky podnikových zásad" }, - "additionalContentAvailable": { - "message": "Je k dispozici další obsah" + "showCharacterCount": { + "message": "Zobrazit počet znaků" + }, + "hideCharacterCount": { + "message": "Skrýt počet znaků" }, "itemsInTrash": { "message": "Položky v koši" diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 65e6251799..128894e754 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Does dim cyfrineiriau i'w rhestru." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Tynnu" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index b6d41eaf97..301e987dfd 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Der er ingen kodeord at vise." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Fjern" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Virksomhedspolitikkrav er anvendt på denne indstilling" }, - "additionalContentAvailable": { - "message": "Yderligere indhold er tilgængeligt" + "showCharacterCount": { + "message": "Vis tegnantal" + }, + "hideCharacterCount": { + "message": "Skjul tegnantal" }, "itemsInTrash": { "message": "Emner i papirkurv" diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 3101829840..8abbdae03f 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Keine Einträge zum Anzeigen vorhanden." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Entfernen" }, @@ -3478,7 +3487,7 @@ "message": "Überprüfung durch die initiierende Website erforderlich. Diese Funktion ist noch nicht für Konten ohne Master-Passwort implementiert." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "Mit Passkey anmelden?" }, "passkeyAlreadyExists": { "message": "Für diese Anwendung existiert bereits ein Passkey." @@ -3490,7 +3499,7 @@ "message": "Du hast keinen passenden Zugangsdaten für diese Website." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "Keine passenden Zugangsdaten für diese Seite" }, "confirm": { "message": "Bestätigen" @@ -3502,10 +3511,10 @@ "message": "Passkey als neue Zugangsdaten speichern" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "Wähle die Zugangsdaten aus, in die dieser Passkey gespeichert werden soll" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "Wähle einen Passkey zum Anmelden" }, "passkeyItem": { "message": "Passkey-Eintrag" @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Unternehmens-Richtlinienanforderungen wurden auf diese Einstellung angewandt" }, - "additionalContentAvailable": { - "message": "Zusätzlicher Inhalt ist verfügbar" + "showCharacterCount": { + "message": "Zeichenanzahl anzeigen" + }, + "hideCharacterCount": { + "message": "Zeichenanzahl ausblenden" }, "itemsInTrash": { "message": "Einträge im Papierkorb" diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 26dabf51dc..5b1ee6011b 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Δεν υπάρχουν κωδικοί στη λίστα." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Αφαίρεση" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Οι απαιτήσεις της πολιτικής για επιχειρήσεις έχουν εφαρμοστεί σε αυτήν τη ρύθμιση" }, - "additionalContentAvailable": { - "message": "Πρόσθετο περιεχόμενο είναι διαθέσιμο" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Αντικείμενα στον κάδο" diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index e19d5cdd6b..2e82469b47 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1799,6 +1799,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index b4fb303e6b..153c3987da 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in the bin" diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 4eb6149bbf..9c0200ab42 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in bin" diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 74a4809f98..fc98d5e277 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "No hay contraseñas que listar." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Eliminar" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Elementos en la papelera" diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 7d55eec4fd..43d894be6c 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -14,7 +14,7 @@ "message": "Logi oma olemasolevasse kontosse sisse või loo uus konto." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Kutse vastu võetud" }, "createAccount": { "message": "Konto loomine" @@ -69,10 +69,10 @@ "message": "Ülemparooli vihje (ei ole kohustuslik)" }, "joinOrganization": { - "message": "Join organization" + "message": "Liitu organisatsiooniga" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Lõpeta organisatsiooniga liitumine määrates ülemparool." }, "tab": { "message": "Kaart" @@ -114,19 +114,19 @@ "message": "Kopeeri turvakood" }, "copyName": { - "message": "Copy name" + "message": "Kopeeri nimi" }, "copyCompany": { - "message": "Copy company" + "message": "Kopeeri firma nimi" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopeeri isikukood" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopeeri passi number" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopeeri litsentsi number" }, "autoFill": { "message": "Automaatne täitmine" @@ -225,7 +225,7 @@ "message": "Mine edasi veebilaienduste poodi?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "Aita meil jõuda rohkemate inimesteni. Külasta enda laienduste veebipoodi ja jäta sinna positiivne hinnang." }, "changeMasterPasswordOnWebConfirmation": { "message": "Ülemparooli saab muuta Bitwardeni veebirakenduses." @@ -251,7 +251,7 @@ "message": "Rakenduse info" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Rohkem Bitwardeni kohta" }, "continueToBitwardenDotCom": { "message": "Mine edasi bitwarden.com-i?" @@ -263,25 +263,25 @@ "message": "Bitwarden Authenticator" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" + "message": "Bitwardeni Autentiteerijaga saad sa hoiustada autentiteerimise võtmeid ja luua TOTP koode kaheastmeliseks kinnitamiseks. Uuri lähemalt veebilehelt bitwarden.com" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Hoiusta, halda ja jaga turvaliselt arendajate saladusi läbi Bitwarden Secrets Manageri. Uuri lähemalt veebilehelt bitwarden.com." }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "Loo sujuv ja turvaline kogemus sisselogimisel Passwordless.dev-iga ja ilma traditsiooniliste paroolideta. Uuri lähemalt veebilehelt bitwarden.com." }, "freeBitwardenFamilies": { "message": "Tasuta Bitwarden Peredele" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Sul on võimalik saada endale tasuta Bitwarden Families plaan. Lunasta see pakkumine meie veebirakenduses." }, "version": { "message": "Versioon" @@ -302,22 +302,22 @@ "message": "Muuda kausta" }, "newFolder": { - "message": "New folder" + "message": "Uus kaust" }, "folderName": { - "message": "Folder name" + "message": "Kausta nimi" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "Kausta teise kasuta panemiseks lisa sihtkausta nimi, millele järgneb \"/\". Näiteks: Sotsiaalmeedia/Foorumid" }, "noFoldersAdded": { - "message": "No folders added" + "message": "Ei lisanud ühtegi kausta" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "Loo kaustasid, et oma hoidla kirjeid organiseerida" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "Kas sa oled kindel, et soovid selle kausta jäädavalt kustutada?" }, "deleteFolder": { "message": "Kustuta Kaust" @@ -400,11 +400,11 @@ "description": "deprecated. Use specialCharactersLabel instead." }, "include": { - "message": "Include", + "message": "Kasuta", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Kasuta trükitähti", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -412,7 +412,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Kasuta kirjatähti", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -420,7 +420,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Kasuta numbreid", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -428,7 +428,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Kasuta sümboleid", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -459,7 +459,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Väldi raskesti eristatavaid tähti ja sümboleid", "description": "Label for the avoid ambiguous characters checkbox." }, "searchVault": { @@ -484,7 +484,7 @@ "message": "Parool" }, "totp": { - "message": "Authenticator secret" + "message": "Salajane autentikaatori võti" }, "passphrase": { "message": "Paroolifraas" @@ -538,13 +538,13 @@ "message": "Muu" }, "unlockMethods": { - "message": "Unlock options" + "message": "Avamise valikud" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Hoidla ajalõpu tegevuse muutmiseks vali esmalt lahtilukustamise meetod." }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "Määra avamise meetod seadetes" }, "sessionTimeoutHeader": { "message": "Sessiooni ajalõpp" @@ -684,10 +684,10 @@ "message": "Konto on loodud! Võid nüüd sisse logida." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Uus konto loodud!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Olete sisse logitud!" }, "youSuccessfullyLoggedIn": { "message": "Sisselogimine õnnestus" @@ -702,7 +702,7 @@ "message": "Nõutav on kinnituskood." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "Autentimine tühistati või kestis liiga kaua aega. Palun proovi uuesti." }, "invalidVerificationCode": { "message": "Vale kinnituskood" @@ -724,25 +724,25 @@ "message": "Ei õnnestunud skännida sellelt lehelt QR-kood" }, "totpCaptureSuccess": { - "message": "Authenticator key added" + "message": "Autentimise võti on lisatud" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "Skänneeri see QR-kood läbi autentikaatori" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Muuda 2-astmeline kinnitamine sujuvaks" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden saab hoiustada ja täita 2-astmelise kinnitamise koode. Kopeeri ja kleebi võti siia." }, "totpHelperWithCapture": { - "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + "message": "Bitwarden saab hoiustada ja täita 2-astmelise kinnitamise koode. Vajuta kaamera ikoonile, et teha ekraanipilt autentiteerimise QR koodist või kopeeri ja kleebi võti siia." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Uuri lähemalt autentikaatorite kohta" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "Kopeeri autentiteerimise võti (TOTP)" }, "loggedOut": { "message": "Välja logitud" @@ -754,16 +754,16 @@ "message": "Sessioon on aegunud." }, "logIn": { - "message": "Log in" + "message": "Logi sisse" }, "restartRegistration": { - "message": "Restart registration" + "message": "Alusta registreerimist uuesti" }, "expiredLink": { - "message": "Expired link" + "message": "Aegunud link" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Palun alusta registreerimist uuesti või proovi sisse logida." }, "youMayAlreadyHaveAnAccount": { "message": "You may already have an account" @@ -1469,7 +1469,7 @@ "message": "Boolean" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Märkeruut" }, "cfTypeLinked": { "message": "Ühenduses", @@ -1492,7 +1492,7 @@ "message": "Kuvab iga kirje kõrval lehekülje ikooni." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "Näita väikest tuttavat ikooni iga kirje kõrval. Kehtib ka sisselogitud kontodele." }, "enableBadgeCounter": { "message": "Kuva kirjete arvu" @@ -1654,7 +1654,7 @@ "message": "Identiteet" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "Uus $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1690,7 +1690,7 @@ "message": "Kogumikud" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ kogumikku", "placeholders": { "count": { "content": "$1", @@ -1743,7 +1743,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "Serveri nimi [base domain] (soovitatav)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Puuduvad paroolid, mida kuvada." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Eemalda" }, @@ -1864,7 +1873,7 @@ "message": "Vale PIN kood." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Liiga palju ebaõnnestunud katseid. Login välja." }, "unlockWithBiometrics": { "message": "Ava biomeetriaga" @@ -1891,26 +1900,26 @@ "message": "Organisatsiooni seaded mõjutavad parooli genereerija sätteid." }, "passwordGenerator": { - "message": "Password generator" + "message": "Parooli genereerija" }, "usernameGenerator": { - "message": "Username generator" + "message": "Kasutajanime genereerija" }, "useThisPassword": { - "message": "Use this password" + "message": "Kasuta seda parooli" }, "useThisUsername": { - "message": "Use this username" + "message": "Kasuta seda kasutajanime" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "Turvaline parool loodud! Ära unusta uuendata seda ka veebisaidil." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Kasuta generaatorit", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "useGeneratorHelpTextPartTwo": { - "message": "to create a strong unique password", + "message": "et luua tugev ja ainulaadne parool", "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultTimeoutAction": { @@ -1943,7 +1952,7 @@ "message": "Kirje on taastatud" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "On juba konto?" }, "vaultTimeoutLogOutConfirmation": { "message": "Väljalogimine eemaldab hoidlale ligipääsu ning nõuab pärast ajalõpu perioodi uuesti autentimist. Oled kindel, et soovid seda valikut kasutada?" @@ -1955,7 +1964,7 @@ "message": "Täida ja salvesta" }, "fillAndSave": { - "message": "Fill and save" + "message": "Täida ja salvesta" }, "autoFillSuccessAndSavedUri": { "message": "Kirje täideti ja URI salvestati" @@ -2036,19 +2045,19 @@ "message": "Uus ülemparool ei vasta eeskirjades väljatoodud tingimustele." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Soovin saada nõuandeid, uudiseid ja pakkumisi Bitwardenilt oma postkasti." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Lõpeta tellimus" }, "atAnyTime": { - "message": "at any time." + "message": "iga hetk." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Jätkates nõustud sa" }, "and": { - "message": "and" + "message": "ja" }, "acceptPolicies": { "message": "Märkeruudu markeerimisel nõustud järgnevaga:" @@ -2069,10 +2078,10 @@ "message": "Ok" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Juurdepääsukoodi Värskendamine Ebaõnnestus" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Ei leidnud värskendamise koodi või API võtit. Palun proovi logida välja ja uuesti sisse." }, "desktopSyncVerificationTitle": { "message": "Töölaua sünkroonimise kinnitamine" @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 8c509fd7ed..175c5d305d 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Ez dago erakusteko pasahitzik." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Ezabatu" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 0b2a4bf9a3..45a01a0946 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "حذف" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 110264c6a0..07281c5df4 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Ei näytettäviä salasanoja." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Poista" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Yrityskäytännön vaatimuksia on sovellettu tähän asetukseen" }, - "additionalContentAvailable": { - "message": "Lisää sisältöä on saatavilla" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Roskakorin kohteet" diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index d31c41f228..cc1fb49eb9 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Walang mga password na i-list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Alisin" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index e2b04da786..66cd4bf6f8 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Aucun mot de passe à afficher." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Supprimer" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Les exigences de la politique d'entreprise ont été appliquées à ce paramètre" }, - "additionalContentAvailable": { - "message": "Du contenu supplémentaire est disponible" + "showCharacterCount": { + "message": "Afficher le nombre de caractères" + }, + "hideCharacterCount": { + "message": "Cacher le nombre de caractères" }, "itemsInTrash": { "message": "Éléments dans la corbeille" diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 420dd0b789..5091559607 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Non hai contrasinais que listar." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Eliminar" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 658adcc605..89f6719289 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "אין סיסמאות להצגה ברשימה." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "הסר" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 5368a992c4..b9bfa5b595 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "सूची के लिए कोई पासवर्ड नहीं हैं।" }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "हटाएं" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "इस सेटिंग पर एंटरप्राइज़ नीति आवश्यकताएँ लागू की गई हैं" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 2fd8b3f24d..0c6ca6bef7 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Nema lozinki na popisu." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Ukloni" }, @@ -3478,7 +3487,7 @@ "message": "Ishodišna stranica zahtijeva verifikaciju. Ova značajka još nije implementirana za račune bez glavne lozinke." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "Prijava pristupnim ključem?" }, "passkeyAlreadyExists": { "message": "Za ovu aplikaciju već postoji pristupni ključ." @@ -3490,7 +3499,7 @@ "message": "Nema odgovarajuće prijavu za ovu stranicu." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "Nema prijava za ovu web stranicu" }, "confirm": { "message": "Autoriziraj" @@ -3502,10 +3511,10 @@ "message": "Spremi pristupni ključ kao novu prijavu" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "Odaberi za koju prijavu želiš spremiti ovaj pristupni ključ" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "Odaberi pristupni ključ za prijavu" }, "passkeyItem": { "message": "Stavka pristupnog ključa" @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Pravila tvrtke primijenjena su na ovu postavku" }, - "additionalContentAvailable": { - "message": "Dostupan je dodatni sadržaj" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Stavke u smeću" diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index e304fe3936..17c6b7e5e4 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Nincsenek listázható jelszavak." }, + "clearHistory": { + "message": "Előzmények törlése" + }, + "noPasswordsToShow": { + "message": "Nincsenek megjeleníthető jelszavak." + }, + "noRecentlyGeneratedPassword": { + "message": "Mostanában nem lett jelszó generálva." + }, "remove": { "message": "Eltávolítás" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Erre a beállításra a vállalkozás rendszabály követelmények lettek alkalmazva." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Karakterszámláló megjelenítése" + }, + "hideCharacterCount": { + "message": "Karakterszámláló elrejtése" }, "itemsInTrash": { "message": "elem van a lomtárban." diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 2a3e44a942..44dff3c818 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Tidak ada sandi yang dapat dicantumkan." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Hapus" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index fec8c6bc45..3bccac4121 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Non ci sono password da mostrare." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Rimuovi" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "I requisiti della policy aziendale sono stati applicati a questa impostazione" }, - "additionalContentAvailable": { - "message": "Sono disponibili ulteriori contenuti" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Elementi nel cestino" diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 631cd6a936..40d8cef50c 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "表示するパスワードがありません" }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "削除" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "エンタープライズポリシー要件がこの設定に適用されました" }, - "additionalContentAvailable": { - "message": "追加コンテンツが利用可能です" + "showCharacterCount": { + "message": "文字数を表示" + }, + "hideCharacterCount": { + "message": "文字数を隠す" }, "itemsInTrash": { "message": "ゴミ箱にあるアイテム" diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 6c275ffb16..f277d76bfa 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 0aa83ad1d7..81f6bacb96 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index db1a9ee368..e8d36e6fff 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "ಪಟ್ಟಿ ಮಾಡಲು ಯಾವುದೇ ಪಾಸ್ವರ್ಡ್ಗಳು ಇಲ್ಲ." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "ತೆಗೆ" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 67284fce5e..74c9103943 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "비밀번호가 없습니다." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "제거" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 29e92c4097..ebcf61ffd6 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Slaptažodžių sąraše nėra." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Pašalinti" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index cd12134261..0a847e86f6 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Nav paroļu, ko parādīt." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Noņemt" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Šim iestatījumam tika piemērotas uzņēmējdarbības nosacījumu prasības" }, - "additionalContentAvailable": { - "message": "Ir pieejams papildu saturs" + "showCharacterCount": { + "message": "Rādīt rakstzīmju skaitu" + }, + "hideCharacterCount": { + "message": "Paslēpt rakstzīmju skaitu" }, "itemsInTrash": { "message": "Vienumi atkritnē" diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index bf53c4b52d..8a27d914ea 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "പ്രദർശിപ്പിക്കാൻ പാസ്സ്‌വേഡുകൾ ഒന്നും ഇല്ല." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "നീക്കുക" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 15116fe3d4..890fc14fdd 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 0aa83ad1d7..81f6bacb96 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index ffd1a4e7a1..a5d503d398 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Det er ingen passord å liste opp." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Fjern" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 0aa83ad1d7..81f6bacb96 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 78a817da8c..791fd7b237 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Er zijn geen wachtwoorden om weer te geven." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Verwijderen" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Bedrijfsbeleidseisen zijn op deze instelling toegepast" }, - "additionalContentAvailable": { - "message": "Extra inhoud beschikbaar" + "showCharacterCount": { + "message": "Aantal tekens weergeven" + }, + "hideCharacterCount": { + "message": "Aantal tekens verbergen" }, "itemsInTrash": { "message": "Items in prullenbak" diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 0aa83ad1d7..81f6bacb96 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 0aa83ad1d7..81f6bacb96 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index b5ac0206c9..7576f8a0ca 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Brak haseł." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Usuń" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index a14c0ce170..6abab6b104 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -400,11 +400,11 @@ "description": "deprecated. Use specialCharactersLabel instead." }, "include": { - "message": "Include", + "message": "Incluir", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Incluir caracteres maiúsculos", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -412,7 +412,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Incluir caracteres minúsculos", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -420,15 +420,15 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Incluir números", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { - "message": "0-9", + "message": "0 – 9", "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Incluir caracteres especiais", "description": "Full description for the password generator special characters checkbox" }, "specialCharactersLabel": { @@ -459,7 +459,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Evitar Caracteres Ambíguos", "description": "Label for the avoid ambiguous characters checkbox." }, "searchVault": { @@ -684,10 +684,10 @@ "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Sua nova conta foi criada!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Você está conectado!" }, "youSuccessfullyLoggedIn": { "message": "Você logou na sua conta com sucesso" @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Não existem senhas para listar." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remover" }, @@ -3478,7 +3487,7 @@ "message": "Verificação requerida pelo site que a iniciou. Esse recurso ainda não está implementado para contas sem senha mestra." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "Fazer ‘login’ com chave de acesso?" }, "passkeyAlreadyExists": { "message": "Uma chave de acesso já existe para este aplicativo." @@ -3490,7 +3499,7 @@ "message": "Você não tem um login correspondente para este site." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "Sem credenciais correspondentes para este site" }, "confirm": { "message": "Confirmar" @@ -3502,10 +3511,10 @@ "message": "Salvar chave de acesso como um novo login" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "Escolha um ‘login’ para salvar com essa chave de acesso" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "Escolha uma senha para iniciar sessão" }, "passkeyItem": { "message": "Item de chave de acesso" @@ -3703,7 +3712,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "Senha salva!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { @@ -3711,7 +3720,7 @@ "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "Senha atualizada!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { @@ -4300,28 +4309,31 @@ "enterprisePolicyRequirementsApplied": { "message": "Os requisitos de política empresarial foram aplicados nesta configuração" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Mostrar contagem de caracteres" + }, + "hideCharacterCount": { + "message": "Esconder contagem de caracteres" }, "itemsInTrash": { - "message": "Items in trash" + "message": "Itens na lixeira" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "Nenhum item na lixeira" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "Os itens que você excluir aparecerão aqui e serão excluídos permanentemente após 30 dias" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "Os itens que ficarem na lixeira por mais de 30 dias serão excluídos automaticamente" }, "restore": { - "message": "Restore" + "message": "Restaurar" }, "deleteForever": { - "message": "Delete forever" + "message": "Apagar permanentemente" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Você não tem permissão para editar este arquivo" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 459200c735..c516d2d1ed 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Não existem palavras-passe para listar." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remover" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Os requisitos da política empresarial foram aplicados a esta definição" }, - "additionalContentAvailable": { - "message": "Estão disponíveis conteúdos adicionais" + "showCharacterCount": { + "message": "Mostrar contagem de caracteres" + }, + "hideCharacterCount": { + "message": "Ocultar contagem de caracteres" }, "itemsInTrash": { "message": "Itens no lixo" diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 24e77c0564..62dda4fe0f 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Nicio parolă de afișat." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Ștergere" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 1099825fba..fec64ad9ce 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Нет паролей для отображения." }, + "clearHistory": { + "message": "Очистить историю" + }, + "noPasswordsToShow": { + "message": "Нет паролей для отображения" + }, + "noRecentlyGeneratedPassword": { + "message": "Нет недавно сгенерированных паролей" + }, "remove": { "message": "Удалить" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "К этой настройке были применены требования корпоративной политики" }, - "additionalContentAvailable": { - "message": "Дополнительный контент доступен" + "showCharacterCount": { + "message": "Показать количество символов" + }, + "hideCharacterCount": { + "message": "Скрыть количество символов" }, "itemsInTrash": { "message": "Элементы в корзине" diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index acea5593e4..9b906a57ea 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "ලැයිස්තු ගත කිරීමට මුරපද නොමැත." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "ඉවත් කරන්න" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 2eb0172d1f..0f9088dc5e 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Neboli nájdené žiadne heslá." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Odstrániť" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Na toto nastavenie boli uplatnené požiadavky pravidiel spoločnosti" }, - "additionalContentAvailable": { - "message": "K dispozícii je ďalší obsah" + "showCharacterCount": { + "message": "Zobraziť počítadlo znakov" + }, + "hideCharacterCount": { + "message": "Skryť počítadlo znakov" }, "itemsInTrash": { "message": "Položky v koši" diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 030ae2eac7..b9f72e0dae 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Ni takšnih gesel." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Odstrani" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index b3d179ef2b..257478519b 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Нема лозинки у листи." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Уклони" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Захтеви политике предузећа су примењени на ово подешавање" }, - "additionalContentAvailable": { - "message": "Додатни садржај је доступан" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Ставке у смећу" diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 270c1fb374..6c8715a958 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Det finns inga lösenord att lista." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Ta bort" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Visa antal tecken" + }, + "hideCharacterCount": { + "message": "Dölj antal tecken" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 0aa83ad1d7..81f6bacb96 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "There are no passwords to list." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Remove" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 6332df29b7..645cfc5e37 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "ไม่มีรหัสผ่านที่จะแสดง" }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "ลบ" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index d510619a5c..9539f49b9f 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Listelenecek parola yok." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Kaldır" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Bu ayara kurumsal ilke gereksinimleri uygulandı" }, - "additionalContentAvailable": { - "message": "Ek içerikler mevcut" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Çöp kutusundaki kayıtlar" diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index ad39629b28..47f5bb486b 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Немає паролів." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Вилучити" }, @@ -3478,7 +3487,7 @@ "message": "Сайт ініціює обов'язкову верифікацію. Ця функція ще не реалізована для облікових записів без головного пароля." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "Увійти з ключем доступу?" }, "passkeyAlreadyExists": { "message": "Ключ доступу для цієї програми вже існує." @@ -3490,7 +3499,7 @@ "message": "У вас немає відповідних записів для цього сайту." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "Немає відповідних записів для цього сайту" }, "confirm": { "message": "Підтвердити" @@ -3502,10 +3511,10 @@ "message": "Зберегти ключ доступу як новий запис" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "Виберіть запис для збереження цього ключа доступу" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "Виберіть ключ доступу для входу" }, "passkeyItem": { "message": "Ключ доступу" @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "До цього налаштування застосовано вимоги політики компанії" }, - "additionalContentAvailable": { - "message": "Доступний додатковий вміст" + "showCharacterCount": { + "message": "Показати кількість символів" + }, + "hideCharacterCount": { + "message": "Приховати кількість символів" }, "itemsInTrash": { "message": "Записи в смітнику" diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 698f87f78b..559000d519 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "Không có mật khẩu để liệt kê." }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "Xoá" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index fac6baff19..de2bfdf63f 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "没有可列出的密码。" }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "移除" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "企业策略要求已应用于此设置" }, - "additionalContentAvailable": { - "message": "其他内容可用" + "showCharacterCount": { + "message": "显示字符计数" + }, + "hideCharacterCount": { + "message": "隐藏字符计数" }, "itemsInTrash": { "message": "回收站中的项目" diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index e2b2705fda..8f6673017c 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -1796,6 +1796,15 @@ "noPasswordsInList": { "message": "沒有可列出的密碼。" }, + "clearHistory": { + "message": "Clear history" + }, + "noPasswordsToShow": { + "message": "No passwords to show" + }, + "noRecentlyGeneratedPassword": { + "message": "You haven't generated a password recently" + }, "remove": { "message": "移除" }, @@ -4300,8 +4309,11 @@ "enterprisePolicyRequirementsApplied": { "message": "Enterprise policy requirements have been applied to this setting" }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "itemsInTrash": { "message": "Items in trash" diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 5d2d2a3898..e91a58a84c 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -217,6 +217,7 @@ export type OverlayBackgroundExtensionMessageHandlers = { addEditCipherSubmitted: () => void; editedCipher: () => void; deletedCipher: () => void; + fido2AbortRequest: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; }; export type PortMessageParam = { diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 5ac94582ce..30f19e7260 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -18,12 +18,12 @@ import { EnvironmentService, Region, } from "@bitwarden/common/platform/abstractions/environment.service"; -import { Fido2ClientService } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CloudEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; +import { Fido2ActiveRequestManager } from "@bitwarden/common/platform/services/fido2/fido2-active-request-manager"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { FakeAccountService, @@ -32,6 +32,7 @@ import { } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { Fido2CredentialView } from "@bitwarden/common/vault/models/view/fido2-credential.view"; @@ -89,8 +90,9 @@ describe("OverlayBackground", () => { let autofillSettingsService: MockProxy; let i18nService: MockProxy; let platformUtilsService: MockProxy; - let availableAutofillCredentialsMock$: BehaviorSubject; - let fido2ClientService: MockProxy; + let enablePasskeysMock$: BehaviorSubject; + let vaultSettingsServiceMock: MockProxy; + let fido2ActiveRequestManager: Fido2ActiveRequestManager; let selectedThemeMock$: BehaviorSubject; let themeStateService: MockProxy; let overlayBackground: OverlayBackground; @@ -159,10 +161,10 @@ describe("OverlayBackground", () => { autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$; i18nService = mock(); platformUtilsService = mock(); - availableAutofillCredentialsMock$ = new BehaviorSubject([]); - fido2ClientService = mock({ - availableAutofillCredentials$: (_tabId) => availableAutofillCredentialsMock$, - }); + enablePasskeysMock$ = new BehaviorSubject(true); + vaultSettingsServiceMock = mock(); + vaultSettingsServiceMock.enablePasskeys$ = enablePasskeysMock$; + fido2ActiveRequestManager = new Fido2ActiveRequestManager(); selectedThemeMock$ = new BehaviorSubject(ThemeType.Light); themeStateService = mock(); themeStateService.selectedTheme$ = selectedThemeMock$; @@ -176,7 +178,8 @@ describe("OverlayBackground", () => { autofillSettingsService, i18nService, platformUtilsService, - fido2ClientService, + vaultSettingsServiceMock, + fido2ActiveRequestManager, themeStateService, ); portKeyForTabSpy = overlayBackground["portKeyForTab"]; @@ -779,6 +782,15 @@ describe("OverlayBackground", () => { expect(cipherService.getAllDecryptedForUrl).not.toHaveBeenCalled(); }); + it("skips updating the inline menu ciphers if the current tab url has non-http protocol", async () => { + const nonHttpTab = createChromeTabMock({ url: "chrome-extension://id/route" }); + getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(nonHttpTab); + + await overlayBackground.updateOverlayCiphers(); + + expect(cipherService.getAllDecryptedForUrl).not.toHaveBeenCalled(); + }); + it("closes the inline menu on the focused field's tab if the user's auth status is not unlocked", async () => { activeAccountStatusMock$.next(AuthenticationStatus.Locked); const previousTab = mock({ id: 1 }); @@ -1113,7 +1125,11 @@ describe("OverlayBackground", () => { }); it("adds available passkey ciphers to the inline menu", async () => { - availableAutofillCredentialsMock$.next(passkeyCipher.login.fido2Credentials); + void fido2ActiveRequestManager.newActiveRequest( + tab.id, + passkeyCipher.login.fido2Credentials, + new AbortController(), + ); overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ tabId: tab.id, filledByCipherType: CipherType.Login, @@ -1192,10 +1208,15 @@ describe("OverlayBackground", () => { }); it("does not add a passkey to the inline menu when its rpId is part of the neverDomains exclusion list", async () => { - availableAutofillCredentialsMock$.next(passkeyCipher.login.fido2Credentials); + void fido2ActiveRequestManager.newActiveRequest( + tab.id, + passkeyCipher.login.fido2Credentials, + new AbortController(), + ); overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ tabId: tab.id, filledByCipherType: CipherType.Login, + showPasskeys: true, }); cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, passkeyCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); @@ -1248,6 +1269,69 @@ describe("OverlayBackground", () => { showPasskeysLabels: false, }); }); + + it("does not add passkeys to the inline menu if the passkey setting is disabled", async () => { + enablePasskeysMock$.next(false); + void fido2ActiveRequestManager.newActiveRequest( + tab.id, + passkeyCipher.login.fido2Credentials, + new AbortController(), + ); + overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ + tabId: tab.id, + filledByCipherType: CipherType.Login, + showPasskeys: true, + }); + cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, passkeyCipher]); + cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); + getTabFromCurrentWindowIdSpy.mockResolvedValueOnce(tab); + + await overlayBackground.updateOverlayCiphers(); + + expect(listPortSpy.postMessage).toHaveBeenCalledWith({ + command: "updateAutofillInlineMenuListCiphers", + ciphers: [ + { + id: "inline-menu-cipher-0", + name: passkeyCipher.name, + type: CipherType.Login, + reprompt: passkeyCipher.reprompt, + favorite: passkeyCipher.favorite, + icon: { + fallbackImage: "images/bwi-globe.png", + icon: "bwi-globe", + image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", + imageEnabled: true, + }, + accountCreationFieldType: undefined, + login: { + username: passkeyCipher.login.username, + passkey: null, + }, + }, + { + id: "inline-menu-cipher-1", + name: loginCipher1.name, + type: CipherType.Login, + reprompt: loginCipher1.reprompt, + favorite: loginCipher1.favorite, + icon: { + fallbackImage: "images/bwi-globe.png", + icon: "bwi-globe", + image: "https://icons.bitwarden.com//jest-testing-website.com/icon.png", + imageEnabled: true, + }, + accountCreationFieldType: undefined, + login: { + username: loginCipher1.login.username, + passkey: null, + }, + }, + ], + showInlineMenuAccountCreation: false, + showPasskeysLabels: false, + }); + }); }); describe("extension message handlers", () => { @@ -2537,6 +2621,25 @@ describe("OverlayBackground", () => { }); }); }); + + describe("fido2AbortRequest", () => { + const sender = mock({ tab: { id: 1 } }); + it("removes an active request associated with the sender tab", () => { + const removeActiveRequestSpy = jest.spyOn(fido2ActiveRequestManager, "removeActiveRequest"); + + sendMockExtensionMessage({ command: "fido2AbortRequest" }, sender); + + expect(removeActiveRequestSpy).toHaveBeenCalledWith(sender.tab.id); + }); + + it("updates the overlay ciphers after removing the active request", () => { + const updateOverlayCiphersSpy = jest.spyOn(overlayBackground, "updateOverlayCiphers"); + + sendMockExtensionMessage({ command: "fido2AbortRequest" }, sender); + + expect(updateOverlayCiphersSpy).toHaveBeenCalledWith(false); + }); + }); }); describe("handle extension onMessage", () => { @@ -2920,6 +3023,7 @@ describe("OverlayBackground", () => { [sender.frameId, pageDetailsForTab], ]); autofillService.isPasswordRepromptRequired.mockResolvedValue(false); + jest.spyOn(fido2ActiveRequestManager, "getActiveRequest"); sendPortMessage(listMessageConnectorSpy, { command: "fillAutofillInlineMenuCipher", @@ -2929,10 +3033,7 @@ describe("OverlayBackground", () => { }); await flushPromises(); - expect(fido2ClientService.autofillCredential).toHaveBeenCalledWith( - sender.tab.id, - fido2Credential.credentialId, - ); + expect(fido2ActiveRequestManager.getActiveRequest).toHaveBeenCalledWith(sender.tab.id); }); }); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 0c626c6879..0047d1de28 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -6,6 +6,8 @@ import { throttleTime, switchMap, debounceTime, + Observable, + map, } from "rxjs"; import { parse } from "tldts"; @@ -20,13 +22,17 @@ import { DomainSettingsService } from "@bitwarden/common/autofill/services/domai import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { Fido2ClientService } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { + Fido2ActiveRequestEvents, + Fido2ActiveRequestManager, +} from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { buildCipherIcon } from "@bitwarden/common/vault/icon/build-cipher-icon"; import { CardView } from "@bitwarden/common/vault/models/view/card.view"; @@ -144,6 +150,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { addEditCipherSubmitted: () => this.updateOverlayCiphers(), editedCipher: () => this.updateOverlayCiphers(), deletedCipher: () => this.updateOverlayCiphers(), + fido2AbortRequest: ({ sender }) => this.abortFido2ActiveRequest(sender), }; private readonly inlineMenuButtonPortMessageHandlers: InlineMenuButtonPortMessageHandlers = { triggerDelayedAutofillInlineMenuClosure: () => this.triggerDelayedInlineMenuClosure(), @@ -175,7 +182,8 @@ export class OverlayBackground implements OverlayBackgroundInterface { private autofillSettingsService: AutofillSettingsServiceAbstraction, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private fido2ClientService: Fido2ClientService, + private vaultSettingsService: VaultSettingsService, + private fido2ActiveRequestManager: Fido2ActiveRequestManager, private themeStateService: ThemeStateService, ) { this.initOverlayEventObservables(); @@ -196,7 +204,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { */ private initOverlayEventObservables() { this.storeInlineMenuFido2CredentialsSubject - .pipe(switchMap((tabId) => this.fido2ClientService.availableAutofillCredentials$(tabId))) + .pipe(switchMap((tabId) => this.availablePasskeyAuthCredentials$(tabId))) .subscribe((credentials) => this.storeInlineMenuFido2Credentials(credentials)); this.repositionInlineMenuSubject .pipe( @@ -279,6 +287,11 @@ export class OverlayBackground implements OverlayBackgroundInterface { return; } + const request = this.fido2ActiveRequestManager.getActiveRequest(currentTab.id); + if (request) { + request.subject.next({ type: Fido2ActiveRequestEvents.Refresh }); + } + this.inlineMenuFido2Credentials.clear(); this.storeInlineMenuFido2CredentialsSubject.next(currentTab.id); @@ -452,6 +465,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { if (domainExclusions) { domainExclusionsSet = new Set(Object.keys(await this.getExcludedDomains())); } + const passkeysEnabled = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); for (let cipherIndex = 0; cipherIndex < inlineMenuCiphersArray.length; cipherIndex++) { const [inlineMenuCipherId, cipher] = inlineMenuCiphersArray[cipherIndex]; @@ -459,7 +473,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { continue; } - if (!this.showCipherAsPasskey(cipher, domainExclusionsSet)) { + if (!passkeysEnabled || !(await this.showCipherAsPasskey(cipher, domainExclusionsSet))) { inlineMenuCipherData.push( this.buildCipherData({ inlineMenuCipherId, cipher, showFavicons }), ); @@ -497,7 +511,10 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param cipher - The cipher to check * @param domainExclusions - The domain exclusions to check against */ - private showCipherAsPasskey(cipher: CipherView, domainExclusions: Set | null): boolean { + private async showCipherAsPasskey( + cipher: CipherView, + domainExclusions: Set | null, + ): Promise { if (cipher.type !== CipherType.Login || !this.focusedFieldData?.showPasskeys) { return false; } @@ -514,10 +531,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { return false; } - return ( - this.inlineMenuFido2Credentials.size === 0 || - this.inlineMenuFido2Credentials.has(credentialId) - ); + return this.inlineMenuFido2Credentials.has(credentialId); } /** @@ -635,12 +649,35 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param credentials - The FIDO2 credentials to store */ private storeInlineMenuFido2Credentials(credentials: Fido2CredentialView[]) { + this.inlineMenuFido2Credentials.clear(); + credentials.forEach( (credential) => credential?.credentialId && this.inlineMenuFido2Credentials.add(credential.credentialId), ); } + /** + * Gets the passkey credentials available from an active FIDO2 request for a given tab. + * + * @param tabId - The tab id to get the active request for. + */ + private availablePasskeyAuthCredentials$(tabId: number): Observable { + return this.fido2ActiveRequestManager + .getActiveRequest$(tabId) + .pipe(map((request) => request?.credentials ?? [])); + } + + /** + * Aborts an active FIDO2 request for a given tab and updates the inline menu ciphers. + * + * @param sender - The sender of the message + */ + private async abortFido2ActiveRequest(sender: chrome.runtime.MessageSender) { + this.fido2ActiveRequestManager.removeActiveRequest(sender.tab.id); + await this.updateOverlayCiphers(false); + } + /** * Gets the neverDomains setting from the domain settings service. */ @@ -900,11 +937,12 @@ export class OverlayBackground implements OverlayBackgroundInterface { const cipher = this.inlineMenuCiphers.get(inlineMenuCipherId); if (usePasskey && cipher.login?.hasFido2Credentials) { - await this.fido2ClientService.autofillCredential( + await this.authenticatePasskeyCredential( sender.tab.id, cipher.login.fido2Credentials[0].credentialId, ); this.updateLastUsedInlineMenuCipher(inlineMenuCipherId, cipher); + this.closeInlineMenu(sender, { forceCloseInlineMenu: true }); return; } @@ -927,6 +965,24 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.updateLastUsedInlineMenuCipher(inlineMenuCipherId, cipher); } + /** + * Triggers a FIDO2 authentication from the inline menu using the passed credential ID. + * + * @param tabId - The tab ID to trigger the authentication for + * @param credentialId - The credential ID to authenticate + */ + async authenticatePasskeyCredential(tabId: number, credentialId: string) { + const request = this.fido2ActiveRequestManager.getActiveRequest(tabId); + if (!request) { + this.logService.error( + "Could not complete passkey autofill due to missing active Fido2 request", + ); + return; + } + + request.subject.next({ type: Fido2ActiveRequestEvents.Continue, credentialId }); + } + /** * Sets the most recently used cipher at the top of the list of ciphers. * diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts index 2a22a226e3..23d0292e18 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts @@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { Fido2ActiveRequestManager } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction"; import { AssertCredentialParams, CreateCredentialParams, @@ -52,6 +53,7 @@ describe("Fido2Background", () => { let tabMock!: MockProxy; let senderMock!: MockProxy; let logService!: MockProxy; + let fido2ActiveRequestManager: MockProxy; let fido2ClientService!: MockProxy; let vaultSettingsService!: MockProxy; let scriptInjectorServiceMock!: MockProxy; @@ -77,9 +79,11 @@ describe("Fido2Background", () => { enablePasskeysMock$ = new BehaviorSubject(true); vaultSettingsService.enablePasskeys$ = enablePasskeysMock$; + fido2ActiveRequestManager = mock(); fido2ClientService.isFido2FeatureEnabled.mockResolvedValue(true); fido2Background = new Fido2Background( logService, + fido2ActiveRequestManager, fido2ClientService, vaultSettingsService, scriptInjectorServiceMock, diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.ts b/apps/browser/src/autofill/fido2/background/fido2.background.ts index 800c7d4e0d..a9d1b31477 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.ts @@ -3,6 +3,7 @@ import { pairwise } from "rxjs/operators"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { Fido2ActiveRequestManager } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction"; import { AssertCredentialParams, AssertCredentialResult, @@ -49,6 +50,7 @@ export class Fido2Background implements Fido2BackgroundInterface { constructor( private logService: LogService, + private fido2ActiveRequestManager: Fido2ActiveRequestManager, private fido2ClientService: Fido2ClientService, private vaultSettingsService: VaultSettingsService, private scriptInjectorService: ScriptInjectorService, @@ -96,6 +98,7 @@ export class Fido2Background implements Fido2BackgroundInterface { previousEnablePasskeysSetting: boolean, enablePasskeys: boolean, ) { + this.fido2ActiveRequestManager.removeAllActiveRequests(); await this.updateContentScriptRegistration(); if (previousEnablePasskeysSetting === undefined) { diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts index 171aa7cd83..737f8e77ca 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts @@ -60,6 +60,10 @@ import { MessageWithMetadata, Messenger } from "./messaging/messenger"; message.data as InsecureAssertCredentialParams, ); } + + if (message.type === MessageType.AbortRequest) { + return sendExtensionMessage("fido2AbortRequest", { abortedRequestId: requestId }); + } } finally { abortController.signal.removeEventListener("abort", abortHandler); } diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index 4631b78ddb..c44c263dd2 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -131,6 +131,12 @@ import { Messenger } from "./messaging/messenger"; const internalAbortControllers = [new AbortController(), new AbortController()]; const bitwardenResponse = async (internalAbortController: AbortController) => { try { + const abortListener = () => + messenger.request({ + type: MessageType.AbortRequest, + abortedRequestId: abortSignal.toString(), + }); + internalAbortController.signal.addEventListener("abort", abortListener); const response = await messenger.request( { type: MessageType.CredentialGetRequest, @@ -138,6 +144,7 @@ import { Messenger } from "./messaging/messenger"; }, internalAbortController.signal, ); + internalAbortController.signal.removeEventListener("abort", abortListener); if (response.type !== MessageType.CredentialGetResponse) { throw new Error("Something went wrong."); } diff --git a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts index 1e5e880c67..3ab9edf60f 100644 --- a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts @@ -242,12 +242,13 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi cipherIds, userVerification, assumeUserPresence, + masterPasswordRepromptRequired, }: PickCredentialParams): Promise<{ cipherId: string; userVerified: boolean }> { // NOTE: For now, we are defaulting to a userVerified status of `true` when the request // is for a conditionally mediated authentication. This will allow for mediated conditional // authentication to function without requiring user interaction. This is a product // decision, rather than a decision based on the expected technical specifications. - if (assumeUserPresence && cipherIds.length === 1) { + if (assumeUserPresence && cipherIds.length === 1 && !masterPasswordRepromptRequired) { return { cipherId: cipherIds[0], userVerified: userVerification }; } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index df1e479656..1cb615fe06 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -79,6 +79,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { Fido2ActiveRequestManager as Fido2ActiveRequestManagerAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction"; import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; @@ -324,6 +325,7 @@ export default class MainBackground { userVerificationApiService: UserVerificationApiServiceAbstraction; fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction; fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction; + fido2ActiveRequestManager: Fido2ActiveRequestManagerAbstraction; fido2ClientService: Fido2ClientServiceAbstraction; avatarService: AvatarServiceAbstraction; mainContextMenuHandler: MainContextMenuHandler; @@ -1021,7 +1023,7 @@ export default class MainBackground { this.accountService, this.logService, ); - const fido2ActiveRequestManager = new Fido2ActiveRequestManager(); + this.fido2ActiveRequestManager = new Fido2ActiveRequestManager(); this.fido2ClientService = new Fido2ClientService( this.fido2AuthenticatorService, this.configService, @@ -1029,7 +1031,7 @@ export default class MainBackground { this.vaultSettingsService, this.domainSettingsService, this.taskSchedulerService, - fido2ActiveRequestManager, + this.fido2ActiveRequestManager, this.logService, ); @@ -1057,6 +1059,7 @@ export default class MainBackground { if (!this.popupOnlyContext) { this.fido2Background = new Fido2Background( this.logService, + this.fido2ActiveRequestManager, this.fido2ClientService, this.vaultSettingsService, this.scriptInjectorService, @@ -1605,7 +1608,8 @@ export default class MainBackground { this.autofillSettingsService, this.i18nService, this.platformUtilsService, - this.fido2ClientService, + this.vaultSettingsService, + this.fido2ActiveRequestManager, this.themeStateService, ); } diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 5792c10c05..f916555fef 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.8.2", + "version": "2024.9.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index fe99c9e988..12d501ba9e 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2024.8.2", + "version": "2024.9.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index e5761b6c83..5660fc33a9 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -57,6 +57,7 @@ import { PremiumV2Component } from "../billing/popup/settings/premium-v2.compone import { PremiumComponent } from "../billing/popup/settings/premium.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; +import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component"; import { CredentialGeneratorComponent } from "../tools/popup/generator/credential-generator.component"; import { GeneratorComponent } from "../tools/popup/generator/generator.component"; import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component"; @@ -274,12 +275,11 @@ const routes: Routes = [ canActivate: [authGuard], data: { state: "generator" }, }, - { + ...extensionRefreshSwap(PasswordGeneratorHistoryComponent, CredentialGeneratorHistoryComponent, { path: "generator-history", - component: PasswordGeneratorHistoryComponent, canActivate: [authGuard], data: { state: "generator-history" }, - }, + }), ...extensionRefreshSwap(ImportBrowserComponent, ImportBrowserV2Component, { path: "import", canActivate: [authGuard], diff --git a/apps/browser/src/tools/popup/generator/credential-generator-history.component.html b/apps/browser/src/tools/popup/generator/credential-generator-history.component.html new file mode 100644 index 0000000000..48f0479d8d --- /dev/null +++ b/apps/browser/src/tools/popup/generator/credential-generator-history.component.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts b/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts new file mode 100644 index 0000000000..0de71cf5b9 --- /dev/null +++ b/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts @@ -0,0 +1,66 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { BehaviorSubject, distinctUntilChanged, firstValueFrom, map, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { ButtonModule, ContainerComponent } from "@bitwarden/components"; +import { + CredentialGeneratorHistoryComponent as CredentialGeneratorHistoryToolsComponent, + EmptyCredentialHistoryComponent, +} from "@bitwarden/generator-components"; +import { GeneratorHistoryService } from "@bitwarden/generator-history"; + +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; + +@Component({ + selector: "app-credential-generator-history", + templateUrl: "credential-generator-history.component.html", + standalone: true, + imports: [ + ButtonModule, + CommonModule, + ContainerComponent, + JslibModule, + PopOutComponent, + PopupHeaderComponent, + PopupPageComponent, + CredentialGeneratorHistoryToolsComponent, + EmptyCredentialHistoryComponent, + PopupFooterComponent, + ], +}) +export class CredentialGeneratorHistoryComponent { + protected readonly hasHistory$ = new BehaviorSubject(false); + protected readonly userId$ = new BehaviorSubject(null); + + constructor( + private accountService: AccountService, + private history: GeneratorHistoryService, + ) { + this.accountService.activeAccount$ + .pipe( + takeUntilDestroyed(), + map(({ id }) => id), + distinctUntilChanged(), + ) + .subscribe(this.userId$); + + this.userId$ + .pipe( + takeUntilDestroyed(), + switchMap((id) => id && this.history.credentials$(id)), + map((credentials) => credentials.length > 0), + ) + .subscribe(this.hasHistory$); + } + + clear = async () => { + await this.history.clear(await firstValueFrom(this.userId$)); + }; +} diff --git a/apps/browser/src/tools/popup/send/send-groupings.component.ts b/apps/browser/src/tools/popup/send/send-groupings.component.ts index 99cac6f73c..87de6af31c 100644 --- a/apps/browser/src/tools/popup/send/send-groupings.component.ts +++ b/apps/browser/src/tools/popup/send/send-groupings.component.ts @@ -64,7 +64,7 @@ export class SendGroupingsComponent extends BaseSendComponent implements OnInit, dialogService, toastService, ); - super.onSuccessfulLoad = async () => { + this.onSuccessfulLoad = async () => { this.selectAll(); }; } diff --git a/apps/browser/src/tools/popup/send/send-type.component.ts b/apps/browser/src/tools/popup/send/send-type.component.ts index 2a555dfa92..8329831e0b 100644 --- a/apps/browser/src/tools/popup/send/send-type.component.ts +++ b/apps/browser/src/tools/popup/send/send-type.component.ts @@ -66,7 +66,7 @@ export class SendTypeComponent extends BaseSendComponent implements OnInit, OnDe dialogService, toastService, ); - super.onSuccessfulLoad = async () => { + this.onSuccessfulLoad = async () => { this.selectType(this.type); }; this.applySavedState = diff --git a/apps/browser/store/locales/et/copy.resx b/apps/browser/store/locales/et/copy.resx index 0f0c8c13fb..d2a6be860c 100644 --- a/apps/browser/store/locales/et/copy.resx +++ b/apps/browser/store/locales/et/copy.resx @@ -124,48 +124,48 @@ Kodus, tööl ja teel - Bitwarden hoiustab imelihtsalt kõik su paroolid, pääsuvõtmed ja tundliku info. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Tunnustatud firmade PCMag, WIRED, The Verge, CNET, G2 ja teiste poolt kui parim paroolihaldur! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +KAITSE ENDA DIGITAALSET MAAILMA +Lukusta oma digitaalne maailm ja kaitse seda andmelekete vastu luues ja salvestades unikaalsed ja tugevad paroolid iga konto jaoks. Halda kõike täielikult krüpteeritud hoidlas, kuhu pääsed ainult sina ligi. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +PÄÄSE OMA ANDMETELE LIGI IGALT POOLT, IGAL AJAL, IGAST SEADMEST +Halda, hoiusta, kaitse ja jaga lõpmatult palju paroole mööda lõpmatult paljusid seadmeid ilma piiranguteta. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +KÕIGIL PEAKSID OLEMA TÖÖRIISTAD OHUTULT INTERNETIS SEIKLEMISEKS +Kasuta Bitwardenit tasuta ilma reklaamide või andmete müümisega. Bitwarden usub, et kõigil peaks olema võimalus surfata ohutult internetis. Preemium plaanid pakuvad juurdepääsu veel rohkematele funktsioonidele. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +VÕIMENDA OMA TIIME BITWARDENIGA +Tiimide ja ettevõtete plaanid tulevad koos professionaalsete ärifunktsioonidega. Mõned näited on SSO liides, ise majutamine, arvutikataloogide ühendamine ja SCIM kasutamine, globaalsed seaded, API, sündmuste logid ja veel rohkemgi. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Kasuta Bitwardenit oma töötajate turvamiseks ja tundliku informatsiooni jagamiseks kolleegidega. -More reasons to choose Bitwarden: +Veel Põhjusi Miks Valida Bitwarden: -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Maailmatasemel Krüpteering +Paroolid on kaitstud tipptehnoloogilise täieliku krüpteerimisega (AES-256 bit, salted hashtag ja PBKDF2 SHA-256), nii et sinu andmed püsivad privaatsete ja turvalistena. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. +Põhjalikud turvatestid kolmandate firmade poolt +Bitwarden korraldab regulaarselt põhjalike turvateste tuntud kolmandate firmade poolt. Need igaaastased kontrolltestid sisaldavad kogu koodibaasi ülevaatust ja test-küberrünnakuid Bitwardeni IP-de, serverite ja veebirakenduste vastu. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Kõrgetasemeline 2-astmeline autentiteerimine (2FA) +Kaitse oma andmeid kolmanda poole autentitaatoriga, emailile saadetud koodide või FIDO2 WebAuthn süsteemidega, nagu näiteks füüsiline turvavõti või pääsuvõti. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Jaga andmeid otse teistega kasutades täieliku krüpteerimist ja vähenda lekke tõenäosust. -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +Sisse ehitatud Generaator +Loo igale oma saidile pikkasid, keerulisi ning täiesti kordumatuid paroole ja ainulaadseid kasutajanimesid. Veel paremaks privaatsuseks saad end ühendada ka variemaili (email alias) pakkujatega. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Paljudes Keeltes +Bitwardenit on tõlgitud juba 60 erinevasse keelde ja see arv kasvab, tänu meie globaalsele kogukonnale Crowdin platvormil. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Kõigis Sinu Seadmetes +Kaitse ja jaga oma tundliku informatsiooni Bitwardeni Hoidlas nii brauserist, telefonist, arvutist kui ka mujaltki. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden hoiustab peale paroolide veel muudki +Täielikult krüpteeritud andmehalduse lahendused Bitwardenilt võimendavad organisatsioone ja hoiustavad kõike, kaasa arvatud arendajate saladusi ja pääsuvõtmeid. Külasta veebilehte bitwarden.com, et uurida lähemalt Bitwarden Secrets Manageri kohta ja vaata lähemalt passwordless.dev! diff --git a/apps/browser/store/locales/pt_BR/copy.resx b/apps/browser/store/locales/pt_BR/copy.resx index 067f9357b2..24331b3a1b 100644 --- a/apps/browser/store/locales/pt_BR/copy.resx +++ b/apps/browser/store/locales/pt_BR/copy.resx @@ -124,49 +124,46 @@ Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Reconhecido como o melhor gerenciador de senhas por "PCMag", 'WIRED', 'The Verge', 'CNET', 'G2', entre outros! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +PROTEJA A SUA VIDA DIGITAL +Deixe a sua vida digital segura e se proteja contra violações de dados gerando e salvando senhas únicas e fortes para cada conta pessoal. Mantendo tudo isso em um cofre encriptografado de ponta a ponta que apenas você pode acessar. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +ACESSE OS SEUS DADOS, EM QUALQUER LUGAR, HORA E DISPOSITIVO +Gerencie, armazene, proteja e compartilhe senhas facilmente entre dispositivos ilimitados e sem restrições. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +TODOS DEVERIAM TEM FERRAMENTAS PARA SE PROTEGER ONLINE +Utilize Bitwarden de graça sem anuncios ou venda dos seus dados. A Bitwarden acredita que todos deveriam ter a opção de estar seguro online. Planos Premium oferecem recursos mais avançados. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +EMPODERE O SEUS TIMES COM BITWARDEN +Os Planos para times e empresas vem com recursos profissionais para negócios. Alguns exemplos inculuem integração SSO, Auto-hospedagem, integração de diretório e provisionamento SCIM, políticas globais, acesso API, registro de eventos e mais. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Utilizer Bitwarn para proteger os seus empregados e compartilhar informações sensíveis para os colegas. +Mais razões para escolher Bitwarden: -More reasons to choose Bitwarden: +Senhas encriptografadas por classe de palavras são protegidas com criptografia de ponta a ponta avançada (AES-256 bit, salted hashtag, and PBKDF2 SHA-256), então os seus dados ficam seguros e privados. -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Auditoria de Terceiros +Bitwarden regularmente conduz auditorias de terceiros com notáveis empresas de segurança. Essas audições anuais incluem qualificação e penetração do código fonte através de IPs da Bitwarden, servidores e aplicações WEB. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. +2FA Avançado +Proteja o seu login com um autenticador de doisfatores, códigos de email ou credenciais FIDO2 WebAuthn como chave de segurança por hardware ou chave de acesso. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Envio Bitwarden +Transmita dados diretamente para outros enquanto mantem segurança de encriptografia ponta a ponta e limitando exposição. -Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Gerador integrado +Crie senhas grandes, complexas e diferentes e nomes de usuário unicos para cada site que visitar. Integre com provedores de email para privacidade adicional -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +Tradutores globais +As traduções da Bitwarden estão disponíveis para mais de 60 líguas, traduzidas pela comunidade global através do Crowdin. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Aplicações Multi-Plataforma +Proteja e compartilhe conteúdo sensível dentro do Vault da Bitwarden de qualquer navegador, dispositivo móvel, ou desktop OS, entre outros. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. - -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - +Bitwarden protege mais do que apenas soluções em gerenciamento de credenciais de senhas encriptografadas ponta a ponta, Bitwarden empodera organizações para proteger qualquer coisa, incluindo segredos de desenvolvedor e chaves de acesso. +Visite Bitwarden.com para aprender mais sobre Bitwarden Secrets Manager e Bitwarden Passwordless.dev! Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais. diff --git a/apps/cli/package.json b/apps/cli/package.json index 8fe0284ed7..a7759043ff 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2024.8.2", + "version": "2024.9.0", "keywords": [ "bitwarden", "password", diff --git a/apps/cli/src/base-program.ts b/apps/cli/src/base-program.ts index e4340b68e2..0f200d49d9 100644 --- a/apps/cli/src/base-program.ts +++ b/apps/cli/src/base-program.ts @@ -3,6 +3,7 @@ import { firstValueFrom, map } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { UserId } from "@bitwarden/common/types/guid"; import { UnlockCommand } from "./auth/commands/unlock.command"; import { Response } from "./models/response"; @@ -135,37 +136,55 @@ export abstract class BaseProgram { protected async exitIfLocked() { const userId = await this.exitIfNotAuthed(); - if (await this.serviceContainer.cryptoService.hasUserKey()) { + + // If the process.env does not have a BW_SESSION key, then we will never be able to retrieve + // the auto user key from secure storage. This is because the auto user key is encrypted with + // the session key. + const hasUserKey = + await this.serviceContainer.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet( + userId, + ); + + if (hasUserKey) { + // User is unlocked return; - } else if (process.env.BW_NOINTERACTION !== "true") { - // must unlock - if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector(userId)) { - const response = Response.error( - "Your vault is locked. You must unlock your vault using your session key.\n" + - "If you do not have your session key, you can get a new one by logging out and logging in again.", - ); - this.processResponse(response, true); - } else { - const command = new UnlockCommand( - this.serviceContainer.accountService, - this.serviceContainer.masterPasswordService, - this.serviceContainer.cryptoService, - this.serviceContainer.userVerificationService, - this.serviceContainer.cryptoFunctionService, - this.serviceContainer.logService, - this.serviceContainer.keyConnectorService, - this.serviceContainer.environmentService, - this.serviceContainer.syncService, - this.serviceContainer.organizationApiService, - this.serviceContainer.logout, - ); - const response = await command.run(null, null); - if (!response.success) { - this.processResponse(response, true); - } - } - } else { + } + + // User is locked + await this.handleLockedUser(userId); + } + + private async handleLockedUser(userId: UserId) { + if (process.env.BW_NOINTERACTION === "true") { this.processResponse(Response.error("Vault is locked."), true); + return; + } + + // must unlock with interaction allowed + if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector(userId)) { + const response = Response.error( + "Your vault is locked. You must unlock your vault using your session key.\n" + + "If you do not have your session key, you can get a new one by logging out and logging in again.", + ); + this.processResponse(response, true); + } else { + const command = new UnlockCommand( + this.serviceContainer.accountService, + this.serviceContainer.masterPasswordService, + this.serviceContainer.cryptoService, + this.serviceContainer.userVerificationService, + this.serviceContainer.cryptoFunctionService, + this.serviceContainer.logService, + this.serviceContainer.keyConnectorService, + this.serviceContainer.environmentService, + this.serviceContainer.syncService, + this.serviceContainer.organizationApiService, + this.serviceContainer.logout, + ); + const response = await command.run(null, null); + if (!response.success) { + this.processResponse(response, true); + } } } diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 6ecdb24931..7582c76095 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -1,6 +1,5 @@ import * as chalk from "chalk"; import { program, Command, OptionValues } from "commander"; -import { firstValueFrom } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -61,18 +60,8 @@ export class Program extends BaseProgram { process.env.BW_NOINTERACTION = "true"; }); - program.on("option:session", async (key) => { + program.on("option:session", (key) => { process.env.BW_SESSION = key; - - // once we have the session key, we can set the user key in memory - const activeAccount = await firstValueFrom( - this.serviceContainer.accountService.activeAccount$, - ); - if (activeAccount) { - await this.serviceContainer.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet( - activeAccount.id, - ); - } }); program.on("command:*", () => { diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index be1b1cc9e2..b9225fec43 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -285,7 +285,13 @@ export class ServiceContainer { this.secureStorageService = new NodeEnvSecureStorageService( this.storageService, this.logService, - this.encryptService, + // MAC failures for secure storage are being logged for customers today and + // they occur when users unlock / login and refresh a session key but don't + // export it into their environment (e.g. BW_SESSION_KEY). This leaves a stale + // BW_SESSION key in the env which is attempted to be used to decrypt the auto + // unlock user key which obviously fails. So, to resolve this, we will not log + // MAC failures for secure storage. + new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), ); this.memoryStorageService = new MemoryStorageService(); @@ -803,6 +809,10 @@ export class ServiceContainer { await this.i18nService.init(); this.twoFactorService.init(); + // If a user has a BW_SESSION key stored in their env (not process.env.BW_SESSION), + // this should set the user key to unlock the vault on init. + // TODO: ideally, we wouldn't want to do this here but instead only for commands that require the vault to be unlocked + // as this runs on every command and could be a performance hit const activeAccount = await firstValueFrom(this.accountService.activeAccount$); if (activeAccount?.id) { await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(activeAccount.id); diff --git a/apps/desktop/desktop_native/.gitignore b/apps/desktop/desktop_native/.gitignore index 1cfa7dafc2..96e7a71e1b 100644 --- a/apps/desktop/desktop_native/.gitignore +++ b/apps/desktop/desktop_native/.gitignore @@ -4,4 +4,3 @@ index.node **/.DS_Store npm-debug.log* *.node -dist diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index d96e0642fa..6c73c3622b 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -482,15 +482,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - [[package]] name = "derive-new" version = "0.6.0" @@ -512,14 +503,10 @@ dependencies = [ "base64", "cbc", "core-foundation", - "dirs", - "futures", "gio", - "interprocess", "keytar", "libc", "libsecret", - "log", "rand", "retry", "scopeguard", @@ -528,7 +515,6 @@ dependencies = [ "sha2", "thiserror", "tokio", - "tokio-util", "typenum", "widestring", "windows", @@ -545,22 +531,6 @@ dependencies = [ "napi", "napi-build", "napi-derive", - "tokio", - "tokio-util", -] - -[[package]] -name = "desktop_proxy" -version = "0.0.0" -dependencies = [ - "anyhow", - "desktop_core", - "embed_plist", - "futures", - "log", - "simplelog", - "tokio", - "tokio-util", ] [[package]] @@ -573,27 +543,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - [[package]] name = "dlib" version = "0.5.2" @@ -603,24 +552,12 @@ dependencies = [ "libloading", ] -[[package]] -name = "doctest-file" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" - [[package]] name = "downcast-rs" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - [[package]] name = "endi" version = "1.1.0" @@ -709,21 +646,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.30" @@ -731,7 +653,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -799,7 +720,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -994,27 +914,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "interprocess" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13" -dependencies = [ - "doctest-file", - "futures-core", - "libc", - "recvmsg", - "tokio", - "widestring", - "windows-sys 0.52.0", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - [[package]] name = "keytar" version = "0.1.6" @@ -1052,16 +951,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "libsecret" version = "0.5.0" @@ -1149,22 +1038,11 @@ dependencies = [ "adler", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - [[package]] name = "napi" -version = "2.16.7" +version = "2.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633e41b2b983cf7983134f0c50986ca524d0caf38a2c6fc893ea3fa2e26abb0c" +checksum = "dfc300228808a0e6aea5a58115c82889240bcf8dab16fc25ad675b33e454b368" dependencies = [ "bitflags", "ctor", @@ -1182,9 +1060,9 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" [[package]] name = "napi-derive" -version = "2.16.6" +version = "2.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a8a778fd367b13c64232e58632514b795514ece491ce136d96e976d34a3eb8" +checksum = "e0e034ddf6155192cf83f267ede763fe6c164dfa9971585436b16173718d94c4" dependencies = [ "cfg-if", "convert_case", @@ -1253,12 +1131,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num_cpus" version = "1.16.0" @@ -1269,15 +1141,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -1392,12 +1255,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "ordered-stream" version = "0.2.0" @@ -1501,12 +1358,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1582,12 +1433,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "recvmsg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" - [[package]] name = "redox_syscall" version = "0.5.3" @@ -1597,17 +1442,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" version = "1.10.6" @@ -1789,17 +1623,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simplelog" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" -dependencies = [ - "log", - "termcolor", - "time", -] - [[package]] name = "slab" version = "0.4.9" @@ -1815,16 +1638,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -1903,39 +1716,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tokio" version = "1.38.0" @@ -1944,13 +1724,9 @@ checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", - "libc", - "mio", "num_cpus", "pin-project-lite", - "socket2", "tokio-macros", - "windows-sys 0.48.0", ] [[package]] @@ -1964,19 +1740,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" version = "0.8.19" @@ -2272,15 +2035,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 6525b38162..c6b77473b2 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["napi", "core", "proxy"] +members = ["napi", "core"] diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js deleted file mode 100644 index f2f012bf08..0000000000 --- a/apps/desktop/desktop_native/build.js +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const child_process = require("child_process"); -const fs = require("fs"); -const path = require("path"); -const process = require("process"); - -let crossPlatform = process.argv.length > 2 && process.argv[2] === "cross-platform"; - -function buildNapiModule(target, release = true) { - const targetArg = target ? `--target ${target}` : ""; - const releaseArg = release ? "--release" : ""; - return child_process.execSync(`npm run build -- ${releaseArg} ${targetArg}`, { stdio: 'inherit', cwd: path.join(__dirname, "napi") }); -} - -function buildProxyBin(target, release = true) { - const targetArg = target ? `--target ${target}` : ""; - const releaseArg = release ? "--release" : ""; - return child_process.execSync(`cargo build --bin desktop_proxy ${releaseArg} ${targetArg}`, {stdio: 'inherit', cwd: path.join(__dirname, "proxy")}); -} - -if (!crossPlatform) { - console.log("Building native modules in debug mode for the native architecture"); - buildNapiModule(false, false); - buildProxyBin(false, false); - return; -} - -// Note that targets contains pairs of [rust target, node arch] -// We do this to move the output binaries to a location that can -// be easily accessed from electron-builder using ${os} and ${arch} -let targets = []; -switch (process.platform) { - case "win32": - targets = [ - ["i686-pc-windows-msvc", 'ia32'], - ["x86_64-pc-windows-msvc", 'x64'], - ["aarch64-pc-windows-msvc", 'arm64'] - ]; - break; - - case "darwin": - targets = [ - ["x86_64-apple-darwin", 'x64'], - ["aarch64-apple-darwin", 'arm64'] - ]; - break; - - default: - targets = [ - ['x86_64-unknown-linux-musl', 'x64'] - ]; - - process.env["PKG_CONFIG_ALLOW_CROSS"] = "1"; - process.env["PKG_CONFIG_ALL_STATIC"] = "1"; - break; -} - -console.log("Cross building native modules for the targets: ", targets.map(([target, _]) => target).join(", ")); - -fs.mkdirSync(path.join(__dirname, "dist"), { recursive: true }); - -targets.forEach(([target, nodeArch]) => { - buildNapiModule(target); - buildProxyBin(target); - - const ext = process.platform === "win32" ? ".exe" : ""; - fs.copyFileSync(path.join(__dirname, "target", target, "release", `desktop_proxy${ext}`), path.join(__dirname, "dist", `desktop_proxy.${process.platform}-${nodeArch}${ext}`)); -}); diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 46b22a5b74..d885920883 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -6,21 +6,9 @@ version = "0.0.0" publish = false [features] -default = ["sys"] +default = [] manual_test = [] -sys = [ - "dep:widestring", - "dep:windows", - "dep:core-foundation", - "dep:security-framework", - "dep:security-framework-sys", - "dep:gio", - "dep:libsecret", - "dep:zbus", - "dep:zbus_polkit", -] - [dependencies] aes = "=0.8.4" anyhow = "=1.0.86" @@ -29,22 +17,17 @@ arboard = { version = "=3.4.0", default-features = false, features = [ ] } base64 = "=0.22.1" cbc = { version = "=0.1.2", features = ["alloc"] } -dirs = "=5.0.1" -futures = "=0.3.30" -interprocess = { version = "=2.2.1", features = ["tokio"] } libc = "=0.2.155" -log = "=0.4.22" rand = "=0.8.5" retry = "=2.0.0" scopeguard = "=1.2.0" sha2 = "=0.10.8" thiserror = "=1.0.61" tokio = { version = "=1.38.0", features = ["io-util", "sync", "macros"] } -tokio-util = "=0.7.11" typenum = "=1.17.0" [target.'cfg(windows)'.dependencies] -widestring = { version = "=1.1.0", optional = true } +widestring = "=1.1.0" windows = { version = "=0.57.0", features = [ "Foundation", "Security_Credentials_UI", @@ -55,18 +38,18 @@ windows = { version = "=0.57.0", features = [ "Win32_System_WinRT", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging", -], optional = true } +] } [target.'cfg(windows)'.dev-dependencies] keytar = "=0.1.6" [target.'cfg(target_os = "macos")'.dependencies] -core-foundation = { version = "=0.9.4", optional = true } -security-framework = { version = "=2.11.0", optional = true } -security-framework-sys = { version = "=2.11.0", optional = true } +core-foundation = "=0.9.4" +security-framework = "=2.11.0" +security-framework-sys = "=2.11.0" [target.'cfg(target_os = "linux")'.dependencies] -gio = { version = "=0.19.5", optional = true } -libsecret = { version = "=0.5.0", optional = true } -zbus = { version = "=4.3.1", optional = true } -zbus_polkit = { version = "=4.0.0", optional = true } +gio = "=0.19.5" +libsecret = "=0.5.0" +zbus = "=4.3.1" +zbus_polkit = "=4.0.0" diff --git a/apps/desktop/desktop_native/core/src/ipc/client.rs b/apps/desktop/desktop_native/core/src/ipc/client.rs deleted file mode 100644 index 6e24a74629..0000000000 --- a/apps/desktop/desktop_native/core/src/ipc/client.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; - -use interprocess::local_socket::{ - tokio::{prelude::*, Stream}, - GenericFilePath, ToFsName, -}; -use log::{error, info}; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - time::sleep, -}; - -use crate::ipc::NATIVE_MESSAGING_BUFFER_SIZE; - -pub async fn connect( - path: PathBuf, - send: tokio::sync::mpsc::Sender, - mut recv: tokio::sync::mpsc::Receiver, -) { - // Keep track of connection failures to make sure we don't leave the process as a zombie - let mut connection_failures = 0; - - loop { - match connect_inner(&path, &send, &mut recv).await { - Ok(()) => return, - Err(e) => { - connection_failures += 1; - if connection_failures >= 20 { - error!("Failed to connect to IPC server after 20 attempts: {e}"); - return; - } - - error!("Failed to connect to IPC server: {e}"); - } - } - - sleep(Duration::from_secs(5)).await; - } -} - -async fn connect_inner( - path: &Path, - send: &tokio::sync::mpsc::Sender, - recv: &mut tokio::sync::mpsc::Receiver, -) -> Result<(), Box> { - info!("Attempting to connect to {}", path.display()); - - let name = path.as_os_str().to_fs_name::()?; - let mut conn = Stream::connect(name).await?; - - info!("Connected to {}", path.display()); - - // This `connected` and the latter `disconnected` messages are the only ones that - // are sent from the Rust IPC code and not just forwarded from the desktop app. - // As it's only two, we hardcode the JSON values to avoid pulling in a JSON library. - send.send("{\"command\":\"connected\"}".to_owned()).await?; - - let mut buffer = vec![0; NATIVE_MESSAGING_BUFFER_SIZE]; - - // Listen to IPC messages - loop { - tokio::select! { - // Forward messages to the IPC server - msg = recv.recv() => { - match msg { - Some(msg) => { - conn.write_all(msg.as_bytes()).await?; - } - None => { - info!("Client channel closed"); - break; - }, - } - }, - - // Forward messages from the IPC server - res = conn.read(&mut buffer[..]) => { - match res { - Err(e) => { - error!("Error reading from IPC server: {e}"); - break; - } - Ok(0) => { - info!("Connection closed"); - break; - } - Ok(n) => { - let message = String::from_utf8_lossy(&buffer[..n]).to_string(); - send.send(message).await?; - } - } - } - } - } - - let _ = send.send("{\"command\":\"disconnected\"}".to_owned()).await; - - Ok(()) -} diff --git a/apps/desktop/desktop_native/core/src/ipc/mod.rs b/apps/desktop/desktop_native/core/src/ipc/mod.rs deleted file mode 100644 index 6117dab38c..0000000000 --- a/apps/desktop/desktop_native/core/src/ipc/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -pub mod client; -pub mod server; - -/// The maximum size of a message that can be sent over IPC. -/// According to the documentation, the maximum size sent to the browser is 1MB. -/// While the maximum size sent from the browser to the native messaging host is 4GB. -/// -/// Currently we are setting the maximum both ways to be 1MB. -/// -/// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging#app_side -/// https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging#native-messaging-host-protocol -pub const NATIVE_MESSAGING_BUFFER_SIZE: usize = 1024 * 1024; - -/// The maximum number of messages that can be buffered in a channel. -/// This number is more or less arbitrary and can be adjusted as needed, -/// but ideally the messages should be processed as quickly as possible. -pub const MESSAGE_CHANNEL_BUFFER: usize = 32; - -/// Resolve the path to the IPC socket. -pub fn path(name: &str) -> std::path::PathBuf { - #[cfg(target_os = "windows")] - { - // Use a unique IPC pipe //./pipe/xxxxxxxxxxxxxxxxx.app.bitwarden per user. - // Hashing prevents problems with reserved characters and file length limitations. - use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; - use sha2::Digest; - let home = dirs::home_dir().unwrap(); - let hash = sha2::Sha256::digest(home.as_os_str().as_encoded_bytes()); - let hash_b64 = URL_SAFE_NO_PAD.encode(hash.as_slice()); - - format!(r"\\.\pipe\{hash_b64}.app.{name}").into() - } - - #[cfg(target_os = "macos")] - { - let mut home = dirs::home_dir().unwrap(); - - // When running in an unsandboxed environment, path is: /Users// - // While running sandboxed, it's different: /Users//Library/Containers/com.bitwarden.desktop/Data - // - // We want to use App Groups in /Users//Library/Group Containers/LTZ2PFU5D6.com.bitwarden.desktop, - // so we need to remove all the components after the user. - // Note that we subtract 3 because the root directory is counted as a component (/, Users, ). - let num_components = home.components().count(); - for _ in 0..num_components - 3 { - home.pop(); - } - - home.join(format!( - "Library/Group Containers/LTZ2PFU5D6.com.bitwarden.desktop/tmp/app.{name}" - )) - } - - #[cfg(target_os = "linux")] - { - // On Linux, we use the user's cache directory. - let home = dirs::cache_dir().unwrap(); - let path_dir = home.join("com.bitwarden.desktop"); - - // The chache directory might not exist, so create it - let _ = std::fs::create_dir_all(&path_dir); - path_dir.join(format!("app.{name}")) - } -} diff --git a/apps/desktop/desktop_native/core/src/ipc/server.rs b/apps/desktop/desktop_native/core/src/ipc/server.rs deleted file mode 100644 index 053b432220..0000000000 --- a/apps/desktop/desktop_native/core/src/ipc/server.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::{error::Error, path::Path, vec}; - -use futures::TryFutureExt; - -use anyhow::Result; -use interprocess::local_socket::{tokio::prelude::*, GenericFilePath, ListenerOptions}; -use log::{error, info}; -use tokio::{ - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, - sync::{broadcast, mpsc}, -}; -use tokio_util::sync::CancellationToken; - -use super::{MESSAGE_CHANNEL_BUFFER, NATIVE_MESSAGING_BUFFER_SIZE}; - -#[derive(Debug)] -pub struct Message { - pub client_id: u32, - pub kind: MessageType, - // This value should be Some for MessageType::Message and None for the rest - pub message: Option, -} - -#[derive(Debug)] -pub enum MessageType { - Connected, - Disconnected, - Message, -} - -pub struct Server { - cancel_token: CancellationToken, - server_to_clients_send: broadcast::Sender, -} - -impl Server { - /// Create and start the IPC server without blocking. - /// - /// # Parameters - /// - /// - `name`: The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - /// - `client_to_server_send`: This [`mpsc::Sender`] will receive all the [`Message`]'s that the clients send to this server. - pub fn start( - path: &Path, - client_to_server_send: mpsc::Sender, - ) -> Result> { - // If the unix socket file already exists, we get an error when trying to bind to it. So we remove it first. - // Any processes that were using the old socket should remain connected to it but any new connections will use the new socket. - if !cfg!(windows) { - let _ = std::fs::remove_file(path); - } - - let name = path.as_os_str().to_fs_name::()?; - let opts = ListenerOptions::new().name(name); - let listener = opts.create_tokio()?; - - // This broadcast channel is used for sending messages to all connected clients, and so the sender - // will be stored in the server while the receiver will be cloned and passed to each client handler. - let (server_to_clients_send, server_to_clients_recv) = - broadcast::channel::(MESSAGE_CHANNEL_BUFFER); - - // This cancellation token allows us to cleanly stop the server and all the spawned - // tasks without having to wait on all the pending tasks finalizing first - let cancel_token = CancellationToken::new(); - - // Create the server and start listening for incoming connections - // in a separate task to avoid blocking the current task - let server = Server { - cancel_token: cancel_token.clone(), - server_to_clients_send, - }; - tokio::spawn(listen_incoming( - listener, - client_to_server_send, - server_to_clients_recv, - cancel_token, - )); - - Ok(server) - } - - /// Send a message over the IPC server to all the connected clients - /// - /// # Returns - /// - /// The number of clients that the message was sent to. Note that the number of messages - /// sent may be less than the number of connected clients if some clients disconnect while - /// the message is being sent. - pub fn send(&self, message: String) -> Result { - let sent = self.server_to_clients_send.send(message)?; - Ok(sent) - } - - /// Stop the IPC server. - pub fn stop(&self) { - self.cancel_token.cancel(); - } -} - -impl Drop for Server { - fn drop(&mut self) { - self.stop(); - } -} - -async fn listen_incoming( - listener: LocalSocketListener, - client_to_server_send: mpsc::Sender, - server_to_clients_recv: broadcast::Receiver, - cancel_token: CancellationToken, -) { - // We use a simple incrementing ID for each client - let mut next_client_id = 1_u32; - - loop { - tokio::select! { - _ = cancel_token.cancelled() => { - info!("IPC server cancelled."); - break; - }, - - // A new client connection has been established - msg = listener.accept() => { - match msg { - Ok(client_stream) => { - let client_id = next_client_id; - next_client_id += 1; - - let future = handle_connection( - client_stream, - client_to_server_send.clone(), - // We resubscribe to the receiver here so this task can have it's own copy - // Note that this copy will only receive messages sent after this point, - // but that is okay, realistically we don't want any messages before we get a chance - // to send the connected message to the client, which is done inside [`handle_connection`] - server_to_clients_recv.resubscribe(), - cancel_token.clone(), - client_id - ); - tokio::spawn(future.map_err(|e| { - error!("Error handling connection: {}", e) - })); - }, - Err(e) => { - error!("Error accepting connection: {}", e); - break; - }, - } - } - } - } -} - -async fn handle_connection( - mut client_stream: impl AsyncRead + AsyncWrite + Unpin, - client_to_server_send: mpsc::Sender, - mut server_to_clients_recv: broadcast::Receiver, - cancel_token: CancellationToken, - client_id: u32, -) -> Result<(), Box> { - client_to_server_send - .send(Message { - client_id, - kind: MessageType::Connected, - message: None, - }) - .await?; - - let mut buf = vec![0u8; NATIVE_MESSAGING_BUFFER_SIZE]; - - loop { - tokio::select! { - _ = cancel_token.cancelled() => { - info!("Client {client_id} cancelled."); - break; - }, - - // Forward messages to the IPC clients - msg = server_to_clients_recv.recv() => { - match msg { - Ok(msg) => { - client_stream.write_all(msg.as_bytes()).await?; - }, - Err(e) => { - info!("Error reading message: {}", e); - break; - } - } - }, - - // Forwards messages from the IPC clients to the server - // Note that we also send connect and disconnect events so that - // the server can keep track of multiple clients - result = client_stream.read(&mut buf) => { - match result { - Err(e) => { - info!("Error reading from client {client_id}: {e}"); - - client_to_server_send.send(Message { - client_id, - kind: MessageType::Disconnected, - message: None, - }).await?; - break; - }, - Ok(0) => { - info!("Client {client_id} disconnected."); - - client_to_server_send.send(Message { - client_id, - kind: MessageType::Disconnected, - message: None, - }).await?; - break; - }, - Ok(size) => { - let msg = std::str::from_utf8(&buf[..size])?; - - client_to_server_send.send(Message { - client_id, - kind: MessageType::Message, - message: Some(msg.to_string()), - }).await?; - }, - - } - } - } - } - - Ok(()) -} diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index 3132c56f7f..d23a285b4a 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -1,13 +1,7 @@ -#[cfg(feature = "sys")] pub mod biometric; -#[cfg(feature = "sys")] pub mod clipboard; pub mod crypto; pub mod error; -pub mod ipc; -#[cfg(feature = "sys")] pub mod password; -#[cfg(feature = "sys")] pub mod process_isolation; -#[cfg(feature = "sys")] pub mod powermonitor; diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index 6fb710b067..942ccdba21 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -16,10 +16,8 @@ manual_test = [] [dependencies] anyhow = "=1.0.86" desktop_core = { path = "../core" } -napi = { version = "=2.16.7", features = ["async"] } -napi-derive = "=2.16.6" -tokio = { version = "1.38.0" } -tokio-util = "0.7.11" +napi = { version = "=2.16.6", features = ["async"] } +napi-derive = "=2.16.5" [build-dependencies] napi-build = "=2.1.3" diff --git a/apps/desktop/desktop_native/napi/build.js b/apps/desktop/desktop_native/napi/build.js new file mode 100644 index 0000000000..6c92dbad1b --- /dev/null +++ b/apps/desktop/desktop_native/napi/build.js @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const child_process = require("child_process"); +const process = require("process"); + +let targets = []; +switch (process.platform) { + case "win32": + targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"]; + break; + + case "darwin": + targets = ["x86_64-apple-darwin", "aarch64-apple-darwin"]; + break; + + default: + targets = ['x86_64-unknown-linux-musl']; + process.env["PKG_CONFIG_ALLOW_CROSS"] = "1"; + process.env["PKG_CONFIG_ALL_STATIC"] = "1"; + break; +} + +targets.forEach(target => { + child_process.execSync(`npm run build -- --target ${target}`, {stdio: 'inherit'}); +}); diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index fe4ab59fd8..deaf6b8e57 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -51,33 +51,3 @@ export namespace powermonitors { export function onLock(callback: (err: Error | null, ) => any): Promise export function isLockMonitorAvailable(): Promise } -export namespace ipc { - export interface IpcMessage { - clientId: number - kind: IpcMessageType - message?: string - } - export const enum IpcMessageType { - Connected = 0, - Disconnected = 1, - Message = 2 - } - export class IpcServer { - /** - * Create and start the IPC server without blocking. - * - * @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - * @param callback This function will be called whenever a message is received from a client. - */ - static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise - /** Stop the IPC server. */ - stop(): void - /** - * Send a message over the IPC server to all the connected clients - * - * @return The number of clients that the message was sent to. Note that the number of messages - * actually received may be less, as some clients could disconnect before receiving the message. - */ - send(message: string): number - } -} diff --git a/apps/desktop/desktop_native/napi/index.js b/apps/desktop/desktop_native/napi/index.js index a0cfee8e1a..680f1302b9 100644 --- a/apps/desktop/desktop_native/napi/index.js +++ b/apps/desktop/desktop_native/napi/index.js @@ -206,4 +206,10 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -module.exports = nativeBinding +const { passwords, biometrics, clipboards, processisolations, powermonitors } = nativeBinding + +module.exports.passwords = passwords +module.exports.biometrics = biometrics +module.exports.clipboards = clipboards +module.exports.processisolations = processisolations +module.exports.powermonitors = powermonitors diff --git a/apps/desktop/desktop_native/napi/package.json b/apps/desktop/desktop_native/napi/package.json index 9f098c4965..70e472b395 100644 --- a/apps/desktop/desktop_native/napi/package.json +++ b/apps/desktop/desktop_native/napi/package.json @@ -3,7 +3,9 @@ "version": "0.1.0", "description": "", "scripts": { - "build": "napi build --platform --js false", + "build": "napi build --release --platform --js false", + "build:debug": "napi build --platform --js false", + "build:cross-platform": "node build.js", "test": "cargo test" }, "author": "", diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 838eb65124..dfdc316d25 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -189,103 +189,3 @@ pub mod powermonitors { } } - -#[napi] -pub mod ipc { - use desktop_core::ipc::server::{Message, MessageType}; - use napi::threadsafe_function::{ - ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode, - }; - - #[napi(object)] - pub struct IpcMessage { - pub client_id: u32, - pub kind: IpcMessageType, - pub message: Option, - } - - impl From for IpcMessage { - fn from(message: Message) -> Self { - IpcMessage { - client_id: message.client_id, - kind: message.kind.into(), - message: message.message, - } - } - } - - #[napi] - pub enum IpcMessageType { - Connected, - Disconnected, - Message, - } - - impl From for IpcMessageType { - fn from(message_type: MessageType) -> Self { - match message_type { - MessageType::Connected => IpcMessageType::Connected, - MessageType::Disconnected => IpcMessageType::Disconnected, - MessageType::Message => IpcMessageType::Message, - } - } - } - - #[napi] - pub struct IpcServer { - server: desktop_core::ipc::server::Server, - } - - #[napi] - impl IpcServer { - /// Create and start the IPC server without blocking. - /// - /// @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - /// @param callback This function will be called whenever a message is received from a client. - #[napi(factory)] - pub async fn listen( - name: String, - #[napi(ts_arg_type = "(error: null | Error, message: IpcMessage) => void")] - callback: ThreadsafeFunction, - ) -> napi::Result { - let (send, mut recv) = tokio::sync::mpsc::channel::(32); - tokio::spawn(async move { - while let Some(message) = recv.recv().await { - callback.call(Ok(message.into()), ThreadsafeFunctionCallMode::NonBlocking); - } - }); - - let path = desktop_core::ipc::path(&name); - - let server = desktop_core::ipc::server::Server::start(&path, send).map_err(|e| { - napi::Error::from_reason(format!( - "Error listening to server - Path: {path:?} - Error: {e} - {e:?}" - )) - })?; - - Ok(IpcServer { server }) - } - - /// Stop the IPC server. - #[napi] - pub fn stop(&self) -> napi::Result<()> { - self.server.stop(); - Ok(()) - } - - /// Send a message over the IPC server to all the connected clients - /// - /// @return The number of clients that the message was sent to. Note that the number of messages - /// actually received may be less, as some clients could disconnect before receiving the message. - #[napi] - pub fn send(&self, message: String) -> napi::Result { - self.server - .send(message) - .map_err(|e| { - napi::Error::from_reason(format!("Error sending message - Error: {e} - {e:?}")) - }) - // NAPI doesn't support u64 or usize, so we need to convert to u32 - .map(|u| u32::try_from(u).unwrap_or_default()) - } - } -} diff --git a/apps/desktop/desktop_native/proxy/Cargo.toml b/apps/desktop/desktop_native/proxy/Cargo.toml deleted file mode 100644 index 681c34c8ea..0000000000 --- a/apps/desktop/desktop_native/proxy/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -edition = "2021" -exclude = ["index.node"] -license = "GPL-3.0" -name = "desktop_proxy" -version = "0.0.0" -publish = false - -[dependencies] -anyhow = "=1.0.86" -desktop_core = { path = "../core", default-features = false } -futures = "0.3.30" -log = "0.4.21" -simplelog = "0.12.2" -tokio = { version = "1.38.0", features = ["io-std", "io-util", "macros", "rt"] } -tokio-util = { version = "0.7.11", features = ["codec"] } - -[target.'cfg(target_os = "macos")'.dependencies] -embed_plist = "1.2.2" diff --git a/apps/desktop/desktop_native/proxy/src/main.rs b/apps/desktop/desktop_native/proxy/src/main.rs deleted file mode 100644 index 776b363598..0000000000 --- a/apps/desktop/desktop_native/proxy/src/main.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::path::Path; - -use desktop_core::ipc::{MESSAGE_CHANNEL_BUFFER, NATIVE_MESSAGING_BUFFER_SIZE}; -use futures::{SinkExt, StreamExt}; -use log::*; -use tokio_util::codec::LengthDelimitedCodec; - -#[cfg(target_os = "macos")] -embed_plist::embed_info_plist!("../../../resources/info.desktop_proxy.plist"); - -fn init_logging(log_path: &Path, level: log::LevelFilter) { - use simplelog::{ColorChoice, CombinedLogger, Config, SharedLogger, TermLogger, TerminalMode}; - - let config = Config::default(); - - let mut loggers: Vec> = Vec::new(); - loggers.push(TermLogger::new( - level, - config.clone(), - TerminalMode::Stderr, - ColorChoice::Auto, - )); - - match std::fs::File::create(log_path) { - Ok(file) => { - loggers.push(simplelog::WriteLogger::new(level, config, file)); - } - Err(e) => { - eprintln!("Can't create file: {}", e); - } - } - - if let Err(e) = CombinedLogger::init(loggers) { - eprintln!("Failed to initialize logger: {}", e); - } -} - -/// Bitwarden IPC Proxy. -/// -/// This proxy allows browser extensions to communicate with a desktop application using Native -/// Messaging. This method allows an extension to send and receive messages through the use of -/// stdin/stdout streams. -/// -/// However, this also requires the browser to start the process in order for the communication to -/// occur. To overcome this limitation, we implement Inter-Process Communication (IPC) to establish -/// a stable communication channel between the proxy and the running desktop application. -/// -/// Browser extension <-[native messaging]-> proxy <-[ipc]-> desktop -/// -#[tokio::main(flavor = "current_thread")] -async fn main() { - let sock_path = desktop_core::ipc::path("bitwarden"); - - let log_path = { - let mut path = sock_path.clone(); - path.set_extension("bitwarden.log"); - path - }; - - init_logging(&log_path, LevelFilter::Info); - - info!("Starting Bitwarden IPC Proxy."); - - // Different browsers send different arguments when the app starts: - // - // Firefox: - // - The complete path to the app manifest. (in the form `/Users//Library/.../Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json`) - // - (in Firefox 55+) the ID (as given in the manifest.json) of the add-on that started it (in the form `{[UUID]}`). - // - // Chrome on Windows: - // - Origin of the extension that started it (in the form `chrome-extension://[ID]`). - // - Handle to the Chrome native window that started the app. - // - // Chrome on Linux and Mac: - // - Origin of the extension that started it (in the form `chrome-extension://[ID]`). - - let args: Vec<_> = std::env::args().skip(1).collect(); - info!("Process args: {:?}", args); - - // Setup two channels, one for sending messages to the desktop application (`out`) and one for receiving messages from the desktop application (`in`) - let (in_send, in_recv) = tokio::sync::mpsc::channel(MESSAGE_CHANNEL_BUFFER); - let (out_send, mut out_recv) = tokio::sync::mpsc::channel(MESSAGE_CHANNEL_BUFFER); - - let mut handle = tokio::spawn(desktop_core::ipc::client::connect( - sock_path, out_send, in_recv, - )); - - // Create a new codec for reading and writing messages from stdin/stdout. - let mut stdin = LengthDelimitedCodec::builder() - .max_frame_length(NATIVE_MESSAGING_BUFFER_SIZE) - .native_endian() - .new_read(tokio::io::stdin()); - let mut stdout = LengthDelimitedCodec::builder() - .max_frame_length(NATIVE_MESSAGING_BUFFER_SIZE) - .native_endian() - .new_write(tokio::io::stdout()); - - loop { - tokio::select! { - // IPC client has finished, so we should exit as well. - _ = &mut handle => { - break; - } - - // Receive messages from IPC and print to STDOUT. - msg = out_recv.recv() => { - match msg { - Some(msg) => { - debug!("OUT: {}", msg); - stdout.send(msg.into()).await.unwrap(); - } - None => { - info!("Channel closed, exiting."); - break; - } - } - }, - - // Listen to stdin and send messages to ipc processor. - msg = stdin.next() => { - match msg { - Some(Ok(msg)) => { - let m = String::from_utf8(msg.to_vec()).unwrap(); - debug!("IN: {}", m); - in_send.send(m).await.unwrap(); - } - Some(Err(e)) => { - error!("Error parsing input: {}", e); - break; - } - None => { - info!("Received EOF, exiting."); - break; - } - } - } - - } - } -} diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 02bb237776..b6572587fa 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -73,13 +73,6 @@ "CFBundleDevelopmentRegion": "en" }, "singleArchFiles": "node_modules/@bitwarden/desktop-napi/desktop_napi.darwin-*.node", - "extraFiles": [ - { - "from": "desktop_native/dist/desktop_proxy.${platform}-${arch}", - "to": "MacOS/desktop_proxy" - } - ], - "signIgnore": ["MacOS/desktop_proxy"], "target": ["dmg", "zip"] }, "win": { @@ -91,24 +84,16 @@ "from": "../../node_modules/regedit/vbs", "to": "regedit/vbs", "filter": ["**/*"] - } - ], - "extraFiles": [ + }, { - "from": "desktop_native/dist/desktop_proxy.${platform}-${arch}.exe", - "to": "desktop_proxy.exe" + "from": "resources/native-messaging.bat", + "to": "native-messaging.bat" } ] }, "linux": { "category": "Utility", "synopsis": "A secure and free password manager for all of your devices.", - "extraFiles": [ - { - "from": "desktop_native/dist/desktop_proxy.${platform}-${arch}", - "to": "desktop_proxy" - } - ], "target": ["deb", "freebsd", "rpm", "AppImage", "snap"], "desktop": { "Name": "Bitwarden", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 70a0d9cb8c..4562653978 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -18,7 +18,7 @@ "scripts": { "postinstall": "electron-rebuild", "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", - "build-native": "cd desktop_native && node build.js", + "build-native": "cd desktop_native/napi && npm run build", "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", diff --git a/apps/desktop/resources/entitlements.desktop_proxy.plist b/apps/desktop/resources/entitlements.desktop_proxy.plist deleted file mode 100644 index d5c7b8a2cc..0000000000 --- a/apps/desktop/resources/entitlements.desktop_proxy.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.application-groups - - LTZ2PFU5D6.com.bitwarden.desktop - - - diff --git a/apps/desktop/resources/entitlements.mas.plist b/apps/desktop/resources/entitlements.mas.plist index d42ade962c..5bfeba83a6 100644 --- a/apps/desktop/resources/entitlements.mas.plist +++ b/apps/desktop/resources/entitlements.mas.plist @@ -8,10 +8,6 @@ LTZ2PFU5D6 com.apple.security.app-sandbox - com.apple.security.application-groups - - LTZ2PFU5D6.com.bitwarden.desktop - com.apple.security.network.client com.apple.security.files.user-selected.read-write diff --git a/apps/desktop/resources/info.desktop_proxy.plist b/apps/desktop/resources/info.desktop_proxy.plist deleted file mode 100644 index d3c30e3e0e..0000000000 --- a/apps/desktop/resources/info.desktop_proxy.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - CFBundleIdentifier - com.bitwarden.desktop - - diff --git a/apps/desktop/resources/native-messaging.bat b/apps/desktop/resources/native-messaging.bat new file mode 100644 index 0000000000..45519250dd --- /dev/null +++ b/apps/desktop/resources/native-messaging.bat @@ -0,0 +1,7 @@ +@echo off +:: Helper script for starting the Native Messaging Proxy on Windows. + +cd ../ +set ELECTRON_RUN_AS_NODE=1 +set ELECTRON_NO_ATTACH_CONSOLE=1 +Bitwarden.exe resources/app.asar %* diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js index 08cff76e85..e128397e61 100644 --- a/apps/desktop/scripts/after-pack.js +++ b/apps/desktop/scripts/after-pack.js @@ -1,22 +1,14 @@ /* eslint-disable @typescript-eslint/no-var-requires, no-console */ require("dotenv").config(); -const child_process = require("child_process"); const path = require("path"); -const { flipFuses, FuseVersion, FuseV1Options } = require("@electron/fuses"); -const builder = require("electron-builder"); const fse = require("fs-extra"); exports.default = run; async function run(context) { console.log("## After pack"); - // console.log(context); - - if (context.packager.platform.nodeName !== "darwin" || context.arch === builder.Arch.universal) { - await addElectronFuses(context); - } - + console.log(context); if (context.electronPlatformName === "linux") { console.log("Creating memory-protection wrapper script"); const appOutDir = context.appOutDir; @@ -31,132 +23,4 @@ async function run(context) { fse.chmodSync(wrapperBin, "755"); console.log("Copied memory-protection wrapper script"); } - - if (["darwin", "mas"].includes(context.electronPlatformName)) { - const is_mas = context.electronPlatformName === "mas"; - const is_mas_dev = context.targets.some((e) => e.name === "mas-dev"); - - let id; - - // Only use the Bitwarden Identities on CI - if (process.env.GITHUB_ACTIONS === "true") { - if (is_mas) { - id = is_mas_dev - ? "E7C9978F6FBCE0553429185C405E61F5380BE8EB" - : "3rd Party Mac Developer Application: Bitwarden Inc"; - } else { - id = "Developer ID Application: 8bit Solutions LLC"; - } - // Locally, use the first valid code signing identity, unless CSC_NAME is set - } else if (process.env.CSC_NAME) { - id = process.env.CSC_NAME; - } else { - const identities = getIdentities(); - if (identities.length === 0) { - throw new Error("No valid identities found"); - } - id = identities[0].id; - } - - console.log(`Signing proxy binary before the main bundle, using identity '${id}'`); - - const appName = context.packager.appInfo.productFilename; - const appPath = `${context.appOutDir}/${appName}.app`; - const proxyPath = path.join(appPath, "Contents", "MacOS", "desktop_proxy"); - - const packageId = "com.bitwarden.desktop"; - const entitlementsName = "entitlements.desktop_proxy.plist"; - const entitlementsPath = path.join(__dirname, "..", "resources", entitlementsName); - child_process.execSync( - `codesign -s '${id}' -i ${packageId} -f --timestamp --options runtime --entitlements ${entitlementsPath} ${proxyPath}`, - ); - } -} - -// Partially based on electron-builder code: -// https://github.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/macPackager.ts -// https://github.com/electron-userland/electron-builder/blob/master/packages/app-builder-lib/src/codeSign/macCodeSign.ts - -const appleCertificatePrefixes = [ - "Developer ID Application:", - // "Developer ID Installer:", - // "3rd Party Mac Developer Application:", - // "3rd Party Mac Developer Installer:", - "Apple Development:", -]; - -function getIdentities() { - const ids = child_process - .execSync("/usr/bin/security find-identity -v -p codesigning") - .toString(); - - return ids - .split("\n") - .filter((line) => { - for (const prefix of appleCertificatePrefixes) { - if (line.includes(prefix)) { - return true; - } - } - return false; - }) - .map((line) => { - const split = line.trim().split(" "); - const id = split[1]; - const name = split.slice(2).join(" ").replace(/"/g, ""); - return { id, name }; - }); -} - -/** - * @param {import("electron-builder").AfterPackContext} context - */ -async function addElectronFuses(context) { - const platform = context.packager.platform.nodeName; - - const ext = { - darwin: ".app", - win32: ".exe", - linux: "", - }[platform]; - - const IS_LINUX = platform === "linux"; - const executableName = IS_LINUX - ? context.packager.appInfo.productFilename.toLowerCase().replace("-dev", "").replace(" ", "-") - : context.packager.appInfo.productFilename; // .toLowerCase() to accomodate Linux file named `name` but productFileName is `Name` -- Replaces '-dev' because on Linux the executable name is `name` even for the DEV builds - - const electronBinaryPath = path.join(context.appOutDir, `${executableName}${ext}`); - - console.log("## Adding fuses to the electron binary", electronBinaryPath); - - await flipFuses(electronBinaryPath, { - version: FuseVersion.V1, - strictlyRequireAllFuses: true, - resetAdHocDarwinSignature: platform === "darwin" && context.arch === builder.Arch.universal, - - // List of fuses and their default values is available at: - // https://www.electronjs.org/docs/latest/tutorial/fuses - - [FuseV1Options.RunAsNode]: false, - [FuseV1Options.EnableCookieEncryption]: true, - [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, - [FuseV1Options.EnableNodeCliInspectArguments]: false, - - // Currently, asar integrity is only implemented for macOS and Windows - // https://www.electronjs.org/docs/latest/tutorial/asar-integrity - // On macOS, it works by default, but on Windows it requires the - // asarIntegrity feature of electron-builder v25, currently in alpha - // https://github.com/electron-userland/electron-builder/releases/tag/v25.0.0-alpha.10 - [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: platform === "darwin", - - [FuseV1Options.OnlyLoadAppFromAsar]: true, - - // App refuses to open when enabled - [FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]: false, - - // To disable this, we should stop using the file:// protocol to load the app bundle - // This can be done by defining a custom app:// protocol and loading the bundle from there, - // but then any requests to the server will be blocked by CORS policy - [FuseV1Options.GrantFileProtocolExtraPrivileges]: true, - }); } diff --git a/apps/desktop/src/entry.ts b/apps/desktop/src/entry.ts index 3bb8446136..78fe51e8b9 100644 --- a/apps/desktop/src/entry.ts +++ b/apps/desktop/src/entry.ts @@ -1,33 +1,31 @@ -import { spawn } from "child_process"; -import * as path from "path"; +import { NativeMessagingProxy } from "./proxy/native-messaging-proxy"; -import { app } from "electron"; +// We need to import the other dependencies using `require` since `import` will +// generate `Error: Cannot find module 'electron'`. The cause of this error is +// due to native messaging setting the ELECTRON_RUN_AS_NODE env flag on windows +// which removes the electron module. This flag is needed for stdin/out to work +// properly on Windows. if ( - process.platform === "darwin" && process.argv.some((arg) => arg.indexOf("chrome-extension://") !== -1 || arg.indexOf("{") !== -1) ) { - // If we're on MacOS, we need to support DuckDuckGo's IPC communication, - // which for the moment is launching the Bitwarden process. - // Ideally the browser would instead startup the desktop_proxy process - // when available, but for now we'll just launch it here. + if (process.platform === "darwin") { + // eslint-disable-next-line + const app = require("electron").app; - app.on("ready", () => { - app.dock.hide(); + app.on("ready", () => { + app.dock.hide(); + }); + } + + process.stdout.on("error", (e) => { + if (e.code === "EPIPE") { + process.exit(0); + } }); - const proc = spawn(path.join(process.execPath, "..", "desktop_proxy"), process.argv.slice(1), { - cwd: process.cwd(), - stdio: "inherit", - shell: false, - }); - - proc.on("exit", () => { - process.exit(0); - }); - proc.on("error", () => { - process.exit(1); - }); + const proxy = new NativeMessagingProxy(); + proxy.run(); } else { // eslint-disable-next-line const Main = require("./main").Main; diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index f56a342d37..8c31a6e702 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -865,7 +865,7 @@ "message": "Bizi izləyin" }, "syncVault": { - "message": "Anbarı eyniləşdir" + "message": "Anbarı sinxronlaşdır" }, "changeMasterPass": { "message": "Ana parolu dəyişdir" @@ -894,10 +894,10 @@ "message": "Brauzer uzantısını al" }, "syncingComplete": { - "message": "Eyniləşdirmə tamamlandı" + "message": "Sinxr tamamlandı" }, "syncingFailed": { - "message": "Uğursuz eyniləşdirmə" + "message": "Sinxr uğursuz oldu" }, "yourVaultIsLocked": { "message": "Anbarınız kilidlənib. Davam etmək üçün kimliyinizi doğrulayın." @@ -928,10 +928,10 @@ "message": "İki mərhələli giriş" }, "vaultTimeout": { - "message": "Anbara müraciət bitəcək" + "message": "Anbar vaxtının bitməsi" }, "vaultTimeoutDesc": { - "message": "Anbara müraciətin bitəcəyi vaxtı seçin və seçilən əməliyyatı icra edin." + "message": "Anbarın vaxt bitmə əməliyyatını nə vaxt icra edəcəyini seçin." }, "immediately": { "message": "Dərhal" @@ -1617,13 +1617,13 @@ "message": "Bir və ya daha çox təşkilat siyasətləri yaradıcı seçimlərinizə təsir edir." }, "vaultTimeoutAction": { - "message": "Anbara müraciət vaxtının bitmə əməliyyatı" + "message": "Anbar vaxtının bitmə əməliyyatı" }, "vaultTimeoutActionLockDesc": { - "message": "Kilidli bir anbar, təkrar müraciət etmək üçün ana parolunuzu yenidən yazmağınızı tələb edir." + "message": "Anbarınıza təkrar müraciət etmək üçün ana parol və ya digər kilid açma üsulu tələb olunur." }, "vaultTimeoutActionLogOutDesc": { - "message": "Anbarınıza yenidən müraciət etmək üçün təkrar kimlik doğrulama tələb olunur." + "message": "Anbarınıza təkrar müraciət etmək üçün təkrar kimlik doğrulama tələb olunur." }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Anbar vaxt bitməsi əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu quraşdırın." diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 2d661f6db4..033da99272 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -404,7 +404,7 @@ "message": "Pikkus" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "Parooli miinimumpikkus" }, "uppercase": { "message": "Suurtäht (A-Z)" @@ -479,7 +479,7 @@ "message": "Maksimaalne faili suurus on 500 MB." }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "Krüpteerimisvõtme ühendamine nõutud. Palun logi sisse läbi veebibrauseri, et uuendada enda krüpteerimisvõtit." }, "editedFolder": { "message": "Kaust on muudetud" @@ -500,10 +500,10 @@ "message": "Konto loomine" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Määra tugev parool" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Lõpeta konto loomine määrates parooli" }, "logIn": { "message": "Logi sisse" @@ -527,7 +527,7 @@ "message": "Ülemparooli vihje (ei ole kohustuslik)" }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "Kui sa unustad oma parooli, saad saata parooli vihje e-mailile.\n$CURRENT$/$MAXIMUM$ tähepiirang.", "placeholders": { "current": { "content": "$1", @@ -540,22 +540,22 @@ } }, "masterPassword": { - "message": "Master password" + "message": "Ülemparool" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Ülemparooli ei saa taastada, kui sa selle unustama peaksid!" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "Kinnita ülemparool" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Ülemparooli vihje" }, "joinOrganization": { - "message": "Join organization" + "message": "Liitu organisatsiooniga" }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Lõpeta organisatsiooniga liitumine määrates ülemparool." }, "settings": { "message": "Seaded" @@ -592,10 +592,10 @@ } }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Sisselogimine õnnestus" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Võid selle akna sulgeda" }, "masterPassDoesntMatch": { "message": "Ülemparoolid ei ühti." @@ -604,10 +604,10 @@ "message": "Sinu konto on loodud! Võid nüüd sisse logida." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "Uus konto loodud!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Olete sisse logitud!" }, "masterPassSent": { "message": "Ülemparooli vihje saadeti Sinu e-postile." @@ -640,7 +640,7 @@ "message": "Nõutav on kinnituskood." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "Autentimine tühistati või kestis liiga kaua aega. Palun proovi uuesti." }, "invalidVerificationCode": { "message": "Vale kinnituskood" @@ -694,17 +694,17 @@ "message": "Autentimise rakendus" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Sisesta oma autentikaatori (näiteks Bitwarden Authenticator) genereeritud kood.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP security key" + "message": "Yubico OTP turvavõti" }, "yubiKeyDesc": { "message": "Kasuta kontole ligipääsemiseks YubiKey-d. See töötab YubiKey 4, 4 Nano, 4C ja NEO seadmetega." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Sisesta Duo Security genereeritud kood.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -721,7 +721,7 @@ "message": "E-post" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "Sisesta oma emailile saadetud kood." }, "loginUnavailable": { "message": "Sisselogimine ei ole saadaval" @@ -742,13 +742,13 @@ "message": "Specify the base URL of your on-premise hosted bitwarden installation." }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "Sisesta enda ise-majutatud Bitwardeni serveri nimi (base URL). Näiteks: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "Täpsemaks seadistamiseks võid määrata serveri nime (base URL) igale teenusele eraldi." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Sa pead lisama serveri nime (base URL) või vähemalt ühe iseseadistatud keskkonna." }, "customEnvironment": { "message": "Kohandatud keskkond" @@ -799,22 +799,22 @@ "message": "Välja logitud" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "Kontolt edukalt välja logitud." }, "loginExpired": { "message": "Sessioon on aegunud." }, "restartRegistration": { - "message": "Restart registration" + "message": "Alusta registreerimist uuesti" }, "expiredLink": { - "message": "Expired link" + "message": "Aegunud link" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Palun alusta registreerimist uuesti või proovi sisse logida." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Sul on juba võib-olla konto" }, "logOutConfirmation": { "message": "Oled kindel, et soovid välja logida?" @@ -871,10 +871,10 @@ "message": "Muuda ülemparooli" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Jätka veebibrauseris?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Ülemparooli saab muuta Bitwardeni veebirakenduses." }, "fingerprintPhrase": { "message": "Unikaalne sõnajada", @@ -1160,7 +1160,7 @@ "message": "1 GB ulatuses krüpteeritud salvestusruum." }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "Eraomanduses kaheastmelise logimise valikud, nagu näiteks YubiKey ja Duo." }, "premiumSignUpReports": { "message": "Parooli hügieen, konto seisukord ja andmelekete raportid aitavad hoidlat turvalisena hoida." @@ -1181,7 +1181,7 @@ "message": "Saad Preemium versiooni osta bitwarden.com veebihoidlas. Soovid seda kohe teha?" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "Sa saad hankida Preemiumi oma konto seadetes veebibrauseris." }, "premiumCurrentMember": { "message": "Oled preemium kasutaja!" @@ -1286,10 +1286,10 @@ } }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Juurdepääsukoodi Värskendamine Ebaõnnestus" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Ei leidnud värskendamise koodi või API võtit. Palun proovi logida välja ja uuesti sisse." }, "help": { "message": "Abi" @@ -1379,7 +1379,7 @@ "description": "ex. Date this password was updated" }, "exportFrom": { - "message": "Export from" + "message": "Ekspordi asukohast" }, "exportVault": { "message": "Ekspordi hoidla" @@ -1388,31 +1388,31 @@ "message": "Failivorming" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "See eksporditav fail on parooliga kaitstud ja nõuab dekrüpteerimiseks parooli." }, "filePassword": { - "message": "File password" + "message": "Faili parool" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "message": "Seda parooli kasutatakse selle faili eksportimiseks ja importimiseks" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "Kasuta oma konto krüpteerimise võtit, mis koosneb sinu kasutajanimest ja ülemparoolist, et krüpteerida fail ja takistada selle importimine teistesse kontodesse." }, "passwordProtected": { - "message": "Password protected" + "message": "Parooliga kaitstud" }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "Määra faili parool, et see krüpteerida ja importida teise Bitwardeni kontosse kasutates seda parooli dekrüpteerimiseks." }, "exportTypeHeading": { - "message": "Export type" + "message": "Ekspordi tüüp" }, "accountRestricted": { - "message": "Account restricted" + "message": "Kontosisene" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"Faili parool\" ja \"Faili parooli kinnitus\" ei kattu." }, "hCaptchaUrl": { "message": "hCaptcha Url", @@ -1511,28 +1511,28 @@ "message": "Vale PIN kood." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Liiga palju ebaõnnestunud katseid. Login välja." }, "unlockWithWindowsHello": { "message": "Lukusta lahti Windows Helloga" }, "additionalWindowsHelloSettings": { - "message": "Additional Windows Hello settings" + "message": "Windows Hello lisaseaded" }, "unlockWithPolkit": { - "message": "Unlock with system authentication" + "message": "Ava arvuti autentimissüsteemiga" }, "windowsHelloConsentMessage": { "message": "Kinnita Bitwardenisse sisselogimine." }, "polkitConsentMessage": { - "message": "Authenticate to unlock Bitwarden." + "message": "Autentiteeri ennast Bitwardeni avamiseks." }, "unlockWithTouchId": { "message": "Lukusta lahti Touch ID-ga" }, "additionalTouchIdSettings": { - "message": "Additional Touch ID settings" + "message": "Muud Touch ID seaded" }, "touchIdConsentMessage": { "message": "Kinnita Bitwardenisse sisselogimine." @@ -1541,16 +1541,16 @@ "message": "Küsi avamisel Windows Hello tuvastust" }, "autoPromptPolkit": { - "message": "Ask for system authentication on launch" + "message": "Kasuta käivitamisel arvuti autentimissüsteemi" }, "autoPromptTouchId": { "message": "Küsi avamisel Touch ID tuvastust" }, "requirePasswordOnStart": { - "message": "Require password or PIN on app start" + "message": "Nõua parooli või PINi rakenduse kävitumisel" }, "recommendedForSecurity": { - "message": "Recommended for security." + "message": "Soovitatud turvalisuse huvides." }, "lockWithMasterPassOnRestart": { "message": "Lukusta ülemparooliga, kui rakendus taaskäivitatakse" @@ -1626,7 +1626,7 @@ "message": "Hoidlast väljalogimine nõuab taaskordseks ligipääsuks uut autentimist." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "Hoidla ajalõpu muutmiseks vali esmalt lahtilukustamise meetod." }, "lock": { "message": "Lukusta", @@ -1667,15 +1667,15 @@ "message": "Määra ülemparool" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "Teie organisatsiooni seadeid värskendati, mistõttu peate määrama ülemparooli.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "Sinu organisatsioon nõuab sult ülemparooli seadistamist.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "verificationRequired": { - "message": "Verification required", + "message": "Tuvastamine vajalik", "description": "Default title for the user verification dialog." }, "currentMasterPass": { @@ -1730,19 +1730,19 @@ "message": "Uus ülemparool ei vasta eeskirjades väljatoodud tingimustele." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Soovin saada nõuandeid, uudiseid ja pakkumisi Bitwardenilt oma postkasti." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Lõpeta tellimus" }, "atAnyTime": { - "message": "at any time." + "message": "iga hetk." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Jätkates nõustud sa" }, "and": { - "message": "and" + "message": "ja" }, "acceptPolicies": { "message": "Märkeruudu markeerimisel nõustud järgnevaga:" @@ -1766,10 +1766,10 @@ "message": "Brauseri integratsioon ei ole toetatud" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Brauseriga ühendamine ebaõnnestus" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Midagi läks valesti brauseriga ühendamisel." }, "browserIntegrationMasOnlyDesc": { "message": "Paraku on brauseri integratsioon hetkel toetatud ainult Mac App Store'i versioonis." @@ -1787,10 +1787,10 @@ "message": "See seadistus võimaldab täiendavat kaitset, küsides brauseriga ühendamisel teie unikaalset sõnajada. Sisselülitamisel nõuab see seadistus iga kord kasutaja kinnitust, kui luuakse ühendus brauseri ja töölaua rakenduse vahel." }, "enableHardwareAcceleration": { - "message": "Use hardware acceleration" + "message": "Kasuta riistvaralist kiirendamist" }, "enableHardwareAccelerationDesc": { - "message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required." + "message": "See seade on vaikimisi SEES. Lülita see VÄLJA kui sul tekib probleeme graafikaga. Arvuti tuleb pärast seda taaskäivitada." }, "approve": { "message": "Kinnita" @@ -1823,10 +1823,10 @@ "message": "Selleks, et kasutada biomeetriat brauseris, peab selle esmalt Bitwardeni töölaua rakenduse seadetes sisse lülitama." }, "biometricsManualSetupTitle": { - "message": "Automatic setup not available" + "message": "Automaatne seadistamine ei ole saadaval" }, "biometricsManualSetupDesc": { - "message": "Due to the installation method, biometrics support could not be automatically enabled. Would you like to open the documentation on how to do this manually?" + "message": "Tänu sinu installimise meetodile ei õnnestunud automaatselt biomeetria sisse lülitada. Kas soovid avada juhise kuidas seda käsitsi teha?" }, "personalOwnershipSubmitError": { "message": "Ettevõtte poliitika tõttu ei saa sa andmeid oma personaalsesse Hoidlasse salvestada. Vali Omanikuks organisatsioon ja vali mõni saadavaolevatest Kogumikest." @@ -1838,7 +1838,7 @@ "message": "Organisatsiooni poliitika on seadnud omaniku valikutele piirangu." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "Kirjete importimine isiklikku hoidlasse on organisatsiooni poolt keelatud." }, "allSends": { "message": "Kõik Sendid", @@ -2013,7 +2013,7 @@ "message": "Vajalik on e-posti kinnitamine" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Email kinnitatud" }, "emailVerificationRequiredDesc": { "message": "Enne selle funktsiooni kasutamist pead oma e-posti kinnitama." @@ -2040,43 +2040,43 @@ "message": "Sinu ülemparool ei vasta ühele või rohkemale organisatsiooni poolt seatud poliitikale. Hoidlale ligipääsemiseks pead oma ülemaprooli uuendama. Jätkamisel logitakse sind praegusest sessioonist välja, mistõttu pead uuesti sisse logima. Teistes seadmetes olevad aktiivsed sessioonid aeguvad umbes ühe tunni jooksul." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "Sinu organisatsioon on keelanud ära krüpteerimisvõtmete hoiustamise arvutites. Palun määra ülemparool oma hoidlale ligi pääsemiseks." }, "tryAgain": { - "message": "Try again" + "message": "Proovi uuesti" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "Selle muudatuse jõustamiseks on vaja teid kinnitada. Jätkamiseks sisestage PIN." }, "setPin": { - "message": "Set PIN" + "message": "Määra PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "Kinnita biomeetriaga" }, "awaitingConfirmation": { - "message": "Awaiting confirmation" + "message": "Ootan kinnitust" }, "couldNotCompleteBiometrics": { - "message": "Could not complete biometrics." + "message": "Biomeetria kasutamine ebaõnnestus." }, "needADifferentMethod": { - "message": "Need a different method?" + "message": "Soovid kasutada teist võimalust?" }, "useMasterPassword": { - "message": "Use master password" + "message": "Kasuta ülemparooli" }, "usePin": { - "message": "Use PIN" + "message": "Kasuta PIN-koodi" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Kasuta biomeetriat" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Sisesta oma e-posti aadressile saadetud kinnituskood." }, "resendCode": { - "message": "Resend code" + "message": "Saada kood uuesti" }, "hours": { "message": "Tundi" @@ -2127,7 +2127,7 @@ "message": "Valitud hoidla ajalõpp ei ole organisatsiooni poolt määratud reeglitega kooskõlas." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Kutse vastu võetud" }, "resetPasswordPolicyAutoEnroll": { "message": "Automaatne liitumine" @@ -2199,7 +2199,7 @@ "message": "Vaheta kontot" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "On juba konto?" }, "options": { "message": "Valikud" @@ -2220,10 +2220,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "Ekspordin organisatsiooni hoidlat" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Ainult organisatsiooniga $ORGANIZATION$ seotud kirjed eksportidakse. Personaalsete hoidlate ja teiste organisatsioonide kirjeid ei ekspordita.", "placeholders": { "organization": { "content": "$1", @@ -2310,11 +2310,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Genereeritud Bitwardeni poolt.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Veebisait: $WEBSITE$. Genereeritud Bitwardeni poolt.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2324,7 +2324,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Vigane $SERVICENAME$ API võti", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2334,7 +2334,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Vigane $SERVICENAME$ API võti: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2348,7 +2348,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Ei õnnestunud hankida pakkuja $SERVICENAME$ maskeeritud emaili konto ID-d.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2358,7 +2358,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Vigane $SERVICENAME$ domeen.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2368,7 +2368,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Vigane $SERVICENAME$ URL.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2378,7 +2378,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Tundmatu error pakkujaga $SERVICENAME$.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2388,7 +2388,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Tundmatu edastaja: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2450,7 +2450,7 @@ "message": "Logi sisse läbi teise seadme" }, "loginInitiated": { - "message": "Login initiated" + "message": "Alustan sisselogimist" }, "notificationSentDevice": { "message": "Sinu seadmesse saadeti teavitus." @@ -2548,25 +2548,25 @@ "message": "Sisselogimise päring on saadetud" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Loon kontot asukohas" }, "checkYourEmail": { - "message": "Check your email" + "message": "Kontrollige oma postkasti" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Ava sulle emailiga saadetud link" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "ja jätka konto loomist." }, "noEmail": { - "message": "No email?" + "message": "Pole emaili?" }, "goBack": { - "message": "Go back" + "message": "Tagasi" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "et muuta oma meiliaadressi." }, "exposedMasterPassword": { "message": "Ülemparool on haavatav" @@ -2587,10 +2587,10 @@ "message": "Tähtis:" }, "accessTokenUnableToBeDecrypted": { - "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + "message": "Sind logiti välja, sest sinu juurdepääsuvõtit (access token) ei õnnestunud dekrüpteerida. Probleemi lahendamiseks palun logige uuesti sisse." }, "refreshTokenSecureStorageRetrievalFailure": { - "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + "message": "Sind logiti välja, sest sinu uuendamisvõtit (refresh token) ei õnnestunud saada. Probleemi lahendamiseks palun logige uuesti sisse." }, "masterPasswordHint": { "message": "Ülemparooli ei saa taastada, kui sa selle unustama peaksid!" @@ -2605,83 +2605,83 @@ } }, "windowsBiometricUpdateWarning": { - "message": "Bitwarden recommends updating your biometric settings to require your master password (or PIN) on the first unlock. Would you like to update your settings now?" + "message": "Bitwarden soovitab muuta oma biomeetria seadeid, et nõuda esimesel sisselogimisel ülemparooli (või PINi). Kas soovid uuendada oma seadeid kohe?" }, "windowsBiometricUpdateWarningTitle": { - "message": "Recommended Settings Update" + "message": "Soovitatud Muudatus Seadetes" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Seadme kinnitamine on nõutud. Palun vali kuidas soovid seda teha:" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "Hoia see seade meeles" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Lülita see välja, kui oled avalikus seadmes" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Kinnita teises seadmes" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "Küsi administraatori nõusolekut" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "Kinnita ülemparooliga" }, "region": { - "message": "Region" + "message": "Piirkond" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "Organisatsiooni SSO identifikaator on nõutud." }, "eu": { - "message": "EU", + "message": "EL", "description": "European Union" }, "loggingInOn": { - "message": "Logging in on" + "message": "Login sisse aadressil" }, "selfHostedServer": { - "message": "self-hosted" + "message": "ise majutatud" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "Ligipääs keelatud. Sul pole lubatud seda lehekülge vaadata." }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Konto edukalt loodud!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "Taotlus administraatorile saadetud" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "Sinu taotlus saadeti administraatorile." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Kinnitamise järel saad selle kohta teavituse." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Ei õnnestu sisse logida?" }, "loginApproved": { - "message": "Login approved" + "message": "Sisselogimine kinnitatud" }, "userEmailMissing": { - "message": "User email missing" + "message": "Kasutaja email puudub" }, "deviceTrusted": { - "message": "Device trusted" + "message": "Usaldusväärne seade" }, "inputRequired": { - "message": "Input is required." + "message": "Sisend on nõutud." }, "required": { - "message": "required" + "message": "nõutud" }, "search": { - "message": "Search" + "message": "Otsi" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "Sisend peab olema vähemalt $COUNT$ tähemärki pikk.", "placeholders": { "count": { "content": "$1", @@ -2690,7 +2690,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Sisend ei tohi olla üle $COUNT$ tähemärgi pikkune.", "placeholders": { "count": { "content": "$1", @@ -2699,7 +2699,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "Järgnevad märgid pole lubatud: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -2708,7 +2708,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "Selle väärtus peab olema vähemalt $MIN$.", "placeholders": { "min": { "content": "$1", @@ -2717,7 +2717,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "Selle väärtus ei tohi ületada $MAX$.", "placeholders": { "max": { "content": "$1", @@ -2726,17 +2726,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "Üks või rohkem emaili on vigased" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "Sisend ei tohi koosneda ainult tühikutest.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "See pole e-posti aadress." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ välja nõuab tähelepanu.", "placeholders": { "count": { "content": "$1", @@ -2745,22 +2745,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Vali --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Filtreerimiseks trüki siia --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Valikute hankimine..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Ühtki kirjet ei leitud" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Tühjenda kõik" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ $QUANTITY$ veel", "placeholders": { "quantity": { "content": "$1", @@ -2769,47 +2769,47 @@ } }, "submenu": { - "message": "Submenu" + "message": "Alammenüü" }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Lülita sisse külgpaneel" }, "skipToContent": { - "message": "Skip to content" + "message": "Sisu juurde" }, "typePasskey": { - "message": "Passkey" + "message": "Pääsuvõti" }, "passkeyNotCopied": { - "message": "Passkey will not be copied" + "message": "Pääsuvõtit ei kopeerita" }, "passkeyNotCopiedAlert": { - "message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?" + "message": "Pääsukoodi ei kopeerita kloonitud kirjele. Oled kindel, et soovid jätkata?" }, "aliasDomain": { - "message": "Alias domain" + "message": "Varidomeen" }, "importData": { - "message": "Import data", + "message": "Impordi andmed", "description": "Used for the desktop menu item and the header of the import dialog" }, "importError": { - "message": "Import error" + "message": "Tõrge importimisel" }, "importErrorDesc": { - "message": "There was a problem with the data you tried to import. Please resolve the errors listed below in your source file and try again." + "message": "Andmete importimisel ilmnes tõrge. Paranda originaalfailis olevad vead (kuvatud all) ning proovi uuesti." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "Lahenda allolevad probleemid ja proovi uuesti." }, "description": { - "message": "Description" + "message": "Kirjeldus" }, "importSuccess": { - "message": "Data successfully imported" + "message": "Andmed edukalt imporditud" }, "importSuccessNumberOfItems": { - "message": "A total of $AMOUNT$ items were imported.", + "message": "Kokku imporditi $AMOUNT$ kirjet.", "placeholders": { "amount": { "content": "$1", @@ -2818,10 +2818,10 @@ } }, "total": { - "message": "Total" + "message": "Kokku" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "Impordid andmeid organisatsiooni $ORGANIZATION$. Neid andmeid võidakse jagada teiste organisatsiooni liikmetega. Soovid jätkata?", "placeholders": { "organization": { "content": "$1", @@ -2830,43 +2830,43 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "Duo teenustega ühendamine ebaõnnestus. Kasuta teist kahe-astmelise sisselogimise meetodit või kontakteeru Duoga." }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Käivita Duo ja järgi juhiseid, et lõpetada sisselogimine." }, "duoRequiredByOrgForAccount": { - "message": "Duo two-step login is required for your account." + "message": "Duo kahe-astmeline sisselogimine on sinu kontol nõutud." }, "launchDuo": { - "message": "Launch Duo in Browser" + "message": "Käivita Duo brauseris" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "Andmed ei ole korrektsed. Palun kontrollige imporditavat faili ja proovige uuesti." }, "importNothingError": { - "message": "Nothing was imported." + "message": "Ei imporditud midagi." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "Eksporditud faili dekrüpteerimine nurjus. Sinu krüpteerimisvõti ei ühti selle võtmega, mida kasutati andmete eksportimisel." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "Vale parool, palun kasuta seda parooli mille sisestasid eksportfaili loomisel." }, "destination": { - "message": "Destination" + "message": "Sihtkoht" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "Lisainfo importimise valikute kohta" }, "selectImportFolder": { - "message": "Select a folder" + "message": "Vali kaust" }, "selectImportCollection": { - "message": "Select a collection" + "message": "Vali kogumik" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "Tee siin valik, kui soovid, et imporditud faili sisu liigutatakse asukohta $DESTINATION$", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -2876,25 +2876,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "Fail sisaldab määramata kirjeid." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "Vali imporditava faili formaat" }, "selectImportFile": { - "message": "Select the import file" + "message": "Vali imporditav fail" }, "chooseFile": { - "message": "Choose File" + "message": "Vali fail" }, "noFileChosen": { - "message": "No file chosen" + "message": "Ühtegi faili pole valitud" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "või kopeeri/kleebi imporditava faili sisu" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "$NAME$ Kasutusjuhend", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -2904,120 +2904,120 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "Kinnita hoidla importimine" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "Fail on parooliga kaitstud. Palun sisesta faili importimiseks selle parool." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "Kinnita faili parool" }, "exportSuccess": { - "message": "Vault data exported" + "message": "Hoidla sisu edukalt eksporditud" }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "Mitmeastmeline autentiteerimine tühistatud" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "Ei leidnud LastPassi andmeid" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "Vale kasutajanimi või parool" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Vale parool" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Vale kood" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "Vale PIN-kood" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "Mitmeastmeline autentiteerimine ebaõnnestus" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "Lisa ka jagatud kaustad" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "LastPassi Email" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "Impordin sinu kontot..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "LastPassi mitmeastmeline autentiteerimine nõutud" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "Sisesta oma ühekordne kood autentiteerimise rakendusest" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "Kinnita sisselogimistaotlus oma autentiteerimisrakenduses või sisesta ühekordne kood." }, "passcode": { - "message": "Passcode" + "message": "Pääsukood" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "LastPassi ülemparool" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "LastPassi autentiteerimine nõutud" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "Ootan SSO autentiteerimise kinnitamist" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "Sisselogimiseks palun jätka kasutades oma firma andmeid." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "Vaata täpsemaid juhiseid meie veebilehel", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "Impordi otse LastPassist" }, "importFromCSV": { - "message": "Import from CSV" + "message": "Impordi CSV-st" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "Proovi uuesti või kinnita sulle LastPassilt saadetud kirjas, et see oled sina." }, "collection": { - "message": "Collection" + "message": "Kogumik" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "Sisesta sinu LastPassi kontoga seotud YubiKey oma arvuti USB porti ja vajuta nuppu selle peal." }, "commonImportFormats": { - "message": "Common formats", + "message": "Tüüpilised meetodid", "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Tehtud" }, "troubleshooting": { - "message": "Troubleshooting" + "message": "Tõrkeotsing" }, "disableHardwareAccelerationRestart": { - "message": "Disable hardware acceleration and restart" + "message": "Lülita riistavara kiirendus välja ja tee taaskäivitus" }, "enableHardwareAccelerationRestart": { - "message": "Enable hardware acceleration and restart" + "message": "Lülita riistavara kiirendus sisse ja tee taaskäivitus" }, "removePasskey": { - "message": "Remove passkey" + "message": "Eemalda pääsuvõti" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Pääsuvõti eemaldatud" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Sihtkogumikku määramine ebaõnnestus." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Sihtkausta määramine ebaõnnestus." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Vaata kirjeid asukohas $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3027,7 +3027,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tagasi asukohta $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3037,11 +3037,11 @@ } }, "back": { - "message": "Back", + "message": "Tagasi", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Eemalda $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3051,15 +3051,15 @@ } }, "data": { - "message": "Data" + "message": "Andmed" }, "fileSends": { - "message": "File Sends" + "message": "Kõik Send Failid" }, "textSends": { - "message": "Text Sends" + "message": "Kõik Tekst-Sendid" }, "ssoError": { - "message": "No free ports could be found for the sso login." + "message": "SSO-ga sisselogimiseks ei leitud ühtegi vaba porti." } } diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 212e5b50ee..86d07440a7 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -220,7 +220,6 @@ export class Main { this.windowMain, app.getPath("userData"), app.getPath("exe"), - app.getAppPath(), ); this.desktopAutofillSettingsService = new DesktopAutofillSettingsService(stateProvider); @@ -266,21 +265,13 @@ export class Main { if (browserIntegrationEnabled || ddgIntegrationEnabled) { // Re-register the native messaging host integrations on startup, in case they are not present if (browserIntegrationEnabled) { - this.nativeMessagingMain - .generateManifests() - .catch((err) => this.logService.error("Error while generating manifests", err)); + this.nativeMessagingMain.generateManifests().catch(this.logService.error); } if (ddgIntegrationEnabled) { - this.nativeMessagingMain - .generateDdgManifests() - .catch((err) => this.logService.error("Error while generating DDG manifests", err)); + this.nativeMessagingMain.generateDdgManifests().catch(this.logService.error); } - this.nativeMessagingMain - .listen() - .catch((err) => - this.logService.error("Error while starting native message listener", err), - ); + this.nativeMessagingMain.listen(); } app.removeAsDefaultProtocolClient("bitwarden"); diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 036f35e61c..8c8404578b 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -1,34 +1,34 @@ import { existsSync, promises as fs } from "fs"; +import { Socket } from "net"; import { homedir, userInfo } from "os"; import * as path from "path"; import * as util from "util"; import { ipcMain } from "electron"; +import * as ipc from "node-ipc"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { ipc } from "@bitwarden/desktop-napi"; -import { isDev } from "../utils"; +import { getIpcSocketRoot } from "../proxy/ipc"; import { WindowMain } from "./window.main"; export class NativeMessagingMain { - private ipcServer: ipc.IpcServer | null; - private connected: number[] = []; + private connected: Socket[] = []; + private socket: any; constructor( private logService: LogService, private windowMain: WindowMain, private userPath: string, private exePath: string, - private appPath: string, ) { ipcMain.handle( "nativeMessaging.manifests", async (_event: any, options: { create: boolean }) => { if (options.create) { + this.listen(); try { - await this.listen(); await this.generateManifests(); } catch (e) { this.logService.error("Error generating manifests: " + e); @@ -51,8 +51,8 @@ export class NativeMessagingMain { "nativeMessaging.ddgManifests", async (_event: any, options: { create: boolean }) => { if (options.create) { + this.listen(); try { - await this.listen(); await this.generateDdgManifests(); } catch (e) { this.logService.error("Error generating duckduckgo manifests: " + e); @@ -72,46 +72,56 @@ export class NativeMessagingMain { ); } - async listen() { - if (this.ipcServer) { - this.ipcServer.stop(); + listen() { + ipc.config.id = "bitwarden"; + ipc.config.retry = 1500; + const ipcSocketRoot = getIpcSocketRoot(); + if (ipcSocketRoot != null) { + ipc.config.socketRoot = ipcSocketRoot; } - this.ipcServer = await ipc.IpcServer.listen("bitwarden", (error, msg) => { - switch (msg.kind) { - case ipc.IpcMessageType.Connected: { - this.connected.push(msg.clientId); - this.logService.info("Native messaging client " + msg.clientId + " has connected"); - break; - } - case ipc.IpcMessageType.Disconnected: { - const index = this.connected.indexOf(msg.clientId); - if (index > -1) { - this.connected.splice(index, 1); - } + ipc.serve(() => { + ipc.server.on("message", (data: any, socket: any) => { + this.socket = socket; + this.windowMain.win.webContents.send("nativeMessaging", data); + }); - this.logService.info("Native messaging client " + msg.clientId + " has disconnected"); - break; + ipcMain.on("nativeMessagingReply", (event, msg) => { + if (this.socket != null && msg != null) { + this.send(msg, this.socket); } - case ipc.IpcMessageType.Message: - this.windowMain.win.webContents.send("nativeMessaging", JSON.parse(msg.message)); - break; - } + }); + + ipc.server.on("connect", (socket: Socket) => { + this.connected.push(socket); + }); + + ipc.server.on("socket.disconnected", (socket, destroyedSocketID) => { + const index = this.connected.indexOf(socket); + if (index > -1) { + this.connected.splice(index, 1); + } + + this.socket = null; + ipc.log("client " + destroyedSocketID + " has disconnected!"); + }); }); - ipcMain.on("nativeMessagingReply", (event, msg) => { - if (msg != null) { - this.send(msg); - } - }); + ipc.server.start(); } stop() { - this.ipcServer?.stop(); + ipc.server.stop(); + // Kill all existing connections + this.connected.forEach((socket) => { + if (!socket.destroyed) { + socket.destroy(); + } + }); } - send(message: object) { - this.ipcServer?.send(JSON.stringify(message)); + send(message: object, socket: any) { + ipc.server.emit(socket, "message", message); } async generateManifests() { @@ -321,20 +331,11 @@ export class NativeMessagingMain { } private binaryPath() { - const ext = process.platform === "win32" ? ".exe" : ""; - - if (isDev()) { - return path.join( - this.appPath, - "..", - "desktop_native", - "target", - "debug", - `desktop_proxy${ext}`, - ); + if (process.platform === "win32") { + return path.join(path.dirname(this.exePath), "resources", "native-messaging.bat"); } - return path.join(path.dirname(this.exePath), `desktop_proxy${ext}`); + return this.exePath; } private getRegeditInstance() { diff --git a/apps/desktop/src/proxy/ipc.ts b/apps/desktop/src/proxy/ipc.ts new file mode 100644 index 0000000000..0160d6bf29 --- /dev/null +++ b/apps/desktop/src/proxy/ipc.ts @@ -0,0 +1,78 @@ +/* eslint-disable no-console */ +import { createHash } from "crypto"; +import { existsSync, mkdirSync } from "fs"; +import { homedir } from "os"; +import { join as path_join } from "path"; + +import * as ipc from "node-ipc"; + +export function getIpcSocketRoot(): string | null { + let socketRoot = null; + + switch (process.platform) { + case "darwin": { + const ipcSocketRootDir = path_join(homedir(), "tmp"); + if (!existsSync(ipcSocketRootDir)) { + mkdirSync(ipcSocketRootDir); + } + socketRoot = ipcSocketRootDir + "/"; + break; + } + case "win32": { + // Let node-ipc use a unique IPC pipe //./pipe/xxxxxxxxxxxxxxxxx.app.bitwarden per user. + // Hashing prevents problems with reserved characters and file length limitations. + socketRoot = createHash("sha1").update(homedir()).digest("hex") + "."; + } + } + return socketRoot; +} + +ipc.config.id = "proxy"; +ipc.config.retry = 1500; +ipc.config.logger = console.warn; // Stdout is used for native messaging +const ipcSocketRoot = getIpcSocketRoot(); +if (ipcSocketRoot != null) { + ipc.config.socketRoot = ipcSocketRoot; +} + +export default class IPC { + onMessage: (message: object) => void; + + private connected = false; + + connect() { + ipc.connectTo("bitwarden", () => { + ipc.of.bitwarden.on("connect", () => { + this.connected = true; + console.error("## connected to bitwarden desktop ##"); + + // Notify browser extension, connection is established to desktop application. + this.onMessage({ command: "connected" }); + }); + + ipc.of.bitwarden.on("disconnect", () => { + this.connected = false; + console.error("disconnected from world"); + + // Notify browser extension, no connection to desktop application. + this.onMessage({ command: "disconnected" }); + }); + + ipc.of.bitwarden.on("message", (message: any) => { + this.onMessage(message); + }); + + ipc.of.bitwarden.on("error", (err: any) => { + console.error("error", err); + }); + }); + } + + isConnected(): boolean { + return this.connected; + } + + send(json: object) { + ipc.of.bitwarden.emit("message", json); + } +} diff --git a/apps/desktop/src/proxy/native-messaging-proxy.ts b/apps/desktop/src/proxy/native-messaging-proxy.ts new file mode 100644 index 0000000000..f1b54a8201 --- /dev/null +++ b/apps/desktop/src/proxy/native-messaging-proxy.ts @@ -0,0 +1,23 @@ +import IPC from "./ipc"; +import NativeMessage from "./nativemessage"; + +// Proxy is a lightweight application which provides bi-directional communication +// between the browser extension and a running desktop application. +// +// Browser extension <-[native messaging]-> proxy <-[ipc]-> desktop +export class NativeMessagingProxy { + private ipc: IPC; + private nativeMessage: NativeMessage; + + constructor() { + this.ipc = new IPC(); + this.nativeMessage = new NativeMessage(this.ipc); + } + + run() { + this.ipc.connect(); + this.nativeMessage.listen(); + + this.ipc.onMessage = this.nativeMessage.send; + } +} diff --git a/apps/desktop/src/proxy/nativemessage.ts b/apps/desktop/src/proxy/nativemessage.ts new file mode 100644 index 0000000000..f7a32296f8 --- /dev/null +++ b/apps/desktop/src/proxy/nativemessage.ts @@ -0,0 +1,95 @@ +/* eslint-disable no-console */ +import IPC from "./ipc"; + +// Mostly based on the example from MDN, +// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging +export default class NativeMessage { + ipc: IPC; + + constructor(ipc: IPC) { + this.ipc = ipc; + } + + send(message: object) { + const messageBuffer = Buffer.from(JSON.stringify(message)); + + const headerBuffer = Buffer.alloc(4); + headerBuffer.writeUInt32LE(messageBuffer.length, 0); + + process.stdout.write(Buffer.concat([headerBuffer, messageBuffer])); + } + + listen() { + let payloadSize: number = null; + + // A queue to store the chunks as we read them from stdin. + // This queue can be flushed when `payloadSize` data has been read + const chunks: any = []; + + // Only read the size once for each payload + const sizeHasBeenRead = () => Boolean(payloadSize); + + // All the data has been read, reset everything for the next message + const flushChunksQueue = () => { + payloadSize = null; + chunks.splice(0); + }; + + const processData = () => { + // Create one big buffer with all all the chunks + const stringData = Buffer.concat(chunks); + console.error(stringData); + + // The browser will emit the size as a header of the payload, + // if it hasn't been read yet, do it. + // The next time we'll need to read the payload size is when all of the data + // of the current payload has been read (ie. data.length >= payloadSize + 4) + if (!sizeHasBeenRead()) { + try { + payloadSize = stringData.readUInt32LE(0); + } catch (e) { + console.error(e); + return; + } + } + + // If the data we have read so far is >= to the size advertised in the header, + // it means we have all of the data sent. + // We add 4 here because that's the size of the bytes that old the payloadSize + if (stringData.length >= payloadSize + 4) { + // Remove the header + const contentWithoutSize = stringData.slice(4, payloadSize + 4).toString(); + + // Reset the read size and the queued chunks + flushChunksQueue(); + + const json = JSON.parse(contentWithoutSize); + + // Forward to desktop application + this.ipc.send(json); + } + }; + + process.stdin.on("readable", () => { + // A temporary variable holding the nodejs.Buffer of each + // chunk of data read off stdin + let chunk = null; + + // Read all of the available data + // tslint:disable-next-line:no-conditional-assignment + while ((chunk = process.stdin.read()) !== null) { + chunks.push(chunk); + } + + try { + processData(); + } catch (e) { + console.error(e); + } + }); + + process.stdin.on("end", () => { + process.exit(0); + }); + } +} diff --git a/apps/web/package.json b/apps/web/package.json index de153ea1a2..08b8d18283 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2024.8.3", + "version": "2024.9.0", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/apps/web/src/app/billing/individual/billing-history-view.component.html b/apps/web/src/app/billing/individual/billing-history-view.component.html index 2491fc42c7..7dbd8d1792 100644 --- a/apps/web/src/app/billing/individual/billing-history-view.component.html +++ b/apps/web/src/app/billing/individual/billing-history-view.component.html @@ -2,17 +2,6 @@

{{ "billingHistory" | i18n }}

- {{ "loading" | i18n }} - - + + + diff --git a/apps/web/src/app/billing/individual/billing-history-view.component.ts b/apps/web/src/app/billing/individual/billing-history-view.component.ts index 3ee6415d6d..e88d9bb154 100644 --- a/apps/web/src/app/billing/individual/billing-history-view.component.ts +++ b/apps/web/src/app/billing/individual/billing-history-view.component.ts @@ -1,8 +1,11 @@ import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; +import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/account/account-billing-api.service.abstraction"; +import { + BillingInvoiceResponse, + BillingTransactionResponse, +} from "@bitwarden/common/billing/models/response/billing.response"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @Component({ @@ -11,12 +14,14 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl export class BillingHistoryViewComponent implements OnInit { loading = false; firstLoaded = false; - billing: BillingHistoryResponse; + invoices: BillingInvoiceResponse[] = []; + transactions: BillingTransactionResponse[] = []; + hasAdditionalHistory: boolean = false; constructor( - private apiService: ApiService, private platformUtilsService: PlatformUtilsService, private router: Router, + private accountBillingApiService: AccountBillingApiServiceAbstraction, ) {} async ngOnInit() { @@ -35,7 +40,27 @@ export class BillingHistoryViewComponent implements OnInit { return; } this.loading = true; - this.billing = await this.apiService.getUserBillingHistory(); + + const invoicesPromise = this.accountBillingApiService.getBillingInvoices( + this.invoices.length > 0 ? this.invoices[this.invoices.length - 1].id : null, + ); + + const transactionsPromise = this.accountBillingApiService.getBillingTransactions( + this.transactions.length > 0 + ? this.transactions[this.transactions.length - 1].createdDate + : null, + ); + + const accountInvoices = await invoicesPromise; + const accountTransactions = await transactionsPromise; + const pageSize = 5; + + this.invoices = [...this.invoices, ...accountInvoices]; + this.transactions = [...this.transactions, ...accountTransactions]; + this.hasAdditionalHistory = !( + accountInvoices.length < pageSize && accountTransactions.length < pageSize + ); + this.loading = false; } } diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index 96bf6c43a3..02071f5aa8 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -4,7 +4,7 @@ {{ "upgradeFreeOrganization" | i18n: currentPlanName }}
-

{{ "upgradePlan" | i18n }}

+

{{ "upgradePlans" | i18n }}

{{ "selectAPlan" | i18n }}
@@ -52,12 +52,21 @@ tabindex="0" >
+
+ {{ "recommended" | i18n }} +

- {{ + {{ selectableProduct.nameLocalizationKey | i18n }} @@ -76,9 +85,8 @@ ) | currency: "$" }} + /{{ "monthPerMember" | i18n }} - /{{ "month" | i18n }} - {{ "includesXMembers" | i18n: selectableProduct.PasswordManager.baseSeats }} @@ -111,7 +119,7 @@ | currency: "$") }} - /{{ "monthPerMember" | i18n }} + /{{ "monthPerMember" | i18n }} {{ "freeForever" | i18n }} @@ -128,7 +136,7 @@

{{ "bitwardenPasswordManager" | i18n }}

-

{{ "upgradeEnterpriseMessage" | i18n }}

+

{{ "enterprisePlanUpgradeMessage" | i18n }}

  • @@ -196,13 +204,13 @@ *ngIf="selectableProduct.productTier === productTypes.Teams" class="tw-text-xs tw-px-2" > - {{ "upgradeTeamsMessage" | i18n }} + {{ "teamsPlanUpgradeMessage" | i18n }}

    - {{ "upgradeFamilyMessage" | i18n }} + {{ "familyPlanUpgradeMessage" | i18n }}

      - - + @@ -22,7 +9,16 @@ > {{ "loading" | i18n }} - - + + + diff --git a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts index cd29345200..00ab3fa777 100644 --- a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts +++ b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts @@ -2,8 +2,11 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { concatMap, Subject, takeUntil } from "rxjs"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; +import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction"; +import { + BillingInvoiceResponse, + BillingTransactionResponse, +} from "@bitwarden/common/billing/models/response/billing.response"; @Component({ templateUrl: "organization-billing-history-view.component.html", @@ -11,13 +14,15 @@ import { BillingHistoryResponse } from "@bitwarden/common/billing/models/respons export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy { loading = false; firstLoaded = false; - billing: BillingHistoryResponse; + invoices: BillingInvoiceResponse[] = []; + transactions: BillingTransactionResponse[] = []; organizationId: string; + hasAdditionalHistory: boolean = false; private destroy$ = new Subject(); constructor( - private organizationApiService: OrganizationApiServiceAbstraction, + private organizationBillingApiService: OrganizationBillingApiServiceAbstraction, private route: ActivatedRoute, ) {} @@ -43,8 +48,28 @@ export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy { if (this.loading) { return; } + this.loading = true; - this.billing = await this.organizationApiService.getBillingHistory(this.organizationId); + + const invoicesPromise = this.organizationBillingApiService.getBillingInvoices( + this.organizationId, + this.invoices.length > 0 ? this.invoices[this.invoices.length - 1].id : null, + ); + + const transactionsPromise = this.organizationBillingApiService.getBillingTransactions( + this.organizationId, + this.transactions.length > 0 + ? this.transactions[this.transactions.length - 1].createdDate + : null, + ); + + const invoices = await invoicesPromise; + const transactions = await transactionsPromise; + const pageSize = 5; + + this.invoices = [...this.invoices, ...invoices]; + this.transactions = [...this.transactions, ...transactions]; + this.hasAdditionalHistory = !(invoices.length < pageSize && transactions.length < pageSize); this.loading = false; } } diff --git a/apps/web/src/app/billing/shared/billing-history.component.ts b/apps/web/src/app/billing/shared/billing-history.component.ts index ec85da7d33..ac16b3dc72 100644 --- a/apps/web/src/app/billing/shared/billing-history.component.ts +++ b/apps/web/src/app/billing/shared/billing-history.component.ts @@ -1,7 +1,10 @@ import { Component, Input } from "@angular/core"; import { PaymentMethodType, TransactionType } from "@bitwarden/common/billing/enums"; -import { BillingHistoryResponse } from "@bitwarden/common/billing/models/response/billing-history.response"; +import { + BillingInvoiceResponse, + BillingTransactionResponse, +} from "@bitwarden/common/billing/models/response/billing.response"; @Component({ selector: "app-billing-history", @@ -9,19 +12,14 @@ import { BillingHistoryResponse } from "@bitwarden/common/billing/models/respons }) export class BillingHistoryComponent { @Input() - billing: BillingHistoryResponse; + invoices: BillingInvoiceResponse[]; + + @Input() + transactions: BillingTransactionResponse[]; paymentMethodType = PaymentMethodType; transactionType = TransactionType; - get invoices() { - return this.billing != null ? this.billing.invoices : null; - } - - get transactions() { - return this.billing != null ? this.billing.transactions : null; - } - paymentMethodClasses(type: PaymentMethodType) { switch (type) { case PaymentMethodType.Card: diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 20b77c53fb..f5f39bd022 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 3556df1e13..b874765622 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 78876040eb..b19ac2c391 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -9050,8 +9050,11 @@ "message": "Hər kəsə açıq API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Əlavə məzmun əlçatandır" + "showCharacterCount": { + "message": "Xarakter sayını göstər" + }, + "hideCharacterCount": { + "message": "Xarakter sayını gizlət" }, "editAccess": { "message": "Müraciətə düzəliş et" diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 607d633da5..340b7fb1f4 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index a877a5edf1..dda600ce09 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -9050,8 +9050,11 @@ "message": "Публичен ППИ", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Има налично допълнително съдържание" + "showCharacterCount": { + "message": "Показване на броя знаци" + }, + "hideCharacterCount": { + "message": "Скриване на броя знаци" }, "editAccess": { "message": "Редактиране на достъпа" diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 59016f510e..c7112cc6c1 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index 25404f49da..829615cc55 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 6b3ee609bf..2679458812 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 914bc3471c..6f7367b803 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -9050,8 +9050,11 @@ "message": "Veřejné API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Je k dispozici další obsah" + "showCharacterCount": { + "message": "Zobrazit počet znaků" + }, + "hideCharacterCount": { + "message": "Skrýt počet znaků" }, "editAccess": { "message": "Upravit přístup" diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index c88b9519d3..5e1de3971d 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 23b588f531..fae10f4eef 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -9050,8 +9050,11 @@ "message": "Offentlig API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Yderligere indhold er tilgængeligt" + "showCharacterCount": { + "message": "Vis tegnantal" + }, + "hideCharacterCount": { + "message": "Skjul tegnantal" }, "editAccess": { "message": "Redigér adgang" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index c6d0b2caec..9e83c16860 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -49,19 +49,19 @@ "message": "Zugangsdaten" }, "personalDetails": { - "message": "Personal details" + "message": "Persönliche Details" }, "identification": { - "message": "Identification" + "message": "Identifikation" }, "contactInfo": { - "message": "Contact info" + "message": "Kontaktinformation" }, "cardDetails": { - "message": "Card details" + "message": "Kartendetails" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$-Details", "placeholders": { "brand": { "content": "$1", @@ -70,7 +70,7 @@ } }, "itemHistory": { - "message": "Item history" + "message": "Eintragsverlauf" }, "authenticatorKey": { "message": "Authenticator-Schlüssel" @@ -464,7 +464,7 @@ "message": "Vollständiger Name" }, "address": { - "message": "Address" + "message": "Adresse" }, "address1": { "message": "Adresse 1" @@ -9050,8 +9050,11 @@ "message": "Öffentliche API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Zusätzlicher Inhalt ist verfügbar" + "showCharacterCount": { + "message": "Zeichenanzahl anzeigen" + }, + "hideCharacterCount": { + "message": "Zeichenanzahl ausblenden" }, "editAccess": { "message": "Zugriff bearbeiten" diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 7f79cbd162..cfe7d180e5 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 87ee3c3cea..6bd810c694 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4038,6 +4038,9 @@ "selected": { "message": "Selected" }, + "recommended": { + "message": "Recommended" + }, "ownership": { "message": "Ownership" }, @@ -8759,6 +8762,27 @@ "memberAccessReportPageDesc": { "message": "Audit organization member access across groups, collections, and collection items. The CSV export provides a detailed breakdown per member, including information on collection permissions and account configurations." }, + "memberAccessReportNoCollection": { + "message": "(No Collection)" + }, + "memberAccessReportNoCollectionPermission": { + "message": "(No Collection Permission)" + }, + "memberAccessReportNoGroup": { + "message": "(No Group)" + }, + "memberAccessReportTwoFactorEnabledTrue": { + "message": "On" + }, + "memberAccessReportTwoFactorEnabledFalse": { + "message": "Off" + }, + "memberAccessReportAuthenticationEnabledTrue": { + "message": "On" + }, + "memberAccessReportAuthenticationEnabledFalse": { + "message": "Off" + }, "higherKDFIterations": { "message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker." }, @@ -8962,8 +8986,8 @@ } } }, - "upgradePlan": { - "message": "Upgrade your plan to invite more members and gain access to additional Bitwarden features" + "upgradePlans": { + "message": "Upgrade your plan to invite members and experience powerful security features." }, "upgradeDiscount": { "message": "Save $AMOUNT$%", @@ -8974,11 +8998,11 @@ } } }, - "upgradeEnterpriseMessage": { - "message": "Advanced capabilities for larger businesses" + "enterprisePlanUpgradeMessage": { + "message": "Advanced capabilities for larger organizations" }, - "upgradeTeamsMessage": { - "message": "Businesses looking for powerful security" + "teamsPlanUpgradeMessage": { + "message": "Resilient protection for growing teams" }, "teamsInviteMessage": { "message": "Invite unlimited members" @@ -8989,8 +9013,8 @@ "syncGroupsAndUsersFromDirectory": { "message": "Sync groups and users from a directory" }, - "upgradeFamilyMessage": { - "message": "Share with families and friends" + "familyPlanUpgradeMessage": { + "message": "Secure your family logins" }, "accessToPremiumFeatures": { "message": "Access to Premium features" diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 13d23f1459..aab1efb7c5 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 90293bb066..7ccd08f86b 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 495b9c6e78..b7615fe447 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 639c0f34e6..a9458b4717 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 72ad36be3e..9a54611285 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -52,10 +52,10 @@ "message": "Isikuandmed" }, "identification": { - "message": "Identification" + "message": "Tuvastamine" }, "contactInfo": { - "message": "Contact info" + "message": "Kontaktinfo" }, "cardDetails": { "message": "Pangakaardi detailid" @@ -756,7 +756,7 @@ "message": "Aegunud link" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Palun alusta uuesti registreerimist või proovi sisse logida." }, "youMayAlreadyHaveAnAccount": { "message": "Sul on võib-olla juba konto" @@ -837,7 +837,7 @@ "message": "Kasuta hoidla krüpteerimiseks" }, "useForVaultEncryptionInfo": { - "message": "Log in and unlock on supported devices without your master password. Follow the prompts from your browser to finalize setup." + "message": "Kasuta toetatud seadmetel sisselogimis- ja avamisfunktsioone ilma ülemparoolita. Järgi brauseri juhiseid, et lõpetada seadistus." }, "useForVaultEncryptionErrorReadingPasskey": { "message": "Ei õnnestunud pääsuvõtit lugeda. Proovi uuesti või tühjendage see valik." @@ -849,7 +849,7 @@ "message": "Seadista krüpteerimine" }, "usedForEncryption": { - "message": "Used for encryption" + "message": "Kasutatakse krüpteerimiseks" }, "loginWithPasskeyEnabled": { "message": "Logi sisse pääsuvõti sisse lülitatult" @@ -1253,10 +1253,10 @@ "message": "Kopeeri UUID" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Juurdepääsukoodi Värskendamine Ebaõnnestus" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "Ei leidnud värskendamise koodi või API võtit. Palun proovi logida välja ja uuesti sisse." }, "warning": { "message": "Hoiatus" @@ -1283,7 +1283,7 @@ "message": "Ekspordi" }, "exportFrom": { - "message": "Ekspordi vorm" + "message": "Export from" }, "exportVault": { "message": "Hoidla sisu eksportimine" @@ -2474,7 +2474,7 @@ "message": "Tellimuse haldamine" }, "launchCloudSubscription": { - "message": "Launch Cloud Subscription" + "message": "Ühenda Tasuline Tellimus" }, "storage": { "message": "Salvestusruum" @@ -2573,7 +2573,7 @@ "message": "Võta klienditoega ühendust" }, "contactSupportShort": { - "message": "Võta kasutajatoega ühendust" + "message": "Klienditugi" }, "updatedPaymentMethod": { "message": "Maksemeetod on muudetud." @@ -3001,7 +3001,7 @@ "message": "Anna Juurdepääs" }, "addAccessFilter": { - "message": "Add Access Filter" + "message": "Piira Juurdepääsu" }, "refresh": { "message": "Värskenda" @@ -3451,7 +3451,7 @@ "message": "Seade" }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Konto loomise asukoht" }, "checkYourEmail": { "message": "Kontrolli oma e-posti" @@ -3586,7 +3586,7 @@ "message": "Pääsed organisatsiooni kirjetele ligi niipea, kui administraator sinu liikmelisuse kinnitab. Teavitame sind sellest e-posti teel." }, "inviteInitAcceptedDesc": { - "message": "You can now access this organization." + "message": "Sul on nüüd ligipääs sellele organisatsioonile." }, "inviteAcceptFailed": { "message": "Kutse vastuvõtmine nurjus. Palu organisatsiooni administraatoril uus kutse saata." @@ -3625,7 +3625,7 @@ "message": "Oled avaldanud soovi oma Bitwardeni konto kustutamiseks. Selle kinnitamiseks kliki allolevale nupule." }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "Kas sa oled kindel, et soovid kustudada oma Bitwardeni organisatsiooni?" }, "myOrganization": { "message": "Minu organisatsioon" @@ -3754,14 +3754,14 @@ "message": "Määra maksimaalne kasutajate limiit. Limiidi täitumisel ei saa enam uusi kasutajaid kutsuda." }, "limitSmSubscriptionDesc": { - "message": "Set a seat limit for your Secrets Manager subscription. Once this limit is reached, you will not be able to invite new members." + "message": "Määra maksimaalne Secrets Manageri kasutajate limiit. Limiidi täitumisel ei saa enam uusi liikmeid kutsuda." }, "maxSeatLimit": { - "message": "Maksimaalne kasutajate limiit (valikuline)", + "message": "Kasutajate limiit (valikuline)", "description": "Upper limit of seats to allow through autoscaling" }, "maxSeatCost": { - "message": "Max potential seat cost" + "message": "Suurim võimalik hind inimese kohta" }, "addSeats": { "message": "Lisa kasutajaid", @@ -3772,7 +3772,7 @@ "description": "Seat = User Seat" }, "subscriptionDesc": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited users exceed your subscription seats, you will immediately receive a prorated charge for the additional users." + "message": "Muudatused sinu tellimusele kajastuvad arve lõppsummas. Kui sinu kasutajate arv ületab tellimuse kohtade arvu, saad kohe arve uute kasutajate eest." }, "subscriptionUserSeats": { "message": "Sinu tellimus lubab kasutada/luua kokku $COUNT$ kasutajakontot.", @@ -3784,34 +3784,34 @@ } }, "limitSubscription": { - "message": "Limit subscription (optional)" + "message": "Määra kasutajate ülempiir (valikuline)" }, "subscriptionSeats": { - "message": "Subscription seats" + "message": "Tellimuse kasutajate arv" }, "subscriptionUpdated": { - "message": "Subscription updated" + "message": "Tellimus uuendatud" }, "subscribedToSecretsManager": { - "message": "Subscription updated. You now have access to Secrets Manager." + "message": "Tellimus uuendatud. Nüüd on sul ligipääs Secrets Managerile." }, "additionalOptions": { "message": "Lisavalikud" }, "additionalOptionsDesc": { - "message": "For additional help in managing your subscription, please contact Customer Support." + "message": "Küsimustega seoses oma tellimuse haldamisega kontakteeruge Kasutajatoega." }, "subscriptionUserSeatsUnlimitedAutoscale": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members." + "message": "Muudatused sinu tellimusele kajastuvad arve lõppsummas. Kui sinu liikmete arv ületab tellimuse kohtade arvu, saad kohe arve uute liikmete eest." }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { - "message": "If you want to add additional" + "message": "Kui sa soovid lisada tootesse" }, "smStandaloneTrialSeatCountUpdateMessageFragment2": { - "message": "seats without the bundled offer, please contact" + "message": "teisi liikmeid ilma teiste pakkumisteta, võta ühendust osakonnaga" }, "subscriptionUserSeatsLimitedAutoscale": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members until your $MAX$ seat limit is reached.", + "message": "Muudatused sinu tellimusele kajastuvad arve lõppsummas. Kui sinu liikmete arv ületab tellimuse kohtade arvu, saad kohe arve uute liikmete eest, kuni su maksimaalne liikmete arv ($MAX$) on saavutatud.", "placeholders": { "max": { "content": "$1", @@ -3820,7 +3820,7 @@ } }, "subscriptionUserSeatsWithoutAdditionalSeatsOption": { - "message": "You can invite up to $COUNT$ members for no additional charge. Contact Customer Support to upgrade your plan and invite more members.", + "message": "Sa saad kutsuda tasuta kuni $COUNT$ liiget. Kontakteeru kasutajatoega, et muuta oma tellimust ja kutsuda veel rohkem liikmeid.", "placeholders": { "count": { "content": "$1", @@ -3829,7 +3829,7 @@ } }, "subscriptionFreePlan": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "message": "Sa ei saa kutsuda rohkem kui $COUNT$ liiget ilma oma tellimust muutmata.", "placeholders": { "count": { "content": "$1", @@ -3838,7 +3838,7 @@ } }, "subscriptionUpgrade": { - "message": "You cannot invite more than $COUNT$ members without upgrading your plan.", + "message": "Sa ei saa kutsuda rohkem kui $COUNT$ liiget ilma oma tellimust muutmata.", "placeholders": { "count": { "content": "$1", @@ -3847,7 +3847,7 @@ } }, "subscriptionSponsoredFamiliesPlan": { - "message": "Your subscription allows for a total of $COUNT$ members. Your plan is sponsored and billed to an external organization.", + "message": "Sinu tellimust saab kasutada korraga kuni $COUNT$ liiget. Seda tellimust haldab väline organisatsioon.", "placeholders": { "count": { "content": "$1", @@ -3856,7 +3856,7 @@ } }, "subscriptionMaxReached": { - "message": "Adjustments to your subscription will result in prorated changes to your billing totals. You cannot invite more than $COUNT$ members without increasing your subscription seats.", + "message": "Muudatused sinu tellimusele kajastuvad arve lõppsummas. Sa ei saa kutsuda rohkem kui $COUNT$ liiget ilma oma tellimuse kasutajate arvu suurendamata.", "placeholders": { "count": { "content": "$1", @@ -3865,7 +3865,7 @@ } }, "subscriptionSeatMaxReached": { - "message": "You cannot invite more than $COUNT$ members without increasing your subscription seats.", + "message": "Sa ei saa kutsuda rohkem kui $COUNT$ liiget ilma oma tellimuse kasutajate arvu muutmata.", "placeholders": { "count": { "content": "$1", @@ -4009,19 +4009,19 @@ "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "Organisatsioon on välja lülitatud." + "message": "Organisatsioon on määramata ajaks peatatud" }, "secretsAccessSuspended": { - "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." + "message": "Peatatud organisatsioonidele ei ole võimalik ligi pääseda. Palun kontakteeruge oma organisatsiooni haldajaga." }, "secretsCannotCreate": { - "message": "Secrets cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "Peatatud organisatsioonidesse ei ole võimalik salvestada uusi IT-saladusi. Palun kontakteeruge oma organisatsiooni haldajaga." }, "projectsCannotCreate": { - "message": "Projects cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "Peatatud organisatsioonides ei ole võimalik luua uusi projekte. Palun kontakteeruge oma organisatsiooni haldajaga." }, "serviceAccountsCannotCreate": { - "message": "Service accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "Peatatud organisatsioonides ei ole võimalik luua uusi kontosid. Palun kontakteeruge oma organisatsiooni haldajaga." }, "disabledOrganizationFilterError": { "message": "Organisatsiooni alla kuuluvatele kirjetele ei ole ligipääsu. Kontakteeru oma organisatsiooni omanikuga." @@ -4923,64 +4923,64 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more, see how it works, **or** try it now.'" }, "developmentDevOpsAndITTeamsChooseBWSecret": { - "message": "Development, DevOps, and IT teams choose Bitwarden Secrets Manager to securely manage and deploy their infrastructure and machine secrets." + "message": "Arendus- ja IT-tiimid kasutavad Bitwarden Secrets Manageri, et turvaliselt hallata ja hoiustada oma infrastruktuuri ja süsteemide saladusi." }, "centralizeSecretsManagement": { - "message": "Centralize secrets management." + "message": "Koguge IT-saladused ühte kohta." }, "centralizeSecretsManagementDescription": { - "message": "Securely store and manage secrets in one location to prevent secret sprawl across your organization." + "message": "Hoiusta ja halda ohutult IT-saladusi ühes asukohas, et vältida nende kadumist organisatsioonis laiali valgudes." }, "preventSecretLeaks": { - "message": "Prevent secret leaks." + "message": "Väldi saladuste lekkimist." }, "preventSecretLeaksDescription": { - "message": "Protect secrets with end-to-end encryption. No more hard coding secrets or sharing through .env files." + "message": "Kaitse IT-saladusi täieliku krüpteerimisega. Ütle ei nende koodibaasi lisamisele või jagamisele läbi .env failide." }, "enhanceDeveloperProductivity": { - "message": "Enhance developer productivity." + "message": "Paranda programmeerijate ja arendajate produktiivsust." }, "enhanceDeveloperProductivityDescription": { - "message": "Programmatically retrieve and deploy secrets at runtime so developers can focus on what matters most, like improving code quality." + "message": "Kasuta IT-saladusi otse läbi programmi ja loo uusi saladusi masinat peatamata, nii et arendajad saavad keskenduda sellele, mis on kõige tähtsam, näiteks vigade parandamisele." }, "strengthenBusinessSecurity": { - "message": "Strengthen business security." + "message": "Paranda firma turvalisust." }, "strengthenBusinessSecurityDescription": { - "message": "Maintain tight control over machine and human access to secrets with SSO integrations, event logs, and access rotation." + "message": "Säilita kontroll masinate ja inimeste ligipääsu üle IT-saladustele läbi SSO kasutamise, sündmuste ajaloo ja ligipääsu uuendamise." }, "tryItNow": { - "message": "Try it now" + "message": "Proovi kohe" }, "sendRequest": { - "message": "Send request" + "message": "Saada taotlus" }, "addANote": { - "message": "Add a note" + "message": "Lisa märge" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" }, "moreProductsFromBitwarden": { - "message": "More products from Bitwarden" + "message": "Veel tooteid Bitwardenilt" }, "requestAccessToSecretsManager": { - "message": "Request access to Secrets Manager" + "message": "Esita taotlus Secrets Manageri kasutamiseks" }, "youNeedApprovalFromYourAdminToTrySecretsManager": { - "message": "You need approval from your administrator to try Secrets Manager." + "message": "Sul on vaja enda administraatori nõusolekut Secrets Manageri proovimiseks." }, "smAccessRequestEmailSent": { - "message": "Access request for secrets manager email sent to admins." + "message": "Secrets Manageri kasutamise taotlus saadetud administraatorile." }, "requestAccessSMDefaultEmailContent": { - "message": "Hi,\n\nI am requesting a subscription to Bitwarden Secrets Manager for our team. Your support would mean a great deal!\n\nBitwarden Secrets Manager is an end-to-end encrypted secrets management solution for securely storing, sharing, and deploying machine credentials like API keys, database passwords, and authentication certificates.\n\nSecrets Manager will help us to:\n\n- Improve security\n- Streamline operations\n- Prevent costly secret leaks\n\nTo request a free trial for our team, please reach out to Bitwarden.\n\nThank you for your help!" + "message": "Tere!\n\nEsitan palve Bitwarden Secrets Manageri kasutamiseks meie tiimi jaoks. Teie toetus oleks meile suureks abiks!\n\nBitwarden Secrets Manager on täielikult krüpteeritud IT-saladuste (näiteks API võtmete, andmebaasi paroolide ja autentiteerimise sertifikaatide) haldamise lahendus, mis on mõeldud nende turvaliselt hoiustamiseks, jagamiseks ja kasutamiseks.\n\nSecrets Manager aitab meil:\n\n- Parandada turvalisust\n- Muuta toimingud sujuvamaks\n- Takistada võimalikke andmelekkeid\n\nEt esitada taotlus tasuta prooviajaks, palun kontakteeruge Bitwardeniga.\n\nSuur tänu teie abi eest!" }, "giveMembersAccess": { "message": "Give members access:" }, "viewAndSelectTheMembers": { - "message": "view and select the members you want to give access to Secrets Manager." + "message": "vaata ja vali liikmeid, kellele sa soovid anda ligipääsu Secrets Managerile." }, "openYourOrganizations": { "message": "Open your organization's" @@ -5271,10 +5271,10 @@ "message": "Provider successfully set up" }, "clients": { - "message": "Clients" + "message": "Kliendid" }, "client": { - "message": "Client", + "message": "Klient", "description": "This is used as a table header to describe which client application created an event log." }, "providerAdmin": { @@ -5354,7 +5354,7 @@ } }, "providerIsDisabled": { - "message": "Provider suspended" + "message": "Teenusepakkuja on peatatud määramata ajaks" }, "providerUpdated": { "message": "Provider saved" @@ -5627,43 +5627,43 @@ "message": "Single sign-on configuration saved" }, "sponsoredFamilies": { - "message": "Free Bitwarden Families" + "message": "Tasuta Bitwarden Families pakett" }, "sponsoredFamiliesEligible": { - "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." + "message": "Sina ja su pere olete valitud tasuta Bitwarden Families paketi saamiseks. Lunasta see läbi oma emaili, et hoida oma andmed ohutus kohas isegi kui sa ei ole tööl." }, "sponsoredFamiliesEligibleCard": { - "message": "Redeem your Free Bitwarden for Families plan today to keep your data secure even when you are not at work." + "message": "Lunasta tasuta pakett Bitwarden for Families juba täna, et kaitsta oma andmeid isegi siis, kui sa ei ole tööl." }, "sponsoredFamiliesInclude": { - "message": "The Bitwarden for Families plan include" + "message": "Pakett Bitwarden for Families sisaldab" }, "sponsoredFamiliesPremiumAccess": { - "message": "Premium access for up to 6 users" + "message": "Ligipääs Preemiumile kuni kuuele inimesele" }, "sponsoredFamiliesSharedCollections": { - "message": "Shared collections for Family secrets" + "message": "Jagatud kogumikud pere saladuste talletamiseks" }, "badToken": { - "message": "The link is no longer valid. Please have the sponsor resend the offer." + "message": "See link ei ole enam aktiivne. Uue lingi saamiseks kontakteeruge oma sponsoriga, et ta saadaks pakkumise uuesti." }, "reclaimedFreePlan": { - "message": "Reclaimed free plan" + "message": "Uuendatud tasuta pakett" }, "redeem": { - "message": "Redeem" + "message": "Lunasta" }, "sponsoredFamiliesSelectOffer": { - "message": "Select the organization you would like sponsored" + "message": "Vali organisatsioon, mida soovid sponsoreerida" }, "familiesSponsoringOrgSelect": { - "message": "Which Free Families offer would you like to redeem?" + "message": "Millist Free Families pakkumist soovid sa lunastada?" }, "sponsoredFamiliesEmail": { - "message": "Enter your personal email to redeem Bitwarden Families" + "message": "Sisesta oma isiklik email, et lunastada Bitwarden Families" }, "sponsoredFamiliesLeaveCopy": { - "message": "If you remove an offer or are removed from the sponsoring organization, your Families sponsorship will expire at the next renewal date." + "message": "Kui sa lõpetad pakkumise, või sind eemaldatakse sponsoreerivast organisatsioonist, siis sinu Families sponsorlus lõpetatakse perioodi lõpus." }, "acceptBitwardenFamiliesHelp": { "message": "Accept offer for an existing organization or create a new Families organization." @@ -5723,13 +5723,13 @@ "message": "Recipient" }, "removeSponsorship": { - "message": "Remove sponsorship" + "message": "Eemalda sponsorlus" }, "removeSponsorshipConfirmation": { - "message": "After removing a sponsorship, you will be responsible for this subscription and related invoices. Are you sure you want to continue?" + "message": "Peale sponsorluse eemaldamist vastutad sa selle tellimusega seotud arvete eest. Kas oled kindel, et soovid jätkata?" }, "sponsorshipCreated": { - "message": "Sponsorship created" + "message": "Sponsorlus loodud" }, "emailSent": { "message": "E-kiri on saadetud" @@ -5780,30 +5780,30 @@ } }, "leaveOrganization": { - "message": "Leave organization" + "message": "Lahku organisatsioonist" }, "removeMasterPassword": { - "message": "Remove master password" + "message": "Eemalda ülemparool" }, "removedMasterPassword": { - "message": "Master password removed" + "message": "Ülemparool eemaldatud" }, "allowSso": { - "message": "Allow SSO authentication" + "message": "Lülita SSO autentiteerimine sisse" }, "allowSsoDesc": { "message": "Once set up, your configuration will be saved and members will be able to authenticate using their Identity Provider credentials." }, "ssoPolicyHelpStart": { - "message": "Use the", + "message": "Kasuta", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpAnchor": { - "message": "require single sign-on authentication policy", + "message": "\"nõua ühekordse sisselogimise autentimise (SSO) kasutamist\",", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "ssoPolicyHelpEnd": { - "message": "to require all members to log in with SSO.", + "message": "et nõuda liikmetelt SSO kasutamist.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Use the require single-sign-on authentication policy to require all members to log in with SSO.'" }, "memberDecryptionOption": { @@ -5831,10 +5831,10 @@ "message": "\"Login with SSO and Key Connector Decryption\" is activated. This policy will only apply to owners and admins." }, "enabledSso": { - "message": "SSO turned on" + "message": "SSO sisse lülitatud" }, "disabledSso": { - "message": "SSO turned on" + "message": "SSO välja lülitatud" }, "enabledKeyConnector": { "message": "Key Connector activated" @@ -5858,7 +5858,7 @@ "message": "New Families organization" }, "acceptOffer": { - "message": "Accept offer" + "message": "Nõustu pakkumisega" }, "sponsoringOrg": { "message": "Sponsoring organization" @@ -5969,16 +5969,16 @@ "message": "Sent (awaiting sync)" }, "sent": { - "message": "Sent" + "message": "Saadetud" }, "requestRemoved": { "message": "Removed (awaiting sync)" }, "requested": { - "message": "Requested" + "message": "Taotlus esitatud" }, "formErrorSummaryPlural": { - "message": "$COUNT$ fields above need your attention.", + "message": "$COUNT$ välja nõuab tähelepanu.", "placeholders": { "count": { "content": "$1", @@ -5987,10 +5987,10 @@ } }, "formErrorSummarySingle": { - "message": "1 field above needs your attention." + "message": "1 väli nõuab tähelepanu." }, "fieldRequiredError": { - "message": "$FIELDNAME$ is required.", + "message": "$FIELDNAME$ on nõutud.", "placeholders": { "fieldname": { "content": "$1", @@ -5999,7 +5999,7 @@ } }, "required": { - "message": "required" + "message": "nõutud" }, "charactersCurrentAndMaximum": { "message": "$CURRENT$/$MAX$ character maximum", @@ -6326,7 +6326,7 @@ "description": "Part of a URL." }, "apiAccessToken": { - "message": "API ligipääsu märk" + "message": "API võti" }, "deviceVerification": { "message": "Seadme kinnitamine" @@ -6608,17 +6608,17 @@ "description": "Action to delete a single secret from the system." }, "deleteSecrets": { - "message": "Delete secrets", + "message": "Kustuta IT-saladusi", "description": "The action to delete multiple secrets from the system." }, "hardDeleteSecret": { "message": "Permanently delete secret" }, "hardDeleteSecrets": { - "message": "Permanently delete secrets" + "message": "Kustuta valitud jäädavalt" }, "secretProjectAssociationDescription": { - "message": "Select projects that the secret will be associated with. Only organization users with access to these projects will be able to see the secret.", + "message": "Vali projektid, millega see saladus seotud on. Seda saladust saavad vaadata ainult organisatsiooni kasutajad ligipääsuga nendele projektidele.", "description": "A prompt explaining how secrets can be associated with projects." }, "selectProjects": { @@ -6630,7 +6630,7 @@ "description": "Label for the search bar used to search projects." }, "project": { - "message": "Project", + "message": "Projekt", "description": "Similar to collections, projects can be used to group secrets." }, "editProject": { @@ -6662,11 +6662,11 @@ "description": "The title for the section that deals with service accounts." }, "secrets": { - "message": "Secrets", + "message": "IT-saladused", "description": "The title for the section of the application that deals with secrets." }, "nameValuePair": { - "message": "Name/Value pair", + "message": "Nimi/Väärtus", "description": "Title for a name/ value pair. Secrets typically consist of a name and value pair." }, "secretEdited": { @@ -6686,15 +6686,15 @@ "description": "Title for creating a new service account." }, "secretsNoItemsTitle": { - "message": "No secrets to show", + "message": "Siin ei ole ühtegi saladust", "description": "Empty state to indicate that there are no secrets to display." }, "secretsNoItemsMessage": { - "message": "To get started, add a new secret or import secrets.", + "message": "Alustamiseks loo uus IT-saladus või kasuta meie importimise funktsiooni.", "description": "Message to encourage the user to start adding secrets." }, "secretsTrashNoItemsMessage": { - "message": "There are no secrets in the trash." + "message": "Prügikast on tühi." }, "serviceAccountsNoItemsMessage": { "message": "Create a new service account to get started automating secret access.", @@ -6705,7 +6705,7 @@ "description": "Title to indicate that there are no service accounts to display." }, "searchSecrets": { - "message": "Search secrets", + "message": "Otsi IT-saladusi", "description": "Placeholder text for searching secrets." }, "deleteServiceAccounts": { @@ -6780,21 +6780,21 @@ "description": "Title for creating a new project." }, "softDeleteSecretWarning": { - "message": "Deleting secrets can affect existing integrations.", + "message": "IT-saladuste kustutamine võib mõjuta ühendatud süsteeme.", "description": "Warns that deleting secrets can have consequences on integrations" }, "softDeletesSuccessToast": { - "message": "Secrets sent to trash", + "message": "IT-saladused prügikasti teisaldatud", "description": "Notifies that the selected secrets have been moved to the trash" }, "hardDeleteSecretConfirmation": { "message": "Are you sure you want to permanently delete this secret?" }, "hardDeleteSecretsConfirmation": { - "message": "Are you sure you want to permanently delete these secrets?" + "message": "Kas sa oled kindel, et soovid kustutada need IT-saladused?" }, "hardDeletesSuccessToast": { - "message": "Secrets permanently deleted" + "message": "IT-saladused lõplikult kustutatud" }, "smAccess": { "message": "Access", @@ -6817,7 +6817,7 @@ "description": "Notifies that a service account has been updated" }, "newSaSelectAccess": { - "message": "Type or select projects or secrets", + "message": "Trüki või vali mõni projekt või saladus", "description": "Instructions for selecting projects or secrets for a new service account" }, "newSaTypeToFilter": { @@ -6881,7 +6881,7 @@ "description": "Empty state to be displayed when there are no projects to display in the list." }, "projectsNoItemsMessage": { - "message": "Add a new project to get started organizing secrets.", + "message": "Loo uus projekt, et alustada oma saladuste organiseerimisega.", "description": "Message to be displayed when there are no projects to display in the list." }, "smConfirmationRequired": { @@ -6940,7 +6940,7 @@ "description": "Copies the generated access token to the user's clipboard." }, "accessToken": { - "message": "Access token", + "message": "Juurdepääsuvõti", "description": "A unique string that gives a client application (eg. CLI) access to a secret or set of secrets." }, "accessTokenExpirationRequired": { @@ -6952,7 +6952,7 @@ "description": "Notification to inform the user that the access token has been created and copied to the clipboard." }, "revokeAccessToken": { - "message": "Revoke access token", + "message": "Eemalda juurdepääsuvõti", "description": "Invalidates / cancels an access token and as such removes access to secrets for the client application." }, "revokeAccessTokens": { @@ -7315,7 +7315,7 @@ "message": "Exporting Organization Secret Data" }, "exportingOrganizationSecretDataDescription": { - "message": "Only the Secrets Manager data associated with $ORGANIZATION$ will be exported. Items in other products or from other organizations will not be included.", + "message": "Ainult organisatsiooni $ORGANIZATION$ Secrets Manageri andmed eksporditakse. Kirjed teistes toodetes või organisatsioonides ei kuulu nende hulka.", "placeholders": { "ORGANIZATION": { "content": "$1", @@ -7423,10 +7423,10 @@ "message": "Secrets Manager" }, "secretsManagerAccessDescription": { - "message": "Activate user access to Secrets Manager." + "message": "Anna kasutajale juurdepääs Secrets Managerile." }, "userAccessSecretsManagerGA": { - "message": "This user can access Secrets Manager" + "message": "See kasutaja pääseb ligi Secrets Managerile" }, "important": { "message": "Important:" @@ -7482,14 +7482,14 @@ "message": "Create a service account" }, "downloadThe": { - "message": "Download the", + "message": "Lae alla", "description": "Link to a downloadable resource. This will be used as part of a larger phrase. Example: Download the Secrets Manager CLI" }, "smCLI": { "message": "Secrets Manager CLI" }, "importSecrets": { - "message": "Import secrets" + "message": "Impordi saladusi" }, "getStarted": { "message": "Get started" @@ -7511,19 +7511,19 @@ "message": "Restore secret" }, "restoreSecrets": { - "message": "Restore secrets" + "message": "Taasta saladusi" }, "restoreSecretPrompt": { "message": "Are you sure you want to restore this secret?" }, "restoreSecretsPrompt": { - "message": "Are you sure you want to restore these secrets?" + "message": "Kas sa oled kindel, et soovid taastada need saladused?" }, "secretRestoredSuccessToast": { "message": "Secret restored" }, "secretsRestoredSuccessToast": { - "message": "Secrets restored" + "message": "IT-saladused taastatud" }, "selectionIsRequired": { "message": "Selection is required." @@ -7593,7 +7593,7 @@ "message": "This feature is not available for free organizations. Contact your organization owner to upgrade." }, "smProjectSecretsNoItemsNoAccess": { - "message": "Contact your organization's admin to manage secrets for this project.", + "message": "Kontakteeruge oma organisatsiooni administraatoriga, et hallata selle projekti saladusi.", "description": "The message shown to the user under a project's secrets tab when the user only has read access to the project." }, "enforceOnLoginDesc": { @@ -7693,18 +7693,18 @@ "message": "User updated a password issued through account recovery." }, "activatedAccessToSecretsManager": { - "message": "Activated access to Secrets Manager", + "message": "Juurdepääs Secrets Managerile aktiveeritud", "description": "Confirmation message that one or more users gained access to Secrets Manager" }, "activateAccess": { "message": "Activate access" }, "bulkEnableSecretsManagerDescription": { - "message": "Grant the following members access to Secrets Manager. The role granted in the Password Manager will apply to Secrets Manager.", + "message": "Anna järgnevatele inimestele ligipääs Secrets Managerile. Password Manageris antud roll kehtib ka Secrets Managerile.", "description": "This description is shown to an admin when they are attempting to add more users to Secrets Manager." }, "activateSecretsManager": { - "message": "Activate Secrets Manager" + "message": "Aktiveeri Secrets Manager" }, "yourOrganizationsFingerprint": { "message": "Sinu organisatsiooni unikaalne sõnajada", @@ -7798,7 +7798,7 @@ } }, "startYour7DayFreeTrialOfBitwardenSecretsManagerFor": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for $ORG$", + "message": "Alusta oma organisatsiooni $ORG$ 7-päevast tasuta Bitwarden Secrets Manageri katseperioodi", "placeholders": { "org": { "content": "$1", @@ -7851,7 +7851,7 @@ "message": "Invite Users" }, "secretsManagerForPlan": { - "message": "Secrets Manager for $PLAN$", + "message": "Secrets Manager plaanile $PLAN$", "placeholders": { "plan": { "content": "$1", @@ -7860,13 +7860,13 @@ } }, "secretsManagerForPlanDesc": { - "message": "For engineering and DevOps teams to manage secrets throughout the software development lifecycle." + "message": "Arendus- ja programmeerimistiimidele, et hallata saladusi läbi kogu tarkvara arendustsükli." }, "free2PersonOrganization": { "message": "Free 2-person Organizations" }, "unlimitedSecrets": { - "message": "Unlimited secrets" + "message": "Piiramatult IT-saladusi" }, "unlimitedProjects": { "message": "Unlimited projects" @@ -7899,10 +7899,10 @@ } }, "subscribeToSecretsManager": { - "message": "Subscribe to Secrets Manager" + "message": "Telli Secrets Manager" }, "addSecretsManagerUpgradeDesc": { - "message": "Add Secrets Manager to your upgraded plan to maintain access to any secrets created with your previous plan." + "message": "Lisa Secrets Manager oma uude plaani, et säilitada juurdepääs kõigile eelmise plaaniga loodud saladustele." }, "additionalServiceAccounts": { "message": "Additional service accounts" @@ -7944,10 +7944,10 @@ "message": "Password Manager plan price" }, "secretsManagerPlanPrice": { - "message": "Secrets Manager plan price" + "message": "Secrets Manageri plaani hind" }, "passwordManager": { - "message": "Password Manager" + "message": "Paroolihaldur" }, "freeOrganization": { "message": "Free Organization" @@ -8086,7 +8086,7 @@ "message": "Confirmation details" }, "smFreeTrialThankYou": { - "message": "Thank you for signing up for Bitwarden Secrets Manager!" + "message": "Aitäh registreerimast Bitwarden Secrets Manageri!" }, "smFreeTrialConfirmationEmail": { "message": "We've sent a confirmation email to your email at " @@ -8276,10 +8276,10 @@ "description": "The date header used when a subscription is cancelled." }, "machineAccountsCannotCreate": { - "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." + "message": "Peatatud organisatsioonides ei ole võimalik luua uusi kontosid arvutitele. Palun kontakteeruge oma organisatsiooni haldajaga." }, "machineAccount": { - "message": "Machine account", + "message": "Arvuti konto", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { @@ -8491,13 +8491,13 @@ "message": "Integrations" }, "integrationsDesc": { - "message": "Automatically sync secrets from Bitwarden Secrets Manager to a third-party service." + "message": "Sünkroniseeri enda Bitwarden Secrets Manageri saladusi kolmandate isikutega." }, "sdks": { "message": "SDKs" }, "sdksDesc": { - "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." + "message": "Kasuta Bitwarden Secrets Manageri SDK järgnevates programmeerimiskeeltes, millega saad ehitada enda rakendusi." }, "setUpGithubActions": { "message": "Set up Github Actions" @@ -8590,16 +8590,16 @@ "message": "Start your 7-Day free trial of Bitwarden for Enterprise" }, "startYour7DayFreeTrialOfBitwardenSecretsManager": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager" + "message": "Alusta oma 7-päevast prooviaega Bitwarden Secrets Manageriga" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForTeams": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Teams" + "message": "Alusta oma 7-päevast prooviaega Bitwarden Secrets Manageri ja plaaniga Teams" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForFamilies": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Families" + "message": "Alusta oma 7-päevast prooviaega Bitwarden Secrets Manageri ja plaaniga Families" }, "startYour7DayFreeTrialOfBitwardenSecretsManagerForEnterprise": { - "message": "Start your 7-Day free trial of Bitwarden Secrets Manager for Enterprise" + "message": "Alusta oma 7-päevast prooviaega Bitwarden Secrets Manageri ja plaaniga Enterprise" }, "startYour7DayFreeTrialOfBitwardenPasswordManager": { "message": "Start your 7-Day free trial of Bitwarden Password Manager" @@ -8829,10 +8829,10 @@ "message": "Download CSV" }, "monthlySubscriptionUserSeatsMessage": { - "message": "Adjustments to your subscription will result in prorated charges to your billing totals on your next billing period. " + "message": "Muudatused sinu tellimusele kajastuvad järgmise perioodi arve lõppsummas. " }, "annualSubscriptionUserSeatsMessage": { - "message": "Adjustments to your subscription will result in prorated charges on a monthly billing cycle. " + "message": "Muudatused sinu tellimusele kajastuvad igakuiselt arvete lõppsummades. " }, "billingHistoryDescription": { "message": "Download a CSV to obtain client details for each billing date. Prorated charges are not included in the CSV and may vary from the linked invoice. For the most accurate billing details, refer to your monthly invoices.", @@ -9023,7 +9023,7 @@ "message": "Custom roles" }, "unlimitedSecretsStorage": { - "message": "Unlimited secrets storage" + "message": "Piiramatult ruumi saladustele" }, "unlimitedUsers": { "message": "Unlimited users" @@ -9038,7 +9038,7 @@ "message": "Current" }, "secretsManagerSubInfo": { - "message": "Your Secrets Manager subscription will upgrade base on the plan selected" + "message": "Sinu Secrets Manageri tellimus uueneb vastavalt sinu valitud plaanile" }, "bitwardenPasswordManager": { "message": "Bitwarden Password Manager" @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index dbabb5e4a2..091d5c62f9 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 380bd9f594..c977cc38f8 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index c9ebc38914..6952ef43b2 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -9050,8 +9050,11 @@ "message": "Julkinen API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Lisää sisältöä on saatavilla" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Muokkaa käyttöoikeuksia" diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 0f9f0dd212..303f12643f 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index a0f58f9f58..b1ac195190 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -9050,8 +9050,11 @@ "message": "API publique", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Du contenu supplémentaire est disponible" + "showCharacterCount": { + "message": "Afficher le nombre de caractères" + }, + "hideCharacterCount": { + "message": "Cacher le nombre de caractères" }, "editAccess": { "message": "Modifier l'accès" diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 3d105f01e0..f7d3327707 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index bef6306e92..c0f0f764a9 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index ee6caf70e1..6590213579 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 57e49d591c..60c214ad18 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -49,19 +49,19 @@ "message": "Vjerodajnice za prijavu" }, "personalDetails": { - "message": "Personal details" + "message": "Osobni podaci" }, "identification": { - "message": "Identification" + "message": "Identifikacija" }, "contactInfo": { - "message": "Contact info" + "message": "Kontakt podaci" }, "cardDetails": { - "message": "Card details" + "message": "Detalji kartice" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ detalji", "placeholders": { "brand": { "content": "$1", @@ -70,7 +70,7 @@ } }, "itemHistory": { - "message": "Item history" + "message": "Povijest stavke" }, "authenticatorKey": { "message": "Kôd za provjeru" @@ -464,7 +464,7 @@ "message": "Ime i prezime" }, "address": { - "message": "Address" + "message": "Adresa" }, "address1": { "message": "Adresa 1" @@ -9050,8 +9050,11 @@ "message": "Javni API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Dostupan je dodatni sadržaj" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Uredi pristup" diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index a54509a011..f216aaa91a 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -9050,8 +9050,11 @@ "message": "Nyilvános API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Kiegészítő tartalom érhető el." + "showCharacterCount": { + "message": "Karakterszámláló megjelenítése" + }, + "hideCharacterCount": { + "message": "Karakterszámláló elrejtése" }, "editAccess": { "message": "Hozzáférés szerkesztése" diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 5473dece73..30e1638a86 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 8d0d248adc..b2849a2033 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -9050,8 +9050,11 @@ "message": "API pubblica", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Sono disponibili ulteriori contenuti" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Modifica accesso" diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 0cff337d39..8897329333 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -9050,8 +9050,11 @@ "message": "公開 API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "追加コンテンツが利用可能です" + "showCharacterCount": { + "message": "文字数を表示" + }, + "hideCharacterCount": { + "message": "文字数を隠す" }, "editAccess": { "message": "編集権限" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index d096dc4d17..67364dde68 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 2127f7a553..21e8cf6299 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 58014cce85..c9c9ab49f7 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 41aab58f69..934929b1ee 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index cdfa7d28ca..3fa2249bcd 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -9050,8 +9050,11 @@ "message": "Publiskais API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Ir pieejams papildu saturs" + "showCharacterCount": { + "message": "Rādīt rakstzīmju skaitu" + }, + "hideCharacterCount": { + "message": "Paslēpt rakstzīmju skaitu" }, "editAccess": { "message": "Labot piekļuvi" diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 5f34bbb176..12d14414b5 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 2127f7a553..21e8cf6299 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 2127f7a553..21e8cf6299 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 26319da87b..a33e12c544 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 01e22e16b0..f9ba74ae95 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 60f02836bf..0f208d53fc 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -9050,8 +9050,11 @@ "message": "Openbare API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Extra inhoud beschikbaar" + "showCharacterCount": { + "message": "Aantal tekens weergeven" + }, + "hideCharacterCount": { + "message": "Aantal tekens verbergen" }, "editAccess": { "message": "Toegang bewerken" diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 59e6bc8fc4..ff329b1a78 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 2127f7a553..21e8cf6299 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index b5d5bacc6f..205104c177 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 27d0fcaca5..2aecf452ea 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 0217ec4127..5080534922 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -9050,8 +9050,11 @@ "message": "API pública", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Estão disponíveis conteúdos adicionais" + "showCharacterCount": { + "message": "Mostrar contagem de caracteres" + }, + "hideCharacterCount": { + "message": "Ocultar contagem de caracteres" }, "editAccess": { "message": "Editar acesso" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 629f1a24ad..7384da233f 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index a55ab9d52c..b3e2c6efa7 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -222,7 +222,7 @@ "message": "Истек срок действия карты" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "Если вы заменили карту, обновите информацию о ней" }, "expirationMonth": { "message": "Месяц" @@ -3754,7 +3754,7 @@ "message": "Установите лимит мест для вашей подписки. По достижении этого лимита вы не сможете приглашать новых пользователей." }, "limitSmSubscriptionDesc": { - "message": "Установите лимит мест для вашей подписки на Менеджер секретов. По достижении этого лимита вы не сможете приглашать новых пользователей." + "message": "Установите лимит мест для вашей подписки на Менеджер секретов. По достижении этого лимита вы не сможете приглашать новых пользователей.Менеджер секретов" }, "maxSeatLimit": { "message": "Предел мест (необязательно)", @@ -9050,8 +9050,11 @@ "message": "Публичный API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Дополнительный контент доступен" + "showCharacterCount": { + "message": "Показать количество символов" + }, + "hideCharacterCount": { + "message": "Скрыть количество символов" }, "editAccess": { "message": "Изменить доступ" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index fcc73af4a2..f458513267 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 7b42265fec..1d90ff6126 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -49,19 +49,19 @@ "message": "Prihlasovacie údaje" }, "personalDetails": { - "message": "Personal details" + "message": "Osobné údaje" }, "identification": { - "message": "Identification" + "message": "Identifikácia" }, "contactInfo": { - "message": "Contact info" + "message": "Kontaktné informácie" }, "cardDetails": { - "message": "Card details" + "message": "Podrobnosti o karte" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Podrobnosti o $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -70,7 +70,7 @@ } }, "itemHistory": { - "message": "Item history" + "message": "História položky" }, "authenticatorKey": { "message": "Kľúč overovacej aplikácie" @@ -464,7 +464,7 @@ "message": "Celé meno" }, "address": { - "message": "Address" + "message": "Adresa" }, "address1": { "message": "Adresa 1" @@ -9050,8 +9050,11 @@ "message": "Verejné API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "K dispozícii je ďalší obsah" + "showCharacterCount": { + "message": "Zobraziť počítadlo znakov" + }, + "hideCharacterCount": { + "message": "Skryť počítadlo znakov" }, "editAccess": { "message": "Upraviť prístup" diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index b804301bcc..6ff12ad459 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 38efcf5e2a..f0bea1eb57 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -9050,8 +9050,11 @@ "message": "Јавни API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 5c5e0a9251..5df939b84e 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 0fc92e37fa..5aab64e9cb 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Visa antal tecken" + }, + "hideCharacterCount": { + "message": "Dölj antal tecken" }, "editAccess": { "message": "Redigera åtkomst" diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 2127f7a553..21e8cf6299 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 3d7f0c4c9d..9055bc1c9f 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 95aff84512..640ddc5a5d 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -9050,8 +9050,11 @@ "message": "Genel API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Ek içerikler mevcut" + "showCharacterCount": { + "message": "Karakter sayacını göster" + }, + "hideCharacterCount": { + "message": "Karakter sayacını gizle" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 61f5962e6e..c8ae5f4b56 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -49,19 +49,19 @@ "message": "Облікові дані для входу" }, "personalDetails": { - "message": "Personal details" + "message": "Особисті дані" }, "identification": { - "message": "Identification" + "message": "Ідентифікація" }, "contactInfo": { - "message": "Contact info" + "message": "Контактні дані" }, "cardDetails": { - "message": "Card details" + "message": "Подробиці картки" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "Подробиці $BRAND$", "placeholders": { "brand": { "content": "$1", @@ -70,7 +70,7 @@ } }, "itemHistory": { - "message": "Item history" + "message": "Історія запису" }, "authenticatorKey": { "message": "Ключ автентифікації" @@ -464,7 +464,7 @@ "message": "Повне ім'я" }, "address": { - "message": "Address" + "message": "Адреса" }, "address1": { "message": "Адреса 1" @@ -9050,8 +9050,11 @@ "message": "Відкритий API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Доступний додатковий вміст" + "showCharacterCount": { + "message": "Показати кількість символів" + }, + "hideCharacterCount": { + "message": "Приховати кількість символів" }, "editAccess": { "message": "Редагувати доступ" diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 21f040dd0e..90eca2660e 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index f6280a0011..34804b736e 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -9050,8 +9050,11 @@ "message": "公共 API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "其他内容可用" + "showCharacterCount": { + "message": "显示字符计数" + }, + "hideCharacterCount": { + "message": "隐藏字符计数" }, "editAccess": { "message": "编辑权限" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 77d1dd696f..680d375982 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -9050,8 +9050,11 @@ "message": "Public API", "description": "The text, 'API', is an acronymn and should not be translated." }, - "additionalContentAvailable": { - "message": "Additional content is available" + "showCharacterCount": { + "message": "Show character count" + }, + "hideCharacterCount": { + "message": "Hide character count" }, "editAccess": { "message": "Edit access" diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts index 20d33e314a..6aab54f77d 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts @@ -10,13 +10,17 @@ describe("ImportService", () => { const mockOrganizationId = "mockOrgId" as OrganizationId; const reportApiService = mock(); let memberAccessReportService: MemberAccessReportService; - const i18nService = mock(); + const i18nMock = mock({ + t(key) { + return key; + }, + }); beforeEach(() => { reportApiService.getMemberAccessData.mockImplementation(() => Promise.resolve(memberAccessReportsMock), ); - memberAccessReportService = new MemberAccessReportService(reportApiService, i18nService); + memberAccessReportService = new MemberAccessReportService(reportApiService, i18nMock); }); describe("generateMemberAccessReportView", () => { @@ -92,16 +96,16 @@ describe("ImportService", () => { expect.objectContaining({ email: "sjohnson@email.com", name: "Sarah Johnson", - twoStepLogin: "On", - accountRecovery: "On", + twoStepLogin: "memberAccessReportTwoFactorEnabledTrue", + accountRecovery: "memberAccessReportAuthenticationEnabledTrue", group: "Group 1", totalItems: "20", }), expect.objectContaining({ email: "jlull@email.com", name: "James Lull", - twoStepLogin: "Off", - accountRecovery: "Off", + twoStepLogin: "memberAccessReportTwoFactorEnabledFalse", + accountRecovery: "memberAccessReportAuthenticationEnabledFalse", group: "Group 4", totalItems: "5", }), diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts index 4ab7ffd6e4..3616893e23 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts @@ -64,14 +64,25 @@ export class MemberAccessReportService { const exportItems = memberAccessReports.flatMap((report) => { const userDetails = report.accessDetails.map((detail) => { + const collectionName = collectionNameMap.get(detail.collectionName.encryptedString); return { email: report.email, name: report.userName, - twoStepLogin: report.twoFactorEnabled ? "On" : "Off", - accountRecovery: report.accountRecoveryEnabled ? "On" : "Off", - group: detail.groupName, - collection: collectionNameMap.get(detail.collectionName.encryptedString), - collectionPermission: this.getPermissionText(detail), + twoStepLogin: report.twoFactorEnabled + ? this.i18nService.t("memberAccessReportTwoFactorEnabledTrue") + : this.i18nService.t("memberAccessReportTwoFactorEnabledFalse"), + accountRecovery: report.accountRecoveryEnabled + ? this.i18nService.t("memberAccessReportAuthenticationEnabledTrue") + : this.i18nService.t("memberAccessReportAuthenticationEnabledFalse"), + group: detail.groupName + ? detail.groupName + : this.i18nService.t("memberAccessReportNoGroup"), + collection: collectionName + ? collectionName + : this.i18nService.t("memberAccessReportNoCollection"), + collectionPermission: detail.collectionId + ? this.getPermissionText(detail) + : this.i18nService.t("memberAccessReportNoCollectionPermission"), totalItems: detail.itemCount.toString(), }; }); diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-export.view.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-export.view.ts index be9035e1f2..2b009011af 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-export.view.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-export.view.ts @@ -10,12 +10,12 @@ export type MemberAccessExportItem = { }; export const userReportItemHeaders: { [key in keyof MemberAccessExportItem]: string } = { - email: "Email Address", - name: "Full Name", + email: "Email", + name: "Name", twoStepLogin: "Two-Step Login", accountRecovery: "Account Recovery", - group: "Group Name", - collection: "Collection Name", + group: "Group", + collection: "Collection", collectionPermission: "Collection Permission", totalItems: "Total Items", }; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 4e6621d1c8..3f092bee71 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -127,9 +127,13 @@ import { BillingApiServiceAbstraction, OrganizationBillingServiceAbstraction, } from "@bitwarden/common/billing/abstractions"; +import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/account/account-billing-api.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction"; +import { AccountBillingApiService } from "@bitwarden/common/billing/services/account/account-billing-api.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service"; +import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -245,6 +249,10 @@ import { FolderService } from "@bitwarden/common/vault/services/folder/folder.se import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service"; import { ToastService } from "@bitwarden/components"; +import { + GeneratorHistoryService, + LocalGeneratorHistoryService, +} from "@bitwarden/generator-history"; import { legacyPasswordGenerationServiceFactory, legacyUsernameGenerationServiceFactory, @@ -594,6 +602,11 @@ const safeProviders: SafeProvider[] = [ StateProvider, ], }), + safeProvider({ + provide: GeneratorHistoryService, + useClass: LocalGeneratorHistoryService, + deps: [EncryptService, CryptoServiceAbstraction, StateProvider], + }), safeProvider({ provide: UsernameGenerationServiceAbstraction, useFactory: legacyUsernameGenerationServiceFactory, @@ -978,6 +991,16 @@ const safeProviders: SafeProvider[] = [ // subscribes to sync notifications and will update itself based on that. deps: [ApiServiceAbstraction, SyncService], }), + safeProvider({ + provide: OrganizationBillingApiServiceAbstraction, + useClass: OrganizationBillingApiService, + deps: [ApiServiceAbstraction], + }), + safeProvider({ + provide: AccountBillingApiServiceAbstraction, + useClass: AccountBillingApiService, + deps: [ApiServiceAbstraction], + }), safeProvider({ provide: DefaultConfigService, useClass: DefaultConfigService, diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html index 39570dabc8..6603fa970d 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html @@ -13,9 +13,16 @@

-

- {{ title }} -

+ + +

+ {{ title }} +

+ +

+ {{ title }} +

+
{{ subtitle }}
diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.spec.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.spec.ts new file mode 100644 index 0000000000..6b39c90fc9 --- /dev/null +++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.spec.ts @@ -0,0 +1,81 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { BehaviorSubject } from "rxjs"; + +import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; + +import { VaultTimeoutInputComponent } from "./vault-timeout-input.component"; + +describe("VaultTimeoutInputComponent", () => { + let component: VaultTimeoutInputComponent; + let fixture: ComponentFixture; + const get$ = jest.fn().mockReturnValue(new BehaviorSubject({})); + const availableVaultTimeoutActions$ = jest.fn().mockReturnValue(new BehaviorSubject([])); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [VaultTimeoutInputComponent], + providers: [ + { provide: PolicyService, useValue: { get$ } }, + { provide: VaultTimeoutSettingsService, useValue: { availableVaultTimeoutActions$ } }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(VaultTimeoutInputComponent); + component = fixture.componentInstance; + component.vaultTimeoutOptions = [ + { name: "oneMinute", value: 1 }, + { name: "fiveMinutes", value: 5 }, + { name: "fifteenMinutes", value: 15 }, + { name: "thirtyMinutes", value: 30 }, + { name: "oneHour", value: 60 }, + { name: "fourHours", value: 240 }, + { name: "onRefresh", value: VaultTimeoutStringType.OnRestart }, + ]; + fixture.detectChanges(); + }); + + describe("form", () => { + beforeEach(async () => { + await component.ngOnInit(); + }); + + it("invokes the onChange associated with `ControlValueAccessor`", () => { + const onChange = jest.fn(); + component.registerOnChange(onChange); + + component.form.controls.vaultTimeout.setValue(VaultTimeoutStringType.OnRestart); + + expect(onChange).toHaveBeenCalledWith(VaultTimeoutStringType.OnRestart); + }); + + it("updates custom value to match preset option", () => { + // 1 hour + component.form.controls.vaultTimeout.setValue(60); + + expect(component.form.value.custom).toEqual({ hours: 1, minutes: 0 }); + + // 17 minutes + component.form.controls.vaultTimeout.setValue(17); + + expect(component.form.value.custom).toEqual({ hours: 0, minutes: 17 }); + + // 2.25 hours + component.form.controls.vaultTimeout.setValue(135); + + expect(component.form.value.custom).toEqual({ hours: 2, minutes: 15 }); + }); + + it("sets custom timeout to 0 when a preset string option is selected", () => { + // Set custom value to random values + component.form.controls.custom.setValue({ hours: 1, minutes: 1 }); + + component.form.controls.vaultTimeout.setValue(VaultTimeoutStringType.OnLocked); + + expect(component.form.value.custom).toEqual({ hours: 0, minutes: 0 }); + }); + }); +}); diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts index f1cfcec8c3..7c76083560 100644 --- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts +++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts @@ -4,6 +4,8 @@ import { AbstractControl, ControlValueAccessor, FormBuilder, + FormControl, + FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ReactiveFormsModule, @@ -22,13 +24,15 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { VaultTimeout, VaultTimeoutOption } from "@bitwarden/common/types/vault-timeout.type"; import { FormFieldModule, SelectModule } from "@bitwarden/components"; -interface VaultTimeoutFormValue { - vaultTimeout: VaultTimeout | null; - custom: { - hours: number | null; - minutes: number | null; - }; -} +type VaultTimeoutForm = FormGroup<{ + vaultTimeout: FormControl; + custom: FormGroup<{ + hours: FormControl; + minutes: FormControl; + }>; +}>; + +type VaultTimeoutFormValue = VaultTimeoutForm["value"]; @Component({ selector: "auth-vault-timeout-input", @@ -64,7 +68,7 @@ export class VaultTimeoutInputComponent static CUSTOM_VALUE = -100; static MIN_CUSTOM_MINUTES = 0; - form = this.formBuilder.group({ + form: VaultTimeoutForm = this.formBuilder.group({ vaultTimeout: [null], custom: this.formBuilder.group({ hours: [null], @@ -120,7 +124,7 @@ export class VaultTimeoutInputComponent takeUntil(this.destroy$), ) .subscribe((value) => { - const current = Math.max(value, 0); + const current = typeof value === "string" ? 0 : Math.max(value, 0); // This cannot emit an event b/c it would cause form.valueChanges to fire again // and we are already handling that above so just silently update diff --git a/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts new file mode 100644 index 0000000000..4b67ce55c3 --- /dev/null +++ b/libs/common/src/billing/abstractions/account/account-billing-api.service.abstraction.ts @@ -0,0 +1,12 @@ +import { + BillingInvoiceResponse, + BillingTransactionResponse, +} from "@bitwarden/common/billing/models/response/billing.response"; + +export class AccountBillingApiServiceAbstraction { + getBillingInvoices: (id: string, startAfter?: string) => Promise; + getBillingTransactions: ( + id: string, + startAfter?: string, + ) => Promise; +} diff --git a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts new file mode 100644 index 0000000000..4b3592bb6d --- /dev/null +++ b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts @@ -0,0 +1,12 @@ +import { + BillingInvoiceResponse, + BillingTransactionResponse, +} from "@bitwarden/common/billing/models/response/billing.response"; + +export class OrganizationBillingApiServiceAbstraction { + getBillingInvoices: (id: string, startAfter?: string) => Promise; + getBillingTransactions: ( + id: string, + startAfter?: string, + ) => Promise; +} diff --git a/libs/common/src/billing/models/response/billing.response.ts b/libs/common/src/billing/models/response/billing.response.ts index 45d3cf1e67..b94fc1b64b 100644 --- a/libs/common/src/billing/models/response/billing.response.ts +++ b/libs/common/src/billing/models/response/billing.response.ts @@ -29,6 +29,7 @@ export class BillingSourceResponse extends BaseResponse { } export class BillingInvoiceResponse extends BaseResponse { + id: string; url: string; pdfUrl: string; number: string; @@ -38,6 +39,7 @@ export class BillingInvoiceResponse extends BaseResponse { constructor(response: any) { super(response); + this.id = this.getResponseProperty("Id"); this.url = this.getResponseProperty("Url"); this.pdfUrl = this.getResponseProperty("PdfUrl"); this.number = this.getResponseProperty("Number"); diff --git a/libs/common/src/billing/services/account/account-billing-api.service.ts b/libs/common/src/billing/services/account/account-billing-api.service.ts new file mode 100644 index 0000000000..ddd5bad02e --- /dev/null +++ b/libs/common/src/billing/services/account/account-billing-api.service.ts @@ -0,0 +1,34 @@ +import { ApiService } from "../../../abstractions/api.service"; +import { AccountBillingApiServiceAbstraction } from "../../abstractions/account/account-billing-api.service.abstraction"; +import { + BillingInvoiceResponse, + BillingTransactionResponse, +} from "../../models/response/billing.response"; + +export class AccountBillingApiService implements AccountBillingApiServiceAbstraction { + constructor(private apiService: ApiService) {} + + async getBillingInvoices(startAfter?: string): Promise { + const queryParams = startAfter ? `?startAfter=${startAfter}` : ""; + const r = await this.apiService.send( + "GET", + `/accounts/billing/invoices${queryParams}`, + null, + true, + true, + ); + return r?.map((i: any) => new BillingInvoiceResponse(i)) || []; + } + + async getBillingTransactions(startAfter?: string): Promise { + const queryParams = startAfter ? `?startAfter=${startAfter}` : ""; + const r = await this.apiService.send( + "GET", + `/accounts/billing/transactions${queryParams}`, + null, + true, + true, + ); + return r?.map((i: any) => new BillingTransactionResponse(i)) || []; + } +} diff --git a/libs/common/src/billing/services/organization/organization-billing-api.service.ts b/libs/common/src/billing/services/organization/organization-billing-api.service.ts new file mode 100644 index 0000000000..acf12e8320 --- /dev/null +++ b/libs/common/src/billing/services/organization/organization-billing-api.service.ts @@ -0,0 +1,37 @@ +import { ApiService } from "../../../abstractions/api.service"; +import { OrganizationBillingApiServiceAbstraction } from "../../abstractions/organizations/organization-billing-api.service.abstraction"; +import { + BillingInvoiceResponse, + BillingTransactionResponse, +} from "../../models/response/billing.response"; + +export class OrganizationBillingApiService implements OrganizationBillingApiServiceAbstraction { + constructor(private apiService: ApiService) {} + + async getBillingInvoices(id: string, startAfter?: string): Promise { + const queryParams = startAfter ? `?startAfter=${startAfter}` : ""; + const r = await this.apiService.send( + "GET", + `/organizations/${id}/billing/invoices${queryParams}`, + null, + true, + true, + ); + return r?.map((i: any) => new BillingInvoiceResponse(i)) || []; + } + + async getBillingTransactions( + id: string, + startAfter?: string, + ): Promise { + const queryParams = startAfter ? `?startAfter=${startAfter}` : ""; + const r = await this.apiService.send( + "GET", + `/organizations/${id}/billing/transactions${queryParams}`, + null, + true, + true, + ); + return r?.map((i: any) => new BillingTransactionResponse(i)) || []; + } +} diff --git a/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts index 4e164c4577..797133edd2 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-active-request-manager.abstraction.ts @@ -2,9 +2,22 @@ import { Observable, Subject } from "rxjs"; import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; +export const Fido2ActiveRequestEvents = { + Refresh: "refresh-fido2-active-request", + Abort: "abort-fido2-active-request", + Continue: "continue-fido2-active-request", +} as const; + +type Fido2ActiveRequestEvent = typeof Fido2ActiveRequestEvents; + +export type RequestResult = + | { type: Fido2ActiveRequestEvent["Refresh"] } + | { type: Fido2ActiveRequestEvent["Abort"] } + | { type: Fido2ActiveRequestEvent["Continue"]; credentialId: string }; + export interface ActiveRequest { credentials: Fido2CredentialView[]; - subject: Subject; + subject: Subject; } export type RequestCollection = Readonly<{ [tabId: number]: ActiveRequest }>; @@ -16,6 +29,7 @@ export abstract class Fido2ActiveRequestManager { tabId: number, credentials: Fido2CredentialView[], abortController: AbortController, - ) => Promise; + ) => Promise; removeActiveRequest: (tabId: number) => void; + removeAllActiveRequests: () => void; } diff --git a/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts index 2ba67a48be..6467af5fc1 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-client.service.abstraction.ts @@ -1,7 +1,3 @@ -import { Observable } from "rxjs"; - -import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; - export const UserRequestedFallbackAbortReason = "UserRequestedFallback"; export type UserVerification = "discouraged" | "preferred" | "required"; @@ -20,10 +16,6 @@ export type UserVerification = "discouraged" | "preferred" | "required"; export abstract class Fido2ClientService { isFido2FeatureEnabled: (hostname: string, origin: string) => Promise; - availableAutofillCredentials$: (tabId: number) => Observable; - - autofillCredential: (tabId: number, credentialId: string) => Promise; - /** * Allows WebAuthn Relying Party scripts to request the creation of a new public key credential source. * For more information please see: https://www.w3.org/TR/webauthn-3/#sctn-createCredential diff --git a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts index 55232162d4..70fe0b092b 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-user-interface.service.abstraction.ts @@ -45,6 +45,11 @@ export interface PickCredentialParams { * Bypass the UI and assume that the user has already interacted with the authenticator. */ assumeUserPresence?: boolean; + + /** + * Identifies whether a cipher requires a master password reprompt when getting a credential. + */ + masterPasswordRepromptRequired?: boolean; } /** diff --git a/libs/common/src/platform/services/fido2/fido2-active-request-manager.ts b/libs/common/src/platform/services/fido2/fido2-active-request-manager.ts index 0f82d8a09c..d24941ee4e 100644 --- a/libs/common/src/platform/services/fido2/fido2-active-request-manager.ts +++ b/libs/common/src/platform/services/fido2/fido2-active-request-manager.ts @@ -14,6 +14,8 @@ import { ActiveRequest, RequestCollection, Fido2ActiveRequestManager as Fido2ActiveRequestManagerAbstraction, + Fido2ActiveRequestEvents, + RequestResult, } from "../../abstractions/fido2/fido2-active-request-manager.abstraction"; export class Fido2ActiveRequestManager implements Fido2ActiveRequestManagerAbstraction { @@ -53,7 +55,7 @@ export class Fido2ActiveRequestManager implements Fido2ActiveRequestManagerAbstr tabId: number, credentials: Fido2CredentialView[], abortController: AbortController, - ): Promise { + ): Promise { const newRequest: ActiveRequest = { credentials, subject: new Subject(), @@ -65,10 +67,10 @@ export class Fido2ActiveRequestManager implements Fido2ActiveRequestManagerAbstr const abortListener = () => this.abortActiveRequest(tabId); abortController.signal.addEventListener("abort", abortListener); - const credentialId = firstValueFrom(newRequest.subject); + const requestResult = firstValueFrom(newRequest.subject); abortController.signal.removeEventListener("abort", abortListener); - return credentialId; + return requestResult; } /** @@ -85,12 +87,23 @@ export class Fido2ActiveRequestManager implements Fido2ActiveRequestManagerAbstr }); } + /** + * Removes and aborts all active requests. + */ + removeAllActiveRequests() { + Object.keys(this.activeRequests$.value).forEach((tabId) => { + this.abortActiveRequest(Number(tabId)); + }); + this.updateRequests(() => ({})); + } + /** * Aborts the active request associated with a given tab id. * * @param tabId - The tab id to abort the active request for. */ private abortActiveRequest(tabId: number): void { + this.activeRequests$.value[tabId]?.subject.next({ type: Fido2ActiveRequestEvents.Abort }); this.activeRequests$.value[tabId]?.subject.error( new DOMException("The operation either timed out or was not allowed.", "AbortError"), ); diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index adb1adcce4..289e94e6ef 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -576,6 +576,7 @@ describe("FidoAuthenticatorService", () => { expect(userInterfaceSession.pickCredential).toHaveBeenCalledWith({ cipherIds: ciphers.map((c) => c.id), userVerification: false, + masterPasswordRepromptRequired: false, }); }); @@ -592,6 +593,7 @@ describe("FidoAuthenticatorService", () => { expect(userInterfaceSession.pickCredential).toHaveBeenCalledWith({ cipherIds: [discoverableCiphers[0].id], userVerification: false, + masterPasswordRepromptRequired: false, }); }); @@ -609,6 +611,7 @@ describe("FidoAuthenticatorService", () => { expect(userInterfaceSession.pickCredential).toHaveBeenCalledWith({ cipherIds: ciphers.map((c) => c.id), userVerification, + masterPasswordRepromptRequired: false, }); }); } diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index ef09a3d160..ddcc079eb9 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -160,6 +160,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr } const reencrypted = await this.cipherService.encrypt(cipher, activeUserId); await this.cipherService.updateWithServer(reencrypted); + await this.cipherService.clearCache(activeUserId); credentialId = fido2Credential.credentialId; } catch (error) { this.logService?.error( @@ -243,12 +244,18 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr } let response = { cipherId: cipherOptions[0].id, userVerified: false }; + const masterPasswordRepromptRequired = cipherOptions.some( + (cipher) => cipher.reprompt !== CipherRepromptType.None, + ); - if (this.requiresUserVerificationPrompt(params, cipherOptions)) { + if ( + this.requiresUserVerificationPrompt(params, cipherOptions, masterPasswordRepromptRequired) + ) { response = await userInterfaceSession.pickCredential({ cipherIds: cipherOptions.map((cipher) => cipher.id), userVerification: params.requireUserVerification, assumeUserPresence: params.assumeUserPresence, + masterPasswordRepromptRequired, }); } @@ -292,6 +299,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr ); const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId); await this.cipherService.updateWithServer(encrypted); + await this.cipherService.clearCache(activeUserId); } const authenticatorData = await generateAuthData({ @@ -330,13 +338,14 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr private requiresUserVerificationPrompt( params: Fido2AuthenticatorGetAssertionParams, cipherOptions: CipherView[], + masterPasswordRepromptRequired: boolean, ): boolean { return ( params.requireUserVerification || !params.assumeUserPresence || cipherOptions.length > 1 || cipherOptions.length === 0 || - cipherOptions.some((cipher) => cipher.reprompt !== CipherRepromptType.None) + masterPasswordRepromptRequired ); } diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts index c0ae2cabfb..582849ebc1 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts @@ -6,11 +6,12 @@ import { AuthenticationStatus } from "../../../auth/enums/authentication-status" import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { Utils } from "../../../platform/misc/utils"; import { VaultSettingsService } from "../../../vault/abstractions/vault-settings/vault-settings.service"; -import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; import { ConfigService } from "../../abstractions/config/config.service"; import { ActiveRequest, + Fido2ActiveRequestEvents, Fido2ActiveRequestManager, + RequestResult, } from "../../abstractions/fido2/fido2-active-request-manager.abstraction"; import { Fido2AuthenticatorError, @@ -56,7 +57,10 @@ describe("FidoAuthenticatorService", () => { domainSettingsService = mock(); taskSchedulerService = mock(); activeRequest = mock({ - subject: new BehaviorSubject(""), + subject: new BehaviorSubject({ + type: Fido2ActiveRequestEvents.Continue, + credentialId: "", + }), }); requestManager = mock({ getActiveRequest$: (tabId: number) => new BehaviorSubject(activeRequest), @@ -615,7 +619,10 @@ describe("FidoAuthenticatorService", () => { }); beforeEach(() => { - requestManager.newActiveRequest.mockResolvedValue(crypto.randomUUID()); + requestManager.newActiveRequest.mockResolvedValue({ + type: Fido2ActiveRequestEvents.Continue, + credentialId: crypto.randomUUID(), + }); authenticator.getAssertion.mockResolvedValue(createAuthenticatorAssertResult()); }); @@ -676,28 +683,6 @@ describe("FidoAuthenticatorService", () => { }; } }); - - describe("autofill of credentials through the active request manager", () => { - it("returns an observable that updates with an array of the credentials for active Fido2 requests", async () => { - const activeRequestCredentials = mock(); - activeRequest.credentials = [activeRequestCredentials]; - - const observable = client.availableAutofillCredentials$(tab.id); - observable.subscribe((credentials) => { - expect(credentials).toEqual([activeRequestCredentials]); - }); - }); - - it("triggers the logic of the next behavior subject of an active request", async () => { - const activeRequestCredentials = mock(); - activeRequest.credentials = [activeRequestCredentials]; - jest.spyOn(activeRequest.subject, "next"); - - await client.autofillCredential(tab.id, activeRequestCredentials.credentialId); - - expect(activeRequest.subject.next).toHaveBeenCalled(); - }); - }); }); /** This is a fake function that always returns the same byte sequence */ diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.ts b/libs/common/src/platform/services/fido2/fido2-client.service.ts index 972d688912..849e0c7256 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.ts @@ -1,13 +1,15 @@ -import { firstValueFrom, map, Observable, Subscription } from "rxjs"; +import { firstValueFrom, Subscription } from "rxjs"; import { parse } from "tldts"; import { AuthService } from "../../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; import { VaultSettingsService } from "../../../vault/abstractions/vault-settings/vault-settings.service"; -import { Fido2CredentialView } from "../../../vault/models/view/fido2-credential.view"; import { ConfigService } from "../../abstractions/config/config.service"; -import { Fido2ActiveRequestManager } from "../../abstractions/fido2/fido2-active-request-manager.abstraction"; +import { + Fido2ActiveRequestEvents, + Fido2ActiveRequestManager, +} from "../../abstractions/fido2/fido2-active-request-manager.abstraction"; import { Fido2AuthenticatorError, Fido2AuthenticatorErrorCode, @@ -73,17 +75,6 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { ); } - availableAutofillCredentials$(tabId: number): Observable { - return this.requestManager - .getActiveRequest$(tabId) - .pipe(map((request) => request?.credentials ?? [])); - } - - async autofillCredential(tabId: number, credentialId: string) { - const request = this.requestManager.getActiveRequest(tabId); - request.subject.next(credentialId); - } - async isFido2FeatureEnabled(hostname: string, origin: string): Promise { const isUserLoggedIn = (await this.authService.getAuthStatus()) !== AuthenticationStatus.LoggedOut; @@ -385,12 +376,23 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { this.logService?.info( `[Fido2Client] started mediated request, available credentials: ${availableCredentials.length}`, ); - const credentialId = await this.requestManager.newActiveRequest( + const requestResult = await this.requestManager.newActiveRequest( tab.id, availableCredentials, abortController, ); - params.allowedCredentialIds = [Fido2Utils.bufferToString(guidToRawFormat(credentialId))]; + + if (requestResult.type === Fido2ActiveRequestEvents.Refresh) { + continue; + } + + if (requestResult.type === Fido2ActiveRequestEvents.Abort) { + break; + } + + params.allowedCredentialIds = [ + Fido2Utils.bufferToString(guidToRawFormat(requestResult.credentialId)), + ]; assumeUserPresence = true; const clientDataHash = await crypto.subtle.digest({ name: "SHA-256" }, clientDataJSONBytes); diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.ts index 6c8ce3f048..b4a154133c 100644 --- a/libs/common/src/platform/services/user-auto-unlock-key.service.ts +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.ts @@ -16,10 +16,11 @@ export class UserAutoUnlockKeyService { * However, for users that have the auto unlock user key set, we need to set the user key in memory * on application bootstrap and on active account changes so that the user's vault loads unlocked. * @param userId - The user id to check for an auto user key. + * @returns True if the auto user key is set successfully, false otherwise. */ - async setUserKeyInMemoryIfAutoUserKeySet(userId: UserId): Promise { + async setUserKeyInMemoryIfAutoUserKeySet(userId: UserId): Promise { if (userId == null) { - return; + return false; } const autoUserKey = await this.cryptoService.getUserKeyFromStorage( @@ -28,9 +29,10 @@ export class UserAutoUnlockKeyService { ); if (autoUserKey == null) { - return; + return false; } await this.cryptoService.setUserKey(autoUserKey, userId); + return true; } } diff --git a/libs/components/src/item/item-content.component.html b/libs/components/src/item/item-content.component.html index da45c8b3b6..bed8b2f5b7 100644 --- a/libs/components/src/item/item-content.component.html +++ b/libs/components/src/item/item-content.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/libs/tools/generator/components/src/credential-generator-history.component.html b/libs/tools/generator/components/src/credential-generator-history.component.html new file mode 100644 index 0000000000..2b8802b932 --- /dev/null +++ b/libs/tools/generator/components/src/credential-generator-history.component.html @@ -0,0 +1,20 @@ + +
+
+

{{ credential.credential }}

+ {{ + credential.generationDate | date: "medium" + }} +
+ +
+
diff --git a/libs/tools/generator/components/src/credential-generator-history.component.ts b/libs/tools/generator/components/src/credential-generator-history.component.ts new file mode 100644 index 0000000000..2f76027a94 --- /dev/null +++ b/libs/tools/generator/components/src/credential-generator-history.component.ts @@ -0,0 +1,58 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { RouterLink } from "@angular/router"; +import { BehaviorSubject, distinctUntilChanged, map, switchMap } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { + CardComponent, + IconButtonModule, + NoItemsModule, + SectionComponent, + SectionHeaderComponent, +} from "@bitwarden/components"; +import { GeneratedCredential, GeneratorHistoryService } from "@bitwarden/generator-history"; + +@Component({ + standalone: true, + selector: "bit-credential-generator-history", + templateUrl: "credential-generator-history.component.html", + imports: [ + CommonModule, + IconButtonModule, + NoItemsModule, + JslibModule, + RouterLink, + CardComponent, + SectionComponent, + SectionHeaderComponent, + ], +}) +export class CredentialGeneratorHistoryComponent { + protected readonly userId$ = new BehaviorSubject(null); + protected readonly credentials$ = new BehaviorSubject([]); + + constructor( + private accountService: AccountService, + private history: GeneratorHistoryService, + ) { + this.accountService.activeAccount$ + .pipe( + takeUntilDestroyed(), + map(({ id }) => id), + distinctUntilChanged(), + ) + .subscribe(this.userId$); + + this.userId$ + .pipe( + takeUntilDestroyed(), + switchMap((id) => id && this.history.credentials$(id)), + map((credentials) => credentials), + ) + .subscribe(this.credentials$); + } +} diff --git a/libs/tools/generator/components/src/empty-credential-history.component.html b/libs/tools/generator/components/src/empty-credential-history.component.html new file mode 100644 index 0000000000..ee28dc5895 --- /dev/null +++ b/libs/tools/generator/components/src/empty-credential-history.component.html @@ -0,0 +1,7 @@ +
+
+ +

{{ "noPasswordsToShow" | i18n }}

+
{{ "noRecentlyGeneratedPassword" | i18n }}
+
+
diff --git a/libs/tools/generator/components/src/empty-credential-history.component.ts b/libs/tools/generator/components/src/empty-credential-history.component.ts new file mode 100644 index 0000000000..1e23adf0bb --- /dev/null +++ b/libs/tools/generator/components/src/empty-credential-history.component.ts @@ -0,0 +1,18 @@ +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { IconModule, TypographyModule } from "@bitwarden/components"; + +import { NoCredentialsIcon } from "./icons/no-credentials.icon"; + +@Component({ + standalone: true, + selector: "bit-empty-credential-history", + templateUrl: "empty-credential-history.component.html", + imports: [JslibModule, IconModule, TypographyModule], +}) +export class EmptyCredentialHistoryComponent { + noCredentialsIcon = NoCredentialsIcon; + + constructor() {} +} diff --git a/libs/tools/generator/components/src/icons/no-credentials.icon.ts b/libs/tools/generator/components/src/icons/no-credentials.icon.ts new file mode 100644 index 0000000000..63843faccc --- /dev/null +++ b/libs/tools/generator/components/src/icons/no-credentials.icon.ts @@ -0,0 +1,27 @@ +import { svgIcon } from "@bitwarden/components"; + +export const NoCredentialsIcon = svgIcon` + + + + + + + + + + + + + + + + + + + + + + + + `; diff --git a/libs/tools/generator/components/src/index.ts b/libs/tools/generator/components/src/index.ts index 0fc0655a02..5915c5d59f 100644 --- a/libs/tools/generator/components/src/index.ts +++ b/libs/tools/generator/components/src/index.ts @@ -1,2 +1,4 @@ export { PassphraseSettingsComponent } from "./passphrase-settings.component"; +export { CredentialGeneratorHistoryComponent } from "./credential-generator-history.component"; +export { EmptyCredentialHistoryComponent } from "./empty-credential-history.component"; export { PasswordSettingsComponent } from "./password-settings.component"; diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index 96cb63fe39..69aa4e9d69 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -46,7 +46,7 @@ {{ field.name }} - {{ "linked" | i18n }}: {{ field.name }} + {{ "cfTypeLinked" | i18n }}: {{ field.name }} =10" - } - }, "node_modules/@electron/get": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", @@ -33522,9 +33494,9 @@ } }, "node_modules/sass-loader": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz", - "integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.1.tgz", + "integrity": "sha512-xACl1ToTsKnL9Ce5yYpRxrLj9QUDCnwZNhzpC7tKiFyA8zXsd3Ap+HGVnbCgkdQcm43E+i6oKAWBsvGA6ZoiMw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 872849eb85..1ed2e6cab9 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "remark-gfm": "4.0.0", "rimraf": "6.0.1", "sass": "1.74.1", - "sass-loader": "14.2.1", + "sass-loader": "16.0.1", "storybook": "8.2.9", "style-loader": "3.3.4", "tailwindcss": "3.4.10", @@ -157,7 +157,6 @@ "@angular/platform-browser": "16.2.12", "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", - "@electron/fuses": "1.8.0", "@koa/multer": "3.0.2", "@koa/router": "12.0.1", "@microsoft/signalr": "8.0.7",