mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-08 19:18:02 +01:00
Merge branch 'main' into auth/pm-8111/browser-refresh-login-component
This commit is contained in:
commit
b564ff4ca5
45
.github/workflows/build-desktop.yml
vendored
45
.github/workflows/build-desktop.yml
vendored
@ -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'
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": "Елементи в кошчето"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": "Αντικείμενα στον κάδο"
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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."
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": "ゴミ箱にあるアイテム"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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ē"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": "Элементы в корзине"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": "Ставке у смећу"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": "Записи в смітнику"
|
||||
|
@ -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"
|
||||
|
@ -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": "回收站中的项目"
|
||||
|
@ -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"
|
||||
|
@ -217,6 +217,7 @@ export type OverlayBackgroundExtensionMessageHandlers = {
|
||||
addEditCipherSubmitted: () => void;
|
||||
editedCipher: () => void;
|
||||
deletedCipher: () => void;
|
||||
fido2AbortRequest: ({ message, sender }: BackgroundOnMessageHandlerParams) => void;
|
||||
};
|
||||
|
||||
export type PortMessageParam = {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -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],
|
||||
|
@ -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>
|
@ -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$));
|
||||
};
|
||||
}
|
@ -64,7 +64,7 @@ export class SendGroupingsComponent extends BaseSendComponent implements OnInit,
|
||||
dialogService,
|
||||
toastService,
|
||||
);
|
||||
super.onSuccessfulLoad = async () => {
|
||||
this.onSuccessfulLoad = async () => {
|
||||
this.selectAll();
|
||||
};
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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:*", () => {
|
||||
|
@ -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);
|
||||
|
1
apps/desktop/desktop_native/.gitignore
vendored
1
apps/desktop/desktop_native/.gitignore
vendored
@ -4,4 +4,3 @@ index.node
|
||||
**/.DS_Store
|
||||
npm-debug.log*
|
||||
*.node
|
||||
dist
|
||||
|
254
apps/desktop/desktop_native/Cargo.lock
generated
254
apps/desktop/desktop_native/Cargo.lock
generated
@ -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"
|
||||
|
@ -1,3 +1,3 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["napi", "core", "proxy"]
|
||||
members = ["napi", "core"]
|
||||
|
@ -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}`));
|
||||
});
|
@ -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"
|
||||
|
@ -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(())
|
||||
}
|
@ -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}"))
|
||||
}
|
||||
}
|
@ -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(())
|
||||
}
|
@ -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;
|
||||
|
@ -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"
|
||||
|
24
apps/desktop/desktop_native/napi/build.js
Normal file
24
apps/desktop/desktop_native/napi/build.js
Normal 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'});
|
||||
});
|
30
apps/desktop/desktop_native/napi/index.d.ts
vendored
30
apps/desktop/desktop_native/napi/index.d.ts
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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": "",
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user