Merge branch 'main' into auth/pm-8111/browser-refresh-login-component

This commit is contained in:
rr-bw 2024-09-09 15:28:03 -07:00
commit b564ff4ca5
No known key found for this signature in database
GPG Key ID: 3FA13C3ADEE51D5D
218 changed files with 3051 additions and 2413 deletions

View File

@ -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'

View File

@ -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",

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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": "Елементи в кошчето"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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": "Αντικείμενα στον κάδο"

View File

@ -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"
},

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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."

View File

@ -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"

View File

@ -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"

View File

@ -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": "ゴミ箱にあるアイテム"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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ē"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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": "Элементы в корзине"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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": "Ставке у смећу"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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": "Записи в смітнику"

View File

@ -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"

View File

@ -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": "回收站中的项目"

View File

@ -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"

View File

@ -217,6 +217,7 @@ export type OverlayBackgroundExtensionMessageHandlers = {
addEditCipherSubmitted: () => void;
editedCipher: () => void;
deletedCipher: () => void;
fido2AbortRequest: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
};
export type PortMessageParam = {

View File

@ -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<AutofillSettingsService>;
let i18nService: MockProxy<I18nService>;
let platformUtilsService: MockProxy<BrowserPlatformUtilsService>;
let availableAutofillCredentialsMock$: BehaviorSubject<Fido2CredentialView[]>;
let fido2ClientService: MockProxy<Fido2ClientService>;
let enablePasskeysMock$: BehaviorSubject<boolean>;
let vaultSettingsServiceMock: MockProxy<VaultSettingsService>;
let fido2ActiveRequestManager: Fido2ActiveRequestManager;
let selectedThemeMock$: BehaviorSubject<ThemeType>;
let themeStateService: MockProxy<ThemeStateService>;
let overlayBackground: OverlayBackground;
@ -159,10 +161,10 @@ describe("OverlayBackground", () => {
autofillSettingsService.inlineMenuVisibility$ = inlineMenuVisibilityMock$;
i18nService = mock<I18nService>();
platformUtilsService = mock<BrowserPlatformUtilsService>();
availableAutofillCredentialsMock$ = new BehaviorSubject([]);
fido2ClientService = mock<Fido2ClientService>({
availableAutofillCredentials$: (_tabId) => availableAutofillCredentialsMock$,
});
enablePasskeysMock$ = new BehaviorSubject(true);
vaultSettingsServiceMock = mock<VaultSettingsService>();
vaultSettingsServiceMock.enablePasskeys$ = enablePasskeysMock$;
fido2ActiveRequestManager = new Fido2ActiveRequestManager();
selectedThemeMock$ = new BehaviorSubject(ThemeType.Light);
themeStateService = mock<ThemeStateService>();
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<chrome.tabs.Tab>({ 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<chrome.runtime.MessageSender>({ 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);
});
});

View File

@ -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<string> | null): boolean {
private async showCipherAsPasskey(
cipher: CipherView,
domainExclusions: Set<string> | null,
): Promise<boolean> {
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<Fido2CredentialView[]> {
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.
*

View File

@ -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<chrome.tabs.Tab>;
let senderMock!: MockProxy<chrome.runtime.MessageSender>;
let logService!: MockProxy<LogService>;
let fido2ActiveRequestManager: MockProxy<Fido2ActiveRequestManager>;
let fido2ClientService!: MockProxy<Fido2ClientService>;
let vaultSettingsService!: MockProxy<VaultSettingsService>;
let scriptInjectorServiceMock!: MockProxy<BrowserScriptInjectorService>;
@ -77,9 +79,11 @@ describe("Fido2Background", () => {
enablePasskeysMock$ = new BehaviorSubject(true);
vaultSettingsService.enablePasskeys$ = enablePasskeysMock$;
fido2ActiveRequestManager = mock<Fido2ActiveRequestManager>();
fido2ClientService.isFido2FeatureEnabled.mockResolvedValue(true);
fido2Background = new Fido2Background(
logService,
fido2ActiveRequestManager,
fido2ClientService,
vaultSettingsService,
scriptInjectorServiceMock,

View File

@ -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) {

View File

@ -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);
}

View File

@ -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.");
}

View File

@ -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 };
}

View File

@ -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,
);
}

View File

@ -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.",

View File

@ -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.",

View File

@ -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],

View File

@ -0,0 +1,20 @@
<popup-page>
<popup-header slot="header" [pageTitle]="'passwordHistory' | i18n" showBackButton>
<ng-container slot="end">
<app-pop-out></app-pop-out>
</ng-container>
</popup-header>
<bit-empty-credential-history *ngIf="!(hasHistory$ | async)" style="display: contents" />
<bit-credential-generator-history *ngIf="hasHistory$ | async" />
<popup-footer slot="footer">
<button
[disabled]="!(hasHistory$ | async)"
bitButton
type="submit"
buttonType="primary"
(click)="clear()"
>
{{ "clearHistory" | i18n }}
</button>
</popup-footer>
</popup-page>

View File

@ -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<boolean>(false);
protected readonly userId$ = new BehaviorSubject<UserId>(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$));
};
}

View File

@ -64,7 +64,7 @@ export class SendGroupingsComponent extends BaseSendComponent implements OnInit,
dialogService,
toastService,
);
super.onSuccessfulLoad = async () => {
this.onSuccessfulLoad = async () => {
this.selectAll();
};
}

View File

@ -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 =

View File

@ -124,48 +124,48 @@
<value>Kodus, tööl ja teel - Bitwarden hoiustab imelihtsalt kõik su paroolid, pääsuvõtmed ja tundliku info.</value>
</data>
<data name="Description" xml:space="preserve">
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
<value>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!
</value>
</data>
<data name="AssetTitle" xml:space="preserve">

View File

@ -124,49 +124,46 @@
<value>Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais.</value>
</data>
<data name="Description" xml:space="preserve">
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
<value>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!
</value>
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!</value>
</data>
<data name="AssetTitle" xml:space="preserve">
<value>Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais.</value>

View File

@ -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",

View File

@ -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);
}
}
}

View File

@ -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:*", () => {

View File

@ -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);

View File

@ -4,4 +4,3 @@ index.node
**/.DS_Store
npm-debug.log*
*.node
dist

View File

@ -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"

View File

@ -1,3 +1,3 @@
[workspace]
resolver = "2"
members = ["napi", "core", "proxy"]
members = ["napi", "core"]

View File

@ -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}`));
});

View File

@ -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"

View File

@ -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<String>,
mut recv: tokio::sync::mpsc::Receiver<String>,
) {
// 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<String>,
recv: &mut tokio::sync::mpsc::Receiver<String>,
) -> Result<(), Box<dyn std::error::Error>> {
info!("Attempting to connect to {}", path.display());
let name = path.as_os_str().to_fs_name::<GenericFilePath>()?;
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(())
}

View File

@ -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/<user>/
// While running sandboxed, it's different: /Users/<user>/Library/Containers/com.bitwarden.desktop/Data
//
// We want to use App Groups in /Users/<user>/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, <user>).
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}"))
}
}

View File

@ -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<String>,
}
#[derive(Debug)]
pub enum MessageType {
Connected,
Disconnected,
Message,
}
pub struct Server {
cancel_token: CancellationToken,
server_to_clients_send: broadcast::Sender<String>,
}
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<Message>`] will receive all the [`Message`]'s that the clients send to this server.
pub fn start(
path: &Path,
client_to_server_send: mpsc::Sender<Message>,
) -> Result<Self, Box<dyn Error>> {
// 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::<GenericFilePath>()?;
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::<String>(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<usize> {
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<Message>,
server_to_clients_recv: broadcast::Receiver<String>,
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<Message>,
mut server_to_clients_recv: broadcast::Receiver<String>,
cancel_token: CancellationToken,
client_id: u32,
) -> Result<(), Box<dyn Error>> {
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(())
}

View File

@ -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;

View File

@ -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"

View File

@ -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'});
});

View File

@ -51,33 +51,3 @@ export namespace powermonitors {
export function onLock(callback: (err: Error | null, ) => any): Promise<void>
export function isLockMonitorAvailable(): Promise<boolean>
}
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<IpcServer>
/** 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
}
}

View File

@ -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

View File

@ -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": "",

View File

@ -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<String>,
}
impl From<Message> 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<MessageType> 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<IpcMessage, ErrorStrategy::CalleeHandled>,
) -> napi::Result<Self> {
let (send, mut recv) = tokio::sync::mpsc::channel::<Message>(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<u32> {
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())
}
}
}

Some files were not shown because too many files have changed in this diff Show More