mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-27 04:03:00 +02:00
Merge branch 'main' into ps/pm-7486/detect-libsecret-service
This commit is contained in:
commit
a0bffc21e6
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -5,5 +5,6 @@
|
|||||||
"**/locales/*[^n]/messages.json": true,
|
"**/locales/*[^n]/messages.json": true,
|
||||||
"**/_locales/[^e]*/messages.json": true,
|
"**/_locales/[^e]*/messages.json": true,
|
||||||
"**/_locales/*[^n]/messages.json": true
|
"**/_locales/*[^n]/messages.json": true
|
||||||
}
|
},
|
||||||
|
"rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"devFlags": {
|
"devFlags": {
|
||||||
"storeSessionDecrypted": false,
|
|
||||||
"managedEnvironment": {
|
"managedEnvironment": {
|
||||||
"base": "https://localhost:8080"
|
"base": "https://localhost:8080"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/browser",
|
"name": "@bitwarden/browser",
|
||||||
"version": "2024.4.1",
|
"version": "2024.4.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack",
|
"build": "webpack",
|
||||||
"build:mv3": "cross-env MANIFEST_VERSION=3 webpack",
|
"build:mv3": "cross-env MANIFEST_VERSION=3 webpack",
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"message": "Enterprise single sign-on"
|
"message": "Enterprise single sign-on"
|
||||||
},
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "Cancel"
|
"message": "Canslo"
|
||||||
},
|
},
|
||||||
"close": {
|
"close": {
|
||||||
"message": "Cau"
|
"message": "Cau"
|
||||||
@ -318,7 +318,7 @@
|
|||||||
"message": "Golygu"
|
"message": "Golygu"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"message": "View"
|
"message": "Gweld"
|
||||||
},
|
},
|
||||||
"noItemsInList": {
|
"noItemsInList": {
|
||||||
"message": "Does dim eitemau i'w rhestru."
|
"message": "Does dim eitemau i'w rhestru."
|
||||||
@ -549,10 +549,10 @@
|
|||||||
"message": "Ydych chi'n siŵr eich bod am allgofnodi?"
|
"message": "Ydych chi'n siŵr eich bod am allgofnodi?"
|
||||||
},
|
},
|
||||||
"yes": {
|
"yes": {
|
||||||
"message": "Yes"
|
"message": "Ydw"
|
||||||
},
|
},
|
||||||
"no": {
|
"no": {
|
||||||
"message": "No"
|
"message": "Na"
|
||||||
},
|
},
|
||||||
"unexpectedError": {
|
"unexpectedError": {
|
||||||
"message": "An unexpected error has occurred."
|
"message": "An unexpected error has occurred."
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
"message": "Bitwarden"
|
"message": "Bitwarden"
|
||||||
},
|
},
|
||||||
"extName": {
|
"extName": {
|
||||||
"message": "Bitwarden Password Manager",
|
"message": "Bitwarden Passwortmanager",
|
||||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||||
},
|
},
|
||||||
"extDesc": {
|
"extDesc": {
|
||||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.",
|
"message": "Zu Hause, am Arbeitsplatz oder unterwegs schützt Bitwarden einfach alle deine Passwörter, Passkeys und vertraulichen Informationen.",
|
||||||
"description": "Extension description"
|
"description": "Extension description"
|
||||||
},
|
},
|
||||||
"loginOrCreateNewAccount": {
|
"loginOrCreateNewAccount": {
|
||||||
@ -173,10 +173,10 @@
|
|||||||
"message": "Master-Passwort ändern"
|
"message": "Master-Passwort ändern"
|
||||||
},
|
},
|
||||||
"continueToWebApp": {
|
"continueToWebApp": {
|
||||||
"message": "Continue to web app?"
|
"message": "Weiter zur Web-App?"
|
||||||
},
|
},
|
||||||
"changeMasterPasswordOnWebConfirmation": {
|
"changeMasterPasswordOnWebConfirmation": {
|
||||||
"message": "You can change your master password on the Bitwarden web app."
|
"message": "Du kannst dein Master-Passwort in der Bitwarden Web-App ändern."
|
||||||
},
|
},
|
||||||
"fingerprintPhrase": {
|
"fingerprintPhrase": {
|
||||||
"message": "Fingerabdruck-Phrase",
|
"message": "Fingerabdruck-Phrase",
|
||||||
@ -3001,7 +3001,7 @@
|
|||||||
"description": "Notification message for when saving credentials has failed."
|
"description": "Notification message for when saving credentials has failed."
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"message": "Success"
|
"message": "Erfolg"
|
||||||
},
|
},
|
||||||
"removePasskey": {
|
"removePasskey": {
|
||||||
"message": "Passkey entfernen"
|
"message": "Passkey entfernen"
|
||||||
@ -3010,21 +3010,21 @@
|
|||||||
"message": "Passkey entfernt"
|
"message": "Passkey entfernt"
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerNotice": {
|
"unassignedItemsBannerNotice": {
|
||||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
"message": "Hinweis: Nicht zugeordnete Organisationseinträge sind nicht mehr in der Ansicht aller Tresore sichtbar und nur über die Administrator-Konsole zugänglich."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerSelfHostNotice": {
|
"unassignedItemsBannerSelfHostNotice": {
|
||||||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
"message": "Hinweis: Ab dem 16. Mai 2024 sind nicht zugewiesene Organisationselemente nicht mehr in der Ansicht aller Tresore sichtbar und nur über die Administrator-Konsole zugänglich."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerCTAPartOne": {
|
"unassignedItemsBannerCTAPartOne": {
|
||||||
"message": "Assign these items to a collection from the",
|
"message": "Weise diese Einträge einer Sammlung aus der",
|
||||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerCTAPartTwo": {
|
"unassignedItemsBannerCTAPartTwo": {
|
||||||
"message": "to make them visible.",
|
"message": "zu, um sie sichtbar zu machen.",
|
||||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||||
},
|
},
|
||||||
"adminConsole": {
|
"adminConsole": {
|
||||||
"message": "Admin Console"
|
"message": "Administrator-Konsole"
|
||||||
},
|
},
|
||||||
"errorAssigningTargetCollection": {
|
"errorAssigningTargetCollection": {
|
||||||
"message": "Fehler beim Zuweisen der Ziel-Sammlung."
|
"message": "Fehler beim Zuweisen der Ziel-Sammlung."
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
"message": "Bitwarden"
|
"message": "Bitwarden"
|
||||||
},
|
},
|
||||||
"extName": {
|
"extName": {
|
||||||
"message": "Bitwarden Password Manager",
|
"message": "Bitwarden 비밀번호 관리자",
|
||||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||||
},
|
},
|
||||||
"extDesc": {
|
"extDesc": {
|
||||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.",
|
"message": "집에서도, 직장에서도, 이동 중에도 Bitwarden은 비밀번호, 패스키, 민감 정보를 쉽게 보호합니다.",
|
||||||
"description": "Extension description"
|
"description": "Extension description"
|
||||||
},
|
},
|
||||||
"loginOrCreateNewAccount": {
|
"loginOrCreateNewAccount": {
|
||||||
@ -173,7 +173,7 @@
|
|||||||
"message": "마스터 비밀번호 변경"
|
"message": "마스터 비밀번호 변경"
|
||||||
},
|
},
|
||||||
"continueToWebApp": {
|
"continueToWebApp": {
|
||||||
"message": "Continue to web app?"
|
"message": "웹 앱에서 계속하시겠용?"
|
||||||
},
|
},
|
||||||
"changeMasterPasswordOnWebConfirmation": {
|
"changeMasterPasswordOnWebConfirmation": {
|
||||||
"message": "You can change your master password on the Bitwarden web app."
|
"message": "You can change your master password on the Bitwarden web app."
|
||||||
@ -688,10 +688,10 @@
|
|||||||
"message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts."
|
"message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts."
|
||||||
},
|
},
|
||||||
"enableUsePasskeys": {
|
"enableUsePasskeys": {
|
||||||
"message": "Ask to save and use passkeys"
|
"message": "패스키를 저장 및 사용할지 묻기"
|
||||||
},
|
},
|
||||||
"usePasskeysDesc": {
|
"usePasskeysDesc": {
|
||||||
"message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts."
|
"message": "보관함에 새 패스키를 저장하거나 로그인할지 물어봅니다. 모든 로그인된 계정에 적용됩니다."
|
||||||
},
|
},
|
||||||
"notificationChangeDesc": {
|
"notificationChangeDesc": {
|
||||||
"message": "Bitwarden에 저장되어 있는 비밀번호를 이 비밀번호로 변경하시겠습니까?"
|
"message": "Bitwarden에 저장되어 있는 비밀번호를 이 비밀번호로 변경하시겠습니까?"
|
||||||
@ -2786,55 +2786,55 @@
|
|||||||
"message": "Confirm file password"
|
"message": "Confirm file password"
|
||||||
},
|
},
|
||||||
"typePasskey": {
|
"typePasskey": {
|
||||||
"message": "Passkey"
|
"message": "패스키"
|
||||||
},
|
},
|
||||||
"passkeyNotCopied": {
|
"passkeyNotCopied": {
|
||||||
"message": "Passkey will not be copied"
|
"message": "패스키가 복사되지 않습니다"
|
||||||
},
|
},
|
||||||
"passkeyNotCopiedAlert": {
|
"passkeyNotCopiedAlert": {
|
||||||
"message": "The passkey will not be copied to the cloned item. Do you want to continue cloning this item?"
|
"message": "패스키는 복제된 아이템에 복사되지 않습니다. 계속 이 항목을 복제하시겠어요?"
|
||||||
},
|
},
|
||||||
"passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": {
|
"passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": {
|
||||||
"message": "Verification required by the initiating site. This feature is not yet implemented for accounts without master password."
|
"message": "사이트에서 인증을 요구합니다. 이 기능은 비밀번호가 없는 계정에서는 아직 지원하지 않습니다."
|
||||||
},
|
},
|
||||||
"logInWithPasskey": {
|
"logInWithPasskey": {
|
||||||
"message": "Log in with passkey?"
|
"message": "패스키로 로그인하시겠어요?"
|
||||||
},
|
},
|
||||||
"passkeyAlreadyExists": {
|
"passkeyAlreadyExists": {
|
||||||
"message": "A passkey already exists for this application."
|
"message": "이미 이 애플리케이션에 해당하는 패스키가 있습니다."
|
||||||
},
|
},
|
||||||
"noPasskeysFoundForThisApplication": {
|
"noPasskeysFoundForThisApplication": {
|
||||||
"message": "No passkeys found for this application."
|
"message": "이 애플리케이션에 대한 패스키를 찾을 수 없습니다."
|
||||||
},
|
},
|
||||||
"noMatchingPasskeyLogin": {
|
"noMatchingPasskeyLogin": {
|
||||||
"message": "You do not have a matching login for this site."
|
"message": "사이트와 일치하는 로그인이 없습니다."
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"message": "Confirm"
|
"message": "Confirm"
|
||||||
},
|
},
|
||||||
"savePasskey": {
|
"savePasskey": {
|
||||||
"message": "Save passkey"
|
"message": "패스키 저장"
|
||||||
},
|
},
|
||||||
"savePasskeyNewLogin": {
|
"savePasskeyNewLogin": {
|
||||||
"message": "Save passkey as new login"
|
"message": "새 로그인으로 패스키 저장"
|
||||||
},
|
},
|
||||||
"choosePasskey": {
|
"choosePasskey": {
|
||||||
"message": "Choose a login to save this passkey to"
|
"message": "패스키를 저장할 로그인 선택하기"
|
||||||
},
|
},
|
||||||
"passkeyItem": {
|
"passkeyItem": {
|
||||||
"message": "Passkey Item"
|
"message": "패스키 항목"
|
||||||
},
|
},
|
||||||
"overwritePasskey": {
|
"overwritePasskey": {
|
||||||
"message": "Overwrite passkey?"
|
"message": "비밀번호를 덮어쓰시겠어요?"
|
||||||
},
|
},
|
||||||
"overwritePasskeyAlert": {
|
"overwritePasskeyAlert": {
|
||||||
"message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?"
|
"message": "이 항목은 이미 패스키가 있습니다. 정말로 현재 패스키를 덮어쓰시겠어요?"
|
||||||
},
|
},
|
||||||
"featureNotSupported": {
|
"featureNotSupported": {
|
||||||
"message": "Feature not yet supported"
|
"message": "Feature not yet supported"
|
||||||
},
|
},
|
||||||
"yourPasskeyIsLocked": {
|
"yourPasskeyIsLocked": {
|
||||||
"message": "Authentication required to use passkey. Verify your identity to continue."
|
"message": "패스키를 사용하려면 인증이 필요합니다. 인증을 진행해주세요."
|
||||||
},
|
},
|
||||||
"multifactorAuthenticationCancelled": {
|
"multifactorAuthenticationCancelled": {
|
||||||
"message": "Multifactor authentication cancelled"
|
"message": "Multifactor authentication cancelled"
|
||||||
@ -3004,10 +3004,10 @@
|
|||||||
"message": "Success"
|
"message": "Success"
|
||||||
},
|
},
|
||||||
"removePasskey": {
|
"removePasskey": {
|
||||||
"message": "Remove passkey"
|
"message": "패스키 제거"
|
||||||
},
|
},
|
||||||
"passkeyRemoved": {
|
"passkeyRemoved": {
|
||||||
"message": "Passkey removed"
|
"message": "패스키 제거됨"
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerNotice": {
|
"unassignedItemsBannerNotice": {
|
||||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
||||||
|
@ -531,7 +531,7 @@
|
|||||||
"message": "Kan de QR-code van de huidige webpagina niet scannen"
|
"message": "Kan de QR-code van de huidige webpagina niet scannen"
|
||||||
},
|
},
|
||||||
"totpCaptureSuccess": {
|
"totpCaptureSuccess": {
|
||||||
"message": "Authenticatie-sleutel toegevoegd"
|
"message": "Authenticatiesleutel toegevoegd"
|
||||||
},
|
},
|
||||||
"totpCapture": {
|
"totpCapture": {
|
||||||
"message": "Scan de authenticatie-QR-code van de huidige webpagina"
|
"message": "Scan de authenticatie-QR-code van de huidige webpagina"
|
||||||
@ -1673,10 +1673,10 @@
|
|||||||
"message": "Browserintegratie is niet ingeschakeld in de Bitwarden-desktopapplicatie. Schakel deze optie in de instellingen binnen de desktop-applicatie in."
|
"message": "Browserintegratie is niet ingeschakeld in de Bitwarden-desktopapplicatie. Schakel deze optie in de instellingen binnen de desktop-applicatie in."
|
||||||
},
|
},
|
||||||
"startDesktopTitle": {
|
"startDesktopTitle": {
|
||||||
"message": "Bitwarden-desktopapplicatie opstarten"
|
"message": "Bitwarden desktopapplicatie opstarten"
|
||||||
},
|
},
|
||||||
"startDesktopDesc": {
|
"startDesktopDesc": {
|
||||||
"message": "Je moet de Bitwarden-desktopapplicatie starten om deze functie te gebruiken."
|
"message": "Je moet de Bitwarden desktopapplicatie starten om deze functie te gebruiken."
|
||||||
},
|
},
|
||||||
"errorEnableBiometricTitle": {
|
"errorEnableBiometricTitle": {
|
||||||
"message": "Kon biometrie niet inschakelen"
|
"message": "Kon biometrie niet inschakelen"
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
"message": "Bitwarden"
|
"message": "Bitwarden"
|
||||||
},
|
},
|
||||||
"extName": {
|
"extName": {
|
||||||
"message": "Bitwarden Password Manager",
|
"message": "Menedżer Haseł Bitwarden",
|
||||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||||
},
|
},
|
||||||
"extDesc": {
|
"extDesc": {
|
||||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.",
|
"message": "W domu, w pracy, lub w ruchu, Bitwarden z łatwością zabezpiecza wszystkie Twoje hasła, passkeys i poufne informacje.",
|
||||||
"description": "Extension description"
|
"description": "Extension description"
|
||||||
},
|
},
|
||||||
"loginOrCreateNewAccount": {
|
"loginOrCreateNewAccount": {
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
"message": "Bitwarden"
|
"message": "Bitwarden"
|
||||||
},
|
},
|
||||||
"extName": {
|
"extName": {
|
||||||
"message": "Bitwarden Password Manager",
|
"message": "Bitwarden Gerenciador de Senhas",
|
||||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||||
},
|
},
|
||||||
"extDesc": {
|
"extDesc": {
|
||||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.",
|
"message": "Em casa, no trabalho, ou em qualquer lugar, o Bitwarden protege facilmente todas as suas senhas, senhas e informações confidenciais.",
|
||||||
"description": "Extension description"
|
"description": "Extension description"
|
||||||
},
|
},
|
||||||
"loginOrCreateNewAccount": {
|
"loginOrCreateNewAccount": {
|
||||||
@ -173,10 +173,10 @@
|
|||||||
"message": "Alterar Senha Mestra"
|
"message": "Alterar Senha Mestra"
|
||||||
},
|
},
|
||||||
"continueToWebApp": {
|
"continueToWebApp": {
|
||||||
"message": "Continue to web app?"
|
"message": "Continuar no aplicativo web?"
|
||||||
},
|
},
|
||||||
"changeMasterPasswordOnWebConfirmation": {
|
"changeMasterPasswordOnWebConfirmation": {
|
||||||
"message": "You can change your master password on the Bitwarden web app."
|
"message": "Você pode alterar a sua senha mestra no aplicativo web Bitwarden."
|
||||||
},
|
},
|
||||||
"fingerprintPhrase": {
|
"fingerprintPhrase": {
|
||||||
"message": "Frase Biométrica",
|
"message": "Frase Biométrica",
|
||||||
@ -500,10 +500,10 @@
|
|||||||
"message": "A sua nova conta foi criada! Agora você pode iniciar a sessão."
|
"message": "A sua nova conta foi criada! Agora você pode iniciar a sessão."
|
||||||
},
|
},
|
||||||
"youSuccessfullyLoggedIn": {
|
"youSuccessfullyLoggedIn": {
|
||||||
"message": "You successfully logged in"
|
"message": "Você logou na sua conta com sucesso"
|
||||||
},
|
},
|
||||||
"youMayCloseThisWindow": {
|
"youMayCloseThisWindow": {
|
||||||
"message": "You may close this window"
|
"message": "Você pode fechar esta janela"
|
||||||
},
|
},
|
||||||
"masterPassSent": {
|
"masterPassSent": {
|
||||||
"message": "Enviamos um e-mail com a dica da sua senha mestra."
|
"message": "Enviamos um e-mail com a dica da sua senha mestra."
|
||||||
@ -1500,7 +1500,7 @@
|
|||||||
"message": "Código PIN inválido."
|
"message": "Código PIN inválido."
|
||||||
},
|
},
|
||||||
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
||||||
"message": "Too many invalid PIN entry attempts. Logging out."
|
"message": "Muitas tentativas de entrada de PIN inválidas. Desconectando."
|
||||||
},
|
},
|
||||||
"unlockWithBiometrics": {
|
"unlockWithBiometrics": {
|
||||||
"message": "Desbloquear com a biometria"
|
"message": "Desbloquear com a biometria"
|
||||||
@ -2005,7 +2005,7 @@
|
|||||||
"message": "Selecionar pasta..."
|
"message": "Selecionar pasta..."
|
||||||
},
|
},
|
||||||
"noFoldersFound": {
|
"noFoldersFound": {
|
||||||
"message": "No folders found",
|
"message": "Nenhuma pasta encontrada",
|
||||||
"description": "Used as a message within the notification bar when no folders are found"
|
"description": "Used as a message within the notification bar when no folders are found"
|
||||||
},
|
},
|
||||||
"orgPermissionsUpdatedMustSetPassword": {
|
"orgPermissionsUpdatedMustSetPassword": {
|
||||||
@ -2017,7 +2017,7 @@
|
|||||||
"description": "Used as a card title description on the set password page to explain why the user is there"
|
"description": "Used as a card title description on the set password page to explain why the user is there"
|
||||||
},
|
},
|
||||||
"verificationRequired": {
|
"verificationRequired": {
|
||||||
"message": "Verification required",
|
"message": "Verificação necessária",
|
||||||
"description": "Default title for the user verification dialog."
|
"description": "Default title for the user verification dialog."
|
||||||
},
|
},
|
||||||
"hours": {
|
"hours": {
|
||||||
@ -2652,40 +2652,40 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tryAgain": {
|
"tryAgain": {
|
||||||
"message": "Try again"
|
"message": "Tentar novamente"
|
||||||
},
|
},
|
||||||
"verificationRequiredForActionSetPinToContinue": {
|
"verificationRequiredForActionSetPinToContinue": {
|
||||||
"message": "Verification required for this action. Set a PIN to continue."
|
"message": "Verificação necessária para esta ação. Defina um PIN para continuar."
|
||||||
},
|
},
|
||||||
"setPin": {
|
"setPin": {
|
||||||
"message": "Set PIN"
|
"message": "Definir PIN"
|
||||||
},
|
},
|
||||||
"verifyWithBiometrics": {
|
"verifyWithBiometrics": {
|
||||||
"message": "Verify with biometrics"
|
"message": "Verificiar com biometria"
|
||||||
},
|
},
|
||||||
"awaitingConfirmation": {
|
"awaitingConfirmation": {
|
||||||
"message": "Awaiting confirmation"
|
"message": "Aguardando confirmação"
|
||||||
},
|
},
|
||||||
"couldNotCompleteBiometrics": {
|
"couldNotCompleteBiometrics": {
|
||||||
"message": "Could not complete biometrics."
|
"message": "Não foi possível completar a biometria."
|
||||||
},
|
},
|
||||||
"needADifferentMethod": {
|
"needADifferentMethod": {
|
||||||
"message": "Need a different method?"
|
"message": "Precisa de um método diferente?"
|
||||||
},
|
},
|
||||||
"useMasterPassword": {
|
"useMasterPassword": {
|
||||||
"message": "Use master password"
|
"message": "Usar a senha mestra"
|
||||||
},
|
},
|
||||||
"usePin": {
|
"usePin": {
|
||||||
"message": "Use PIN"
|
"message": "Usar PIN"
|
||||||
},
|
},
|
||||||
"useBiometrics": {
|
"useBiometrics": {
|
||||||
"message": "Use biometrics"
|
"message": "Usar biometria"
|
||||||
},
|
},
|
||||||
"enterVerificationCodeSentToEmail": {
|
"enterVerificationCodeSentToEmail": {
|
||||||
"message": "Enter the verification code that was sent to your email."
|
"message": "Digite o código de verificação que foi enviado para o seu e-mail."
|
||||||
},
|
},
|
||||||
"resendCode": {
|
"resendCode": {
|
||||||
"message": "Resend code"
|
"message": "Reenviar código"
|
||||||
},
|
},
|
||||||
"total": {
|
"total": {
|
||||||
"message": "Total"
|
"message": "Total"
|
||||||
@ -2700,19 +2700,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"launchDuoAndFollowStepsToFinishLoggingIn": {
|
"launchDuoAndFollowStepsToFinishLoggingIn": {
|
||||||
"message": "Launch Duo and follow the steps to finish logging in."
|
"message": "Inicie o Duo e siga os passos para finalizar o login."
|
||||||
},
|
},
|
||||||
"duoRequiredForAccount": {
|
"duoRequiredForAccount": {
|
||||||
"message": "Duo two-step login is required for your account."
|
"message": "A autenticação em duas etapas do Duo é necessária para sua conta."
|
||||||
},
|
},
|
||||||
"popoutTheExtensionToCompleteLogin": {
|
"popoutTheExtensionToCompleteLogin": {
|
||||||
"message": "Popout the extension to complete login."
|
"message": "Abra a extensão para concluir o login."
|
||||||
},
|
},
|
||||||
"popoutExtension": {
|
"popoutExtension": {
|
||||||
"message": "Popout extension"
|
"message": "Extensão pop-out"
|
||||||
},
|
},
|
||||||
"launchDuo": {
|
"launchDuo": {
|
||||||
"message": "Launch Duo"
|
"message": "Abrir o Duo"
|
||||||
},
|
},
|
||||||
"importFormatError": {
|
"importFormatError": {
|
||||||
"message": "Os dados não estão formatados corretamente. Por favor, verifique o seu arquivo de importação e tente novamente."
|
"message": "Os dados não estão formatados corretamente. Por favor, verifique o seu arquivo de importação e tente novamente."
|
||||||
@ -2846,13 +2846,13 @@
|
|||||||
"message": "Nome de usuário ou senha incorretos"
|
"message": "Nome de usuário ou senha incorretos"
|
||||||
},
|
},
|
||||||
"incorrectPassword": {
|
"incorrectPassword": {
|
||||||
"message": "Incorrect password"
|
"message": "Senha incorreta"
|
||||||
},
|
},
|
||||||
"incorrectCode": {
|
"incorrectCode": {
|
||||||
"message": "Incorrect code"
|
"message": "Código incorreto"
|
||||||
},
|
},
|
||||||
"incorrectPin": {
|
"incorrectPin": {
|
||||||
"message": "Incorrect PIN"
|
"message": "PIN incorreto"
|
||||||
},
|
},
|
||||||
"multifactorAuthenticationFailed": {
|
"multifactorAuthenticationFailed": {
|
||||||
"message": "Falha na autenticação de múltiplos fatores"
|
"message": "Falha na autenticação de múltiplos fatores"
|
||||||
@ -2965,71 +2965,71 @@
|
|||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
"overrideDefaultBrowserAutofillTitle": {
|
"overrideDefaultBrowserAutofillTitle": {
|
||||||
"message": "Make Bitwarden your default password manager?",
|
"message": "Tornar o Bitwarden seu gerenciador de senhas padrão?",
|
||||||
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
|
"description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior"
|
||||||
},
|
},
|
||||||
"overrideDefaultBrowserAutofillDescription": {
|
"overrideDefaultBrowserAutofillDescription": {
|
||||||
"message": "Ignoring this option may cause conflicts between the Bitwarden auto-fill menu and your browser's.",
|
"message": "Ignorar esta opção pode causar conflitos entre o menu de autopreenchimento do Bitwarden e o do seu navegador.",
|
||||||
"description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior"
|
"description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior"
|
||||||
},
|
},
|
||||||
"overrideDefaultBrowserAutoFillSettings": {
|
"overrideDefaultBrowserAutoFillSettings": {
|
||||||
"message": "Make Bitwarden your default password manager",
|
"message": "Faça do Bitwarden seu gerenciador de senhas padrão",
|
||||||
"description": "Label for the setting that allows overriding the default browser autofill settings"
|
"description": "Label for the setting that allows overriding the default browser autofill settings"
|
||||||
},
|
},
|
||||||
"privacyPermissionAdditionNotGrantedTitle": {
|
"privacyPermissionAdditionNotGrantedTitle": {
|
||||||
"message": "Unable to set Bitwarden as the default password manager",
|
"message": "Não é possível definir o Bitwarden como o gerenciador de senhas padrão",
|
||||||
"description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
"description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
||||||
},
|
},
|
||||||
"privacyPermissionAdditionNotGrantedDescription": {
|
"privacyPermissionAdditionNotGrantedDescription": {
|
||||||
"message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.",
|
"message": "Você deve conceder permissões de privacidade do navegador ao Bitwarden para defini-lo como o Gerenciador de Senhas padrão.",
|
||||||
"description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
"description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings"
|
||||||
},
|
},
|
||||||
"makeDefault": {
|
"makeDefault": {
|
||||||
"message": "Make default",
|
"message": "Tornar padrão",
|
||||||
"description": "Button text for the setting that allows overriding the default browser autofill settings"
|
"description": "Button text for the setting that allows overriding the default browser autofill settings"
|
||||||
},
|
},
|
||||||
"saveCipherAttemptSuccess": {
|
"saveCipherAttemptSuccess": {
|
||||||
"message": "Credentials saved successfully!",
|
"message": "Credenciais salvas com sucesso!",
|
||||||
"description": "Notification message for when saving credentials has succeeded."
|
"description": "Notification message for when saving credentials has succeeded."
|
||||||
},
|
},
|
||||||
"updateCipherAttemptSuccess": {
|
"updateCipherAttemptSuccess": {
|
||||||
"message": "Credentials updated successfully!",
|
"message": "Credenciais atualizadas com sucesso!",
|
||||||
"description": "Notification message for when updating credentials has succeeded."
|
"description": "Notification message for when updating credentials has succeeded."
|
||||||
},
|
},
|
||||||
"saveCipherAttemptFailed": {
|
"saveCipherAttemptFailed": {
|
||||||
"message": "Error saving credentials. Check console for details.",
|
"message": "Erro ao salvar credenciais. Verifique o console para detalhes.",
|
||||||
"description": "Notification message for when saving credentials has failed."
|
"description": "Notification message for when saving credentials has failed."
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"message": "Success"
|
"message": "Sucesso"
|
||||||
},
|
},
|
||||||
"removePasskey": {
|
"removePasskey": {
|
||||||
"message": "Remove passkey"
|
"message": "Remover senha"
|
||||||
},
|
},
|
||||||
"passkeyRemoved": {
|
"passkeyRemoved": {
|
||||||
"message": "Passkey removed"
|
"message": "Chave de acesso removida"
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerNotice": {
|
"unassignedItemsBannerNotice": {
|
||||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
"message": "Aviso: Itens da organização não atribuídos não estão mais visíveis na visualização Todos os Cofres e só são acessíveis por meio do painel de administração."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerSelfHostNotice": {
|
"unassignedItemsBannerSelfHostNotice": {
|
||||||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
"message": "Aviso: Em 16 de maio, 2024, itens da organização não serão mais visíveis na visualização Todos os Cofres e só serão acessíveis por meio do painel de administração."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerCTAPartOne": {
|
"unassignedItemsBannerCTAPartOne": {
|
||||||
"message": "Assign these items to a collection from the",
|
"message": "Atribua estes itens a uma coleção da",
|
||||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerCTAPartTwo": {
|
"unassignedItemsBannerCTAPartTwo": {
|
||||||
"message": "to make them visible.",
|
"message": "para torná-los visíveis.",
|
||||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||||
},
|
},
|
||||||
"adminConsole": {
|
"adminConsole": {
|
||||||
"message": "Admin Console"
|
"message": "Painel de administração"
|
||||||
},
|
},
|
||||||
"errorAssigningTargetCollection": {
|
"errorAssigningTargetCollection": {
|
||||||
"message": "Error assigning target collection."
|
"message": "Erro ao atribuir coleção de destino."
|
||||||
},
|
},
|
||||||
"errorAssigningTargetFolder": {
|
"errorAssigningTargetFolder": {
|
||||||
"message": "Error assigning target folder."
|
"message": "Erro ao atribuir pasta de destino."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,10 +173,10 @@
|
|||||||
"message": "Промени главну лозинку"
|
"message": "Промени главну лозинку"
|
||||||
},
|
},
|
||||||
"continueToWebApp": {
|
"continueToWebApp": {
|
||||||
"message": "Continue to web app?"
|
"message": "Ићи на веб апликацију?"
|
||||||
},
|
},
|
||||||
"changeMasterPasswordOnWebConfirmation": {
|
"changeMasterPasswordOnWebConfirmation": {
|
||||||
"message": "You can change your master password on the Bitwarden web app."
|
"message": "Можете променити главну лозинку на Bitwarden веб апликацији."
|
||||||
},
|
},
|
||||||
"fingerprintPhrase": {
|
"fingerprintPhrase": {
|
||||||
"message": "Сигурносна Фраза Сефа",
|
"message": "Сигурносна Фраза Сефа",
|
||||||
@ -3001,7 +3001,7 @@
|
|||||||
"description": "Notification message for when saving credentials has failed."
|
"description": "Notification message for when saving credentials has failed."
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"message": "Success"
|
"message": "Успех"
|
||||||
},
|
},
|
||||||
"removePasskey": {
|
"removePasskey": {
|
||||||
"message": "Уклонити приступачни кључ"
|
"message": "Уклонити приступачни кључ"
|
||||||
@ -3010,10 +3010,10 @@
|
|||||||
"message": "Приступачни кључ је уклоњен"
|
"message": "Приступачни кључ је уклоњен"
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerNotice": {
|
"unassignedItemsBannerNotice": {
|
||||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
"message": "Напомена: Недодељене ставке организације више нису видљиве у приказу Сви сефови и доступне су само преко Админ конзоле."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerSelfHostNotice": {
|
"unassignedItemsBannerSelfHostNotice": {
|
||||||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
"message": "Напомена: од 16 Маја 2024м недодељене ставке организације више нису видљиве у приказу Сви сефови и доступне су само преко Админ конзоле."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerCTAPartOne": {
|
"unassignedItemsBannerCTAPartOne": {
|
||||||
"message": "Assign these items to a collection from the",
|
"message": "Assign these items to a collection from the",
|
||||||
@ -3024,12 +3024,12 @@
|
|||||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||||
},
|
},
|
||||||
"adminConsole": {
|
"adminConsole": {
|
||||||
"message": "Admin Console"
|
"message": "Администраторска конзола"
|
||||||
},
|
},
|
||||||
"errorAssigningTargetCollection": {
|
"errorAssigningTargetCollection": {
|
||||||
"message": "Error assigning target collection."
|
"message": "Грешка при додељивању циљне колекције."
|
||||||
},
|
},
|
||||||
"errorAssigningTargetFolder": {
|
"errorAssigningTargetFolder": {
|
||||||
"message": "Error assigning target folder."
|
"message": "Грешка при додељивању циљне фасцикле."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
"message": "Bitwarden"
|
"message": "Bitwarden"
|
||||||
},
|
},
|
||||||
"extName": {
|
"extName": {
|
||||||
"message": "Bitwarden Password Manager",
|
"message": "Bitwarden 密码管理器",
|
||||||
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
"description": "Extension name, MUST be less than 40 characters (Safari restriction)"
|
||||||
},
|
},
|
||||||
"extDesc": {
|
"extDesc": {
|
||||||
"message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.",
|
"message": "无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。",
|
||||||
"description": "Extension description"
|
"description": "Extension description"
|
||||||
},
|
},
|
||||||
"loginOrCreateNewAccount": {
|
"loginOrCreateNewAccount": {
|
||||||
@ -176,7 +176,7 @@
|
|||||||
"message": "前往网页 App 吗?"
|
"message": "前往网页 App 吗?"
|
||||||
},
|
},
|
||||||
"changeMasterPasswordOnWebConfirmation": {
|
"changeMasterPasswordOnWebConfirmation": {
|
||||||
"message": "You can change your master password on the Bitwarden web app."
|
"message": "您可以在 Bitwarden 网页应用上更改您的主密码。"
|
||||||
},
|
},
|
||||||
"fingerprintPhrase": {
|
"fingerprintPhrase": {
|
||||||
"message": "指纹短语",
|
"message": "指纹短语",
|
||||||
@ -3010,17 +3010,17 @@
|
|||||||
"message": "通行密钥已移除"
|
"message": "通行密钥已移除"
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerNotice": {
|
"unassignedItemsBannerNotice": {
|
||||||
"message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console."
|
"message": "注意:未分配的组织项目在「所有密码库」视图中不再可见,只能通过管理控制台访问。"
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerSelfHostNotice": {
|
"unassignedItemsBannerSelfHostNotice": {
|
||||||
"message": "Notice: On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and will only be accessible via the Admin Console."
|
"message": "注意:从 2024 年 5 月 16 日起,未分配的组织项目在「所有密码库」视图中将不再可见,只能通过管理控制台访问。"
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerCTAPartOne": {
|
"unassignedItemsBannerCTAPartOne": {
|
||||||
"message": "Assign these items to a collection from the",
|
"message": "Assign these items to a collection from the",
|
||||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||||
},
|
},
|
||||||
"unassignedItemsBannerCTAPartTwo": {
|
"unassignedItemsBannerCTAPartTwo": {
|
||||||
"message": "to make them visible.",
|
"message": "以使其可见。",
|
||||||
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
"description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible."
|
||||||
},
|
},
|
||||||
"adminConsole": {
|
"adminConsole": {
|
||||||
|
@ -98,7 +98,9 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory
|
|||||||
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
||||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||||
|
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
|
||||||
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
|
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
|
||||||
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
|
||||||
@ -111,7 +113,6 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge
|
|||||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
|
||||||
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
||||||
import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service";
|
import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service";
|
||||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||||
@ -226,6 +227,7 @@ import { BackgroundPlatformUtilsService } from "../platform/services/platform-ut
|
|||||||
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
||||||
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
||||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
||||||
|
import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider";
|
||||||
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
|
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
|
||||||
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
||||||
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
|
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
|
||||||
@ -246,6 +248,8 @@ export default class MainBackground {
|
|||||||
secureStorageService: AbstractStorageService;
|
secureStorageService: AbstractStorageService;
|
||||||
memoryStorageService: AbstractMemoryStorageService;
|
memoryStorageService: AbstractMemoryStorageService;
|
||||||
memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService;
|
memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService;
|
||||||
|
largeObjectMemoryStorageForStateProviders: AbstractMemoryStorageService &
|
||||||
|
ObservableStorageService;
|
||||||
i18nService: I18nServiceAbstraction;
|
i18nService: I18nServiceAbstraction;
|
||||||
platformUtilsService: PlatformUtilsServiceAbstraction;
|
platformUtilsService: PlatformUtilsServiceAbstraction;
|
||||||
logService: LogServiceAbstraction;
|
logService: LogServiceAbstraction;
|
||||||
@ -402,34 +406,57 @@ export default class MainBackground {
|
|||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
|
|
||||||
const mv3MemoryStorageCreator = (partitionName: string) => {
|
// Creates a session key for mv3 storage of large memory items
|
||||||
if (this.popupOnlyContext) {
|
const sessionKey = new Lazy(async () => {
|
||||||
return new ForegroundMemoryStorageService(partitionName);
|
// Key already in session storage
|
||||||
|
const sessionStorage = new BrowserMemoryStorageService();
|
||||||
|
const existingKey = await sessionStorage.get<SymmetricCryptoKey>("session-key");
|
||||||
|
if (existingKey) {
|
||||||
|
if (sessionStorage.valuesRequireDeserialization) {
|
||||||
|
return SymmetricCryptoKey.fromJSON(existingKey);
|
||||||
|
}
|
||||||
|
return existingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New key
|
||||||
|
const { derivedKey } = await this.keyGenerationService.createKeyWithPurpose(
|
||||||
|
128,
|
||||||
|
"ephemeral",
|
||||||
|
"bitwarden-ephemeral",
|
||||||
|
);
|
||||||
|
await sessionStorage.save("session-key", derivedKey);
|
||||||
|
return derivedKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mv3MemoryStorageCreator = () => {
|
||||||
|
if (this.popupOnlyContext) {
|
||||||
|
return new ForegroundMemoryStorageService();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider using multithreaded encrypt service in popup only context
|
|
||||||
return new LocalBackedSessionStorageService(
|
return new LocalBackedSessionStorageService(
|
||||||
this.logService,
|
sessionKey,
|
||||||
|
this.storageService,
|
||||||
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
|
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
|
||||||
this.keyGenerationService,
|
|
||||||
new BrowserLocalStorageService(),
|
|
||||||
new BrowserMemoryStorageService(),
|
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
partitionName,
|
this.logService,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used
|
this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used
|
||||||
this.memoryStorageService = BrowserApi.isManifestVersion(3)
|
|
||||||
? mv3MemoryStorageCreator("stateService")
|
|
||||||
: new MemoryStorageService();
|
|
||||||
this.memoryStorageForStateProviders = BrowserApi.isManifestVersion(3)
|
this.memoryStorageForStateProviders = BrowserApi.isManifestVersion(3)
|
||||||
? mv3MemoryStorageCreator("stateProviders")
|
? new BrowserMemoryStorageService() // mv3 stores to storage.session
|
||||||
: new BackgroundMemoryStorageService();
|
: new BackgroundMemoryStorageService(); // mv2 stores to memory
|
||||||
|
this.memoryStorageService = BrowserApi.isManifestVersion(3)
|
||||||
|
? this.memoryStorageForStateProviders // manifest v3 can reuse the same storage. They are split for v2 due to lacking a good sync mechanism, which isn't true for v3
|
||||||
|
: new MemoryStorageService();
|
||||||
|
this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3)
|
||||||
|
? mv3MemoryStorageCreator() // mv3 stores to local-backed session storage
|
||||||
|
: this.memoryStorageForStateProviders; // mv2 stores to the same location
|
||||||
|
|
||||||
const storageServiceProvider = new StorageServiceProvider(
|
const storageServiceProvider = new BrowserStorageServiceProvider(
|
||||||
this.storageService,
|
this.storageService,
|
||||||
this.memoryStorageForStateProviders,
|
this.memoryStorageForStateProviders,
|
||||||
|
this.largeObjectMemoryStorageForStateProviders,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||||
@ -466,9 +493,7 @@ export default class MainBackground {
|
|||||||
this.accountService,
|
this.accountService,
|
||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
);
|
);
|
||||||
this.derivedStateProvider = new BackgroundDerivedStateProvider(
|
this.derivedStateProvider = new BackgroundDerivedStateProvider(storageServiceProvider);
|
||||||
this.memoryStorageForStateProviders,
|
|
||||||
);
|
|
||||||
this.stateProvider = new DefaultStateProvider(
|
this.stateProvider = new DefaultStateProvider(
|
||||||
this.activeUserStateProvider,
|
this.activeUserStateProvider,
|
||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
@ -788,7 +813,6 @@ export default class MainBackground {
|
|||||||
this.avatarService,
|
this.avatarService,
|
||||||
logoutCallback,
|
logoutCallback,
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
this.tokenService,
|
|
||||||
);
|
);
|
||||||
this.eventUploadService = new EventUploadService(
|
this.eventUploadService = new EventUploadService(
|
||||||
this.apiService,
|
this.apiService,
|
||||||
@ -1081,20 +1105,22 @@ export default class MainBackground {
|
|||||||
await (this.eventUploadService as EventUploadService).init(true);
|
await (this.eventUploadService as EventUploadService).init(true);
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
|
|
||||||
if (!this.popupOnlyContext) {
|
if (this.popupOnlyContext) {
|
||||||
await this.vaultTimeoutService.init(true);
|
return;
|
||||||
this.fido2Background.init();
|
}
|
||||||
await this.runtimeBackground.init();
|
|
||||||
await this.notificationBackground.init();
|
await this.vaultTimeoutService.init(true);
|
||||||
this.filelessImporterBackground.init();
|
this.fido2Background.init();
|
||||||
await this.commandsBackground.init();
|
await this.runtimeBackground.init();
|
||||||
await this.overlayBackground.init();
|
await this.notificationBackground.init();
|
||||||
await this.tabsBackground.init();
|
this.filelessImporterBackground.init();
|
||||||
this.contextMenusBackground?.init();
|
await this.commandsBackground.init();
|
||||||
await this.idleBackground.init();
|
await this.overlayBackground.init();
|
||||||
if (BrowserApi.isManifestVersion(2)) {
|
await this.tabsBackground.init();
|
||||||
await this.webRequestBackground.init();
|
this.contextMenusBackground?.init();
|
||||||
}
|
await this.idleBackground.init();
|
||||||
|
if (BrowserApi.isManifestVersion(2)) {
|
||||||
|
await this.webRequestBackground.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.platformUtilsService.isFirefox() && !this.isPrivateMode) {
|
if (this.platformUtilsService.isFirefox() && !this.isPrivateMode) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_extName__",
|
"name": "__MSG_extName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "2024.4.1",
|
"version": "2024.4.2",
|
||||||
"description": "__MSG_extDesc__",
|
"description": "__MSG_extDesc__",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Bitwarden Inc.",
|
"author": "Bitwarden Inc.",
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"minimum_chrome_version": "102.0",
|
"minimum_chrome_version": "102.0",
|
||||||
"name": "__MSG_extName__",
|
"name": "__MSG_extName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "2024.4.1",
|
"version": "2024.4.2",
|
||||||
"description": "__MSG_extDesc__",
|
"description": "__MSG_extDesc__",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Bitwarden Inc.",
|
"author": "Bitwarden Inc.",
|
||||||
@ -59,7 +59,6 @@
|
|||||||
"clipboardRead",
|
"clipboardRead",
|
||||||
"clipboardWrite",
|
"clipboardWrite",
|
||||||
"idle",
|
"idle",
|
||||||
"alarms",
|
|
||||||
"scripting",
|
"scripting",
|
||||||
"offscreen"
|
"offscreen"
|
||||||
],
|
],
|
||||||
|
@ -4,14 +4,14 @@ import { BackgroundDerivedStateProvider } from "../../state/background-derived-s
|
|||||||
|
|
||||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||||
import {
|
import {
|
||||||
MemoryStorageServiceInitOptions,
|
StorageServiceProviderInitOptions,
|
||||||
observableMemoryStorageServiceFactory,
|
storageServiceProviderFactory,
|
||||||
} from "./storage-service.factory";
|
} from "./storage-service-provider.factory";
|
||||||
|
|
||||||
type DerivedStateProviderFactoryOptions = FactoryOptions;
|
type DerivedStateProviderFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions &
|
export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions &
|
||||||
MemoryStorageServiceInitOptions;
|
StorageServiceProviderInitOptions;
|
||||||
|
|
||||||
export async function derivedStateProviderFactory(
|
export async function derivedStateProviderFactory(
|
||||||
cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices,
|
cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices,
|
||||||
@ -22,6 +22,6 @@ export async function derivedStateProviderFactory(
|
|||||||
"derivedStateProvider",
|
"derivedStateProvider",
|
||||||
opts,
|
opts,
|
||||||
async () =>
|
async () =>
|
||||||
new BackgroundDerivedStateProvider(await observableMemoryStorageServiceFactory(cache, opts)),
|
new BackgroundDerivedStateProvider(await storageServiceProviderFactory(cache, opts)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ import {
|
|||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
|
|
||||||
import { BrowserApi } from "../../browser/browser-api";
|
import { BrowserApi } from "../../browser/browser-api";
|
||||||
@ -17,10 +19,10 @@ import {
|
|||||||
KeyGenerationServiceInitOptions,
|
KeyGenerationServiceInitOptions,
|
||||||
keyGenerationServiceFactory,
|
keyGenerationServiceFactory,
|
||||||
} from "./key-generation-service.factory";
|
} from "./key-generation-service.factory";
|
||||||
import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory";
|
import { LogServiceInitOptions, logServiceFactory } from "./log-service.factory";
|
||||||
import {
|
import {
|
||||||
platformUtilsServiceFactory,
|
|
||||||
PlatformUtilsServiceInitOptions,
|
PlatformUtilsServiceInitOptions,
|
||||||
|
platformUtilsServiceFactory,
|
||||||
} from "./platform-utils-service.factory";
|
} from "./platform-utils-service.factory";
|
||||||
|
|
||||||
export type DiskStorageServiceInitOptions = FactoryOptions;
|
export type DiskStorageServiceInitOptions = FactoryOptions;
|
||||||
@ -70,13 +72,23 @@ export function memoryStorageServiceFactory(
|
|||||||
return factory(cache, "memoryStorageService", opts, async () => {
|
return factory(cache, "memoryStorageService", opts, async () => {
|
||||||
if (BrowserApi.isManifestVersion(3)) {
|
if (BrowserApi.isManifestVersion(3)) {
|
||||||
return new LocalBackedSessionStorageService(
|
return new LocalBackedSessionStorageService(
|
||||||
await logServiceFactory(cache, opts),
|
new Lazy(async () => {
|
||||||
await encryptServiceFactory(cache, opts),
|
const existingKey = await (
|
||||||
await keyGenerationServiceFactory(cache, opts),
|
await sessionStorageServiceFactory(cache, opts)
|
||||||
|
).get<SymmetricCryptoKey>("session-key");
|
||||||
|
if (existingKey) {
|
||||||
|
return existingKey;
|
||||||
|
}
|
||||||
|
const { derivedKey } = await (
|
||||||
|
await keyGenerationServiceFactory(cache, opts)
|
||||||
|
).createKeyWithPurpose(128, "ephemeral", "bitwarden-ephemeral");
|
||||||
|
await (await sessionStorageServiceFactory(cache, opts)).save("session-key", derivedKey);
|
||||||
|
return derivedKey;
|
||||||
|
}),
|
||||||
await diskStorageServiceFactory(cache, opts),
|
await diskStorageServiceFactory(cache, opts),
|
||||||
await sessionStorageServiceFactory(cache, opts),
|
await encryptServiceFactory(cache, opts),
|
||||||
await platformUtilsServiceFactory(cache, opts),
|
await platformUtilsServiceFactory(cache, opts),
|
||||||
"serviceFactories",
|
await logServiceFactory(cache, opts),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new MemoryStorageService();
|
return new MemoryStorageService();
|
||||||
|
@ -9,7 +9,7 @@ jest.mock("../flags", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
class TestClass {
|
class TestClass {
|
||||||
@devFlag("storeSessionDecrypted") test() {
|
@devFlag("managedEnvironment") test() {
|
||||||
return "test";
|
return "test";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ export type Flags = {
|
|||||||
// required to avoid linting errors when there are no flags
|
// required to avoid linting errors when there are no flags
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
export type DevFlags = {
|
export type DevFlags = {
|
||||||
storeSessionDecrypted?: boolean;
|
|
||||||
managedEnvironment?: GroupPolicyEnvironment;
|
managedEnvironment?: GroupPolicyEnvironment;
|
||||||
} & SharedDevFlags;
|
} & SharedDevFlags;
|
||||||
|
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
|
import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
|
||||||
import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service";
|
import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service";
|
||||||
|
|
||||||
export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
|
export default class BrowserMemoryStorageService
|
||||||
|
extends AbstractChromeStorageService
|
||||||
|
implements AbstractMemoryStorageService
|
||||||
|
{
|
||||||
constructor() {
|
constructor() {
|
||||||
super(chrome.storage.session);
|
super(chrome.storage.session);
|
||||||
}
|
}
|
||||||
|
type = "MemoryStorageService" as const;
|
||||||
|
getBypassCache<T>(key: string): Promise<T> {
|
||||||
|
return this.get(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,412 +1,200 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import {
|
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
StorageUpdate,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { FakeStorageService, makeEncString } from "@bitwarden/common/spec";
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
|
||||||
|
|
||||||
import { LocalBackedSessionStorageService } from "./local-backed-session-storage.service";
|
import { LocalBackedSessionStorageService } from "./local-backed-session-storage.service";
|
||||||
|
|
||||||
describe.skip("LocalBackedSessionStorage", () => {
|
describe("LocalBackedSessionStorage", () => {
|
||||||
const sendMessageWithResponseSpy: jest.SpyInstance = jest.spyOn(
|
const sessionKey = new SymmetricCryptoKey(
|
||||||
BrowserApi,
|
Utils.fromUtf8ToArray("00000000000000000000000000000000"),
|
||||||
"sendMessageWithResponse",
|
|
||||||
);
|
);
|
||||||
|
let localStorage: FakeStorageService;
|
||||||
let encryptService: MockProxy<EncryptService>;
|
let encryptService: MockProxy<EncryptService>;
|
||||||
let keyGenerationService: MockProxy<KeyGenerationService>;
|
|
||||||
let localStorageService: MockProxy<AbstractStorageService>;
|
|
||||||
let sessionStorageService: MockProxy<AbstractMemoryStorageService>;
|
|
||||||
let logService: MockProxy<LogService>;
|
|
||||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||||
|
let logService: MockProxy<LogService>;
|
||||||
let cache: Record<string, unknown>;
|
|
||||||
const testObj = { a: 1, b: 2 };
|
|
||||||
const stringifiedTestObj = JSON.stringify(testObj);
|
|
||||||
|
|
||||||
const key = new SymmetricCryptoKey(Utils.fromUtf8ToArray("00000000000000000000000000000000"));
|
|
||||||
let getSessionKeySpy: jest.SpyInstance;
|
|
||||||
let sendUpdateSpy: jest.SpyInstance<void, [storageUpdate: StorageUpdate]>;
|
|
||||||
const mockEnc = (input: string) => Promise.resolve(new EncString("ENCRYPTED" + input));
|
|
||||||
|
|
||||||
let sut: LocalBackedSessionStorageService;
|
let sut: LocalBackedSessionStorageService;
|
||||||
|
|
||||||
const mockExistingSessionKey = (key: SymmetricCryptoKey) => {
|
|
||||||
sessionStorageService.get.mockImplementation((storageKey) => {
|
|
||||||
if (storageKey === "localEncryptionKey_test") {
|
|
||||||
return Promise.resolve(key?.toJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject("No implementation for " + storageKey);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sendMessageWithResponseSpy.mockResolvedValue(null);
|
localStorage = new FakeStorageService();
|
||||||
logService = mock<LogService>();
|
|
||||||
encryptService = mock<EncryptService>();
|
encryptService = mock<EncryptService>();
|
||||||
keyGenerationService = mock<KeyGenerationService>();
|
platformUtilsService = mock<PlatformUtilsService>();
|
||||||
localStorageService = mock<AbstractStorageService>();
|
logService = mock<LogService>();
|
||||||
sessionStorageService = mock<AbstractMemoryStorageService>();
|
|
||||||
|
|
||||||
sut = new LocalBackedSessionStorageService(
|
sut = new LocalBackedSessionStorageService(
|
||||||
logService,
|
new Lazy(async () => sessionKey),
|
||||||
|
localStorage,
|
||||||
encryptService,
|
encryptService,
|
||||||
keyGenerationService,
|
|
||||||
localStorageService,
|
|
||||||
sessionStorageService,
|
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
"test",
|
logService,
|
||||||
);
|
);
|
||||||
|
|
||||||
cache = sut["cachedSession"];
|
|
||||||
|
|
||||||
keyGenerationService.createKeyWithPurpose.mockResolvedValue({
|
|
||||||
derivedKey: key,
|
|
||||||
salt: "bitwarden-ephemeral",
|
|
||||||
material: null, // Not used
|
|
||||||
});
|
|
||||||
|
|
||||||
getSessionKeySpy = jest.spyOn(sut, "getSessionEncKey");
|
|
||||||
getSessionKeySpy.mockResolvedValue(key);
|
|
||||||
|
|
||||||
// sendUpdateSpy = jest.spyOn(sut, "sendUpdate");
|
|
||||||
// sendUpdateSpy.mockReturnValue();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("get", () => {
|
describe("get", () => {
|
||||||
describe("in local cache or external context cache", () => {
|
it("return the cached value when one is cached", async () => {
|
||||||
it("should return from local cache", async () => {
|
sut["cache"]["test"] = "cached";
|
||||||
cache["test"] = stringifiedTestObj;
|
const result = await sut.get("test");
|
||||||
const result = await sut.get("test");
|
expect(result).toEqual("cached");
|
||||||
expect(result).toStrictEqual(testObj);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return from external context cache when local cache is not available", async () => {
|
|
||||||
sendMessageWithResponseSpy.mockResolvedValue(stringifiedTestObj);
|
|
||||||
const result = await sut.get("test");
|
|
||||||
expect(result).toStrictEqual(testObj);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("not in cache", () => {
|
it("returns a decrypted value when one is stored in local storage", async () => {
|
||||||
const session = { test: stringifiedTestObj };
|
const encrypted = makeEncString("encrypted");
|
||||||
|
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||||
|
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||||
|
const result = await sut.get("test");
|
||||||
|
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey);
|
||||||
|
expect(result).toEqual("decrypted");
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
it("caches the decrypted value when one is stored in local storage", async () => {
|
||||||
mockExistingSessionKey(key);
|
const encrypted = makeEncString("encrypted");
|
||||||
});
|
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||||
|
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||||
|
await sut.get("test");
|
||||||
|
expect(sut["cache"]["test"]).toEqual("decrypted");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("no session retrieved", () => {
|
describe("getBypassCache", () => {
|
||||||
let result: any;
|
it("ignores cached values", async () => {
|
||||||
let spy: jest.SpyInstance;
|
sut["cache"]["test"] = "cached";
|
||||||
beforeEach(async () => {
|
const encrypted = makeEncString("encrypted");
|
||||||
spy = jest.spyOn(sut, "getLocalSession").mockResolvedValue(null);
|
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||||
localStorageService.get.mockResolvedValue(null);
|
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||||
result = await sut.get("test");
|
const result = await sut.getBypassCache("test");
|
||||||
});
|
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey);
|
||||||
|
expect(result).toEqual("decrypted");
|
||||||
|
});
|
||||||
|
|
||||||
it("should grab from session if not in cache", async () => {
|
it("returns a decrypted value when one is stored in local storage", async () => {
|
||||||
expect(spy).toHaveBeenCalledWith(key);
|
const encrypted = makeEncString("encrypted");
|
||||||
});
|
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||||
|
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||||
|
const result = await sut.getBypassCache("test");
|
||||||
|
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey);
|
||||||
|
expect(result).toEqual("decrypted");
|
||||||
|
});
|
||||||
|
|
||||||
it("should return null if session is null", async () => {
|
it("caches the decrypted value when one is stored in local storage", async () => {
|
||||||
expect(result).toBeNull();
|
const encrypted = makeEncString("encrypted");
|
||||||
});
|
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||||
});
|
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||||
|
await sut.getBypassCache("test");
|
||||||
|
expect(sut["cache"]["test"]).toEqual("decrypted");
|
||||||
|
});
|
||||||
|
|
||||||
describe("session retrieved from storage", () => {
|
it("deserializes when a deserializer is provided", async () => {
|
||||||
beforeEach(() => {
|
const encrypted = makeEncString("encrypted");
|
||||||
jest.spyOn(sut, "getLocalSession").mockResolvedValue(session);
|
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||||
});
|
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||||
|
const deserializer = jest.fn().mockReturnValue("deserialized");
|
||||||
it("should return null if session does not have the key", async () => {
|
const result = await sut.getBypassCache("test", { deserializer });
|
||||||
const result = await sut.get("DNE");
|
expect(deserializer).toHaveBeenCalledWith("decrypted");
|
||||||
expect(result).toBeNull();
|
expect(result).toEqual("deserialized");
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the value retrieved from session", async () => {
|
|
||||||
const result = await sut.get("test");
|
|
||||||
expect(result).toEqual(session.test);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set retrieved values in cache", async () => {
|
|
||||||
await sut.get("test");
|
|
||||||
expect(cache["test"]).toBeTruthy();
|
|
||||||
expect(cache["test"]).toEqual(session.test);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should use a deserializer if provided", async () => {
|
|
||||||
const deserializer = jest.fn().mockReturnValue(testObj);
|
|
||||||
const result = await sut.get("test", { deserializer: deserializer });
|
|
||||||
expect(deserializer).toHaveBeenCalledWith(session.test);
|
|
||||||
expect(result).toEqual(testObj);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("has", () => {
|
describe("has", () => {
|
||||||
it("should be false if `get` returns null", async () => {
|
it("returns false when the key is not in cache", async () => {
|
||||||
const spy = jest.spyOn(sut, "get");
|
const result = await sut.has("test");
|
||||||
spy.mockResolvedValue(null);
|
expect(result).toBe(false);
|
||||||
expect(await sut.has("test")).toBe(false);
|
});
|
||||||
|
|
||||||
|
it("returns true when the key is in cache", async () => {
|
||||||
|
sut["cache"]["test"] = "cached";
|
||||||
|
const result = await sut.has("test");
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns true when the key is in local storage", async () => {
|
||||||
|
localStorage.internalStore["session_test"] = makeEncString("encrypted").encryptedString;
|
||||||
|
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||||
|
const result = await sut.has("test");
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([null, undefined])("returns false when %s is cached", async (nullish) => {
|
||||||
|
sut["cache"]["test"] = nullish;
|
||||||
|
await expect(sut.has("test")).resolves.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([null, undefined])(
|
||||||
|
"returns false when null is stored in local storage",
|
||||||
|
async (nullish) => {
|
||||||
|
localStorage.internalStore["session_test"] = nullish;
|
||||||
|
await expect(sut.has("test")).resolves.toBe(false);
|
||||||
|
expect(encryptService.decryptToUtf8).not.toHaveBeenCalled();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("save", () => {
|
||||||
|
const encString = makeEncString("encrypted");
|
||||||
|
beforeEach(() => {
|
||||||
|
encryptService.encrypt.mockResolvedValue(encString);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs a warning when saving the same value twice and in a dev environment", async () => {
|
||||||
|
platformUtilsService.isDev.mockReturnValue(true);
|
||||||
|
sut["cache"]["test"] = "cached";
|
||||||
|
await sut.save("test", "cached");
|
||||||
|
expect(logService.warning).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not log when saving the same value twice and not in a dev environment", async () => {
|
||||||
|
platformUtilsService.isDev.mockReturnValue(false);
|
||||||
|
sut["cache"]["test"] = "cached";
|
||||||
|
await sut.save("test", "cached");
|
||||||
|
expect(logService.warning).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes the key when saving a null value", async () => {
|
||||||
|
const spy = jest.spyOn(sut, "remove");
|
||||||
|
await sut.save("test", null);
|
||||||
expect(spy).toHaveBeenCalledWith("test");
|
expect(spy).toHaveBeenCalledWith("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be true if `get` returns non-null", async () => {
|
it("saves the value to cache", async () => {
|
||||||
const spy = jest.spyOn(sut, "get");
|
await sut.save("test", "value");
|
||||||
spy.mockResolvedValue({});
|
expect(sut["cache"]["test"]).toEqual("value");
|
||||||
expect(await sut.has("test")).toBe(true);
|
});
|
||||||
expect(spy).toHaveBeenCalledWith("test");
|
|
||||||
|
it("encrypts and saves the value to local storage", async () => {
|
||||||
|
await sut.save("test", "value");
|
||||||
|
expect(encryptService.encrypt).toHaveBeenCalledWith(JSON.stringify("value"), sessionKey);
|
||||||
|
expect(localStorage.internalStore["session_test"]).toEqual(encString.encryptedString);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits an update", async () => {
|
||||||
|
const spy = jest.spyOn(sut["updatesSubject"], "next");
|
||||||
|
await sut.save("test", "value");
|
||||||
|
expect(spy).toHaveBeenCalledWith({ key: "test", updateType: "save" });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("remove", () => {
|
describe("remove", () => {
|
||||||
describe("existing cache value is null", () => {
|
it("nulls the value in cache", async () => {
|
||||||
it("should not save null if the local cached value is already null", async () => {
|
sut["cache"]["test"] = "cached";
|
||||||
cache["test"] = null;
|
|
||||||
await sut.remove("test");
|
|
||||||
expect(sendUpdateSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not save null if the externally cached value is already null", async () => {
|
|
||||||
sendMessageWithResponseSpy.mockResolvedValue(null);
|
|
||||||
await sut.remove("test");
|
|
||||||
expect(sendUpdateSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should save null", async () => {
|
|
||||||
cache["test"] = stringifiedTestObj;
|
|
||||||
|
|
||||||
await sut.remove("test");
|
await sut.remove("test");
|
||||||
expect(sendUpdateSpy).toHaveBeenCalledWith({ key: "test", updateType: "remove" });
|
expect(sut["cache"]["test"]).toBeNull();
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("save", () => {
|
|
||||||
describe("currently cached", () => {
|
|
||||||
it("does not save the value a local cached value exists which is an exact match", async () => {
|
|
||||||
cache["test"] = stringifiedTestObj;
|
|
||||||
await sut.save("test", testObj);
|
|
||||||
expect(sendUpdateSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not save the value if a local cached value exists, even if the keys not in the same order", async () => {
|
|
||||||
cache["test"] = JSON.stringify({ b: 2, a: 1 });
|
|
||||||
await sut.save("test", testObj);
|
|
||||||
expect(sendUpdateSpy).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not save the value a externally cached value exists which is an exact match", async () => {
|
|
||||||
sendMessageWithResponseSpy.mockResolvedValue(stringifiedTestObj);
|
|
||||||
await sut.save("test", testObj);
|
|
||||||
expect(sendUpdateSpy).not.toHaveBeenCalled();
|
|
||||||
expect(cache["test"]).toBe(stringifiedTestObj);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("saves the value if the currently cached string value evaluates to a falsy value", async () => {
|
|
||||||
cache["test"] = "null";
|
|
||||||
await sut.save("test", testObj);
|
|
||||||
expect(sendUpdateSpy).toHaveBeenCalledWith({ key: "test", updateType: "save" });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("caching", () => {
|
it("removes the key from local storage", async () => {
|
||||||
beforeEach(() => {
|
localStorage.internalStore["session_test"] = makeEncString("encrypted").encryptedString;
|
||||||
localStorageService.get.mockResolvedValue(null);
|
await sut.remove("test");
|
||||||
sessionStorageService.get.mockResolvedValue(null);
|
expect(localStorage.internalStore["session_test"]).toBeUndefined();
|
||||||
|
|
||||||
localStorageService.save.mockResolvedValue();
|
|
||||||
sessionStorageService.save.mockResolvedValue();
|
|
||||||
|
|
||||||
encryptService.encrypt.mockResolvedValue(mockEnc("{}"));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove key from cache if value is null", async () => {
|
|
||||||
cache["test"] = {};
|
|
||||||
// const cacheSetSpy = jest.spyOn(cache, "set");
|
|
||||||
expect(cache["test"]).toBe(true);
|
|
||||||
await sut.save("test", null);
|
|
||||||
// Don't remove from cache, just replace with null
|
|
||||||
expect(cache["test"]).toBe(null);
|
|
||||||
// expect(cacheSetSpy).toHaveBeenCalledWith("test", null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should set cache if value is non-null", async () => {
|
|
||||||
expect(cache["test"]).toBe(false);
|
|
||||||
// const setSpy = jest.spyOn(cache, "set");
|
|
||||||
await sut.save("test", testObj);
|
|
||||||
expect(cache["test"]).toBe(stringifiedTestObj);
|
|
||||||
// expect(setSpy).toHaveBeenCalledWith("test", stringifiedTestObj);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("local storing", () => {
|
it("emits an update", async () => {
|
||||||
let setSpy: jest.SpyInstance;
|
const spy = jest.spyOn(sut["updatesSubject"], "next");
|
||||||
|
await sut.remove("test");
|
||||||
beforeEach(() => {
|
expect(spy).toHaveBeenCalledWith({ key: "test", updateType: "remove" });
|
||||||
setSpy = jest.spyOn(sut, "setLocalSession").mockResolvedValue();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should store a new session", async () => {
|
|
||||||
jest.spyOn(sut, "getLocalSession").mockResolvedValue(null);
|
|
||||||
await sut.save("test", testObj);
|
|
||||||
|
|
||||||
expect(setSpy).toHaveBeenCalledWith({ test: testObj }, key);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should update an existing session", async () => {
|
|
||||||
const existingObj = { test: testObj };
|
|
||||||
jest.spyOn(sut, "getLocalSession").mockResolvedValue(existingObj);
|
|
||||||
await sut.save("test2", testObj);
|
|
||||||
|
|
||||||
expect(setSpy).toHaveBeenCalledWith({ test2: testObj, ...existingObj }, key);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should overwrite an existing item in session", async () => {
|
|
||||||
const existingObj = { test: {} };
|
|
||||||
jest.spyOn(sut, "getLocalSession").mockResolvedValue(existingObj);
|
|
||||||
await sut.save("test", testObj);
|
|
||||||
|
|
||||||
expect(setSpy).toHaveBeenCalledWith({ test: testObj }, key);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getSessionKey", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
getSessionKeySpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return the stored symmetric crypto key", async () => {
|
|
||||||
sessionStorageService.get.mockResolvedValue({ ...key });
|
|
||||||
const result = await sut.getSessionEncKey();
|
|
||||||
|
|
||||||
expect(result).toStrictEqual(key);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("new key creation", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
keyGenerationService.createKeyWithPurpose.mockResolvedValue({
|
|
||||||
salt: "salt",
|
|
||||||
material: null,
|
|
||||||
derivedKey: key,
|
|
||||||
});
|
|
||||||
jest.spyOn(sut, "setSessionEncKey").mockResolvedValue();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create a symmetric crypto key", async () => {
|
|
||||||
const result = await sut.getSessionEncKey();
|
|
||||||
|
|
||||||
expect(result).toStrictEqual(key);
|
|
||||||
expect(keyGenerationService.createKeyWithPurpose).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should store a symmetric crypto key if it makes one", async () => {
|
|
||||||
const spy = jest.spyOn(sut, "setSessionEncKey").mockResolvedValue();
|
|
||||||
await sut.getSessionEncKey();
|
|
||||||
|
|
||||||
expect(spy).toHaveBeenCalledWith(key);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getLocalSession", () => {
|
|
||||||
it("should return null if session is null", async () => {
|
|
||||||
const result = await sut.getLocalSession(key);
|
|
||||||
|
|
||||||
expect(result).toBeNull();
|
|
||||||
expect(localStorageService.get).toHaveBeenCalledWith("session_test");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("non-null sessions", () => {
|
|
||||||
const session = { test: "test" };
|
|
||||||
const encSession = new EncString(JSON.stringify(session));
|
|
||||||
const decryptedSession = JSON.stringify(session);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
localStorageService.get.mockResolvedValue(encSession.encryptedString);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should decrypt returned sessions", async () => {
|
|
||||||
encryptService.decryptToUtf8
|
|
||||||
.calledWith(expect.anything(), key)
|
|
||||||
.mockResolvedValue(decryptedSession);
|
|
||||||
await sut.getLocalSession(key);
|
|
||||||
expect(encryptService.decryptToUtf8).toHaveBeenNthCalledWith(1, encSession, key);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should parse session", async () => {
|
|
||||||
encryptService.decryptToUtf8
|
|
||||||
.calledWith(expect.anything(), key)
|
|
||||||
.mockResolvedValue(decryptedSession);
|
|
||||||
const result = await sut.getLocalSession(key);
|
|
||||||
expect(result).toEqual(session);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove state if decryption fails", async () => {
|
|
||||||
encryptService.decryptToUtf8.mockResolvedValue(null);
|
|
||||||
const setSessionEncKeySpy = jest.spyOn(sut, "setSessionEncKey").mockResolvedValue();
|
|
||||||
|
|
||||||
const result = await sut.getLocalSession(key);
|
|
||||||
|
|
||||||
expect(result).toBeNull();
|
|
||||||
expect(setSessionEncKeySpy).toHaveBeenCalledWith(null);
|
|
||||||
expect(localStorageService.remove).toHaveBeenCalledWith("session_test");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("setLocalSession", () => {
|
|
||||||
const testSession = { test: "a" };
|
|
||||||
const testJSON = JSON.stringify(testSession);
|
|
||||||
|
|
||||||
it("should encrypt a stringified session", async () => {
|
|
||||||
encryptService.encrypt.mockImplementation(mockEnc);
|
|
||||||
localStorageService.save.mockResolvedValue();
|
|
||||||
await sut.setLocalSession(testSession, key);
|
|
||||||
|
|
||||||
expect(encryptService.encrypt).toHaveBeenNthCalledWith(1, testJSON, key);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove local session if null", async () => {
|
|
||||||
encryptService.encrypt.mockResolvedValue(null);
|
|
||||||
await sut.setLocalSession(null, key);
|
|
||||||
|
|
||||||
expect(localStorageService.remove).toHaveBeenCalledWith("session_test");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should save encrypted string", async () => {
|
|
||||||
encryptService.encrypt.mockImplementation(mockEnc);
|
|
||||||
await sut.setLocalSession(testSession, key);
|
|
||||||
|
|
||||||
expect(localStorageService.save).toHaveBeenCalledWith(
|
|
||||||
"session_test",
|
|
||||||
(await mockEnc(testJSON)).encryptedString,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("setSessionKey", () => {
|
|
||||||
it("should remove if null", async () => {
|
|
||||||
await sut.setSessionEncKey(null);
|
|
||||||
expect(sessionStorageService.remove).toHaveBeenCalledWith("localEncryptionKey_test");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should save key when not null", async () => {
|
|
||||||
await sut.setSessionEncKey(key);
|
|
||||||
expect(sessionStorageService.save).toHaveBeenCalledWith("localEncryptionKey_test", key);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,6 @@ import { Subject } from "rxjs";
|
|||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import {
|
import {
|
||||||
@ -11,13 +10,12 @@ import {
|
|||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
StorageUpdate,
|
StorageUpdate,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { MemoryStorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
import { MemoryStorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
import { devFlag } from "../decorators/dev-flag.decorator";
|
|
||||||
import { devFlagEnabled } from "../flags";
|
|
||||||
import { MemoryStoragePortMessage } from "../storage/port-messages";
|
import { MemoryStoragePortMessage } from "../storage/port-messages";
|
||||||
import { portName } from "../storage/port-name";
|
import { portName } from "../storage/port-name";
|
||||||
|
|
||||||
@ -25,85 +23,64 @@ export class LocalBackedSessionStorageService
|
|||||||
extends AbstractMemoryStorageService
|
extends AbstractMemoryStorageService
|
||||||
implements ObservableStorageService
|
implements ObservableStorageService
|
||||||
{
|
{
|
||||||
|
private ports: Set<chrome.runtime.Port> = new Set([]);
|
||||||
|
private cache: Record<string, unknown> = {};
|
||||||
private updatesSubject = new Subject<StorageUpdate>();
|
private updatesSubject = new Subject<StorageUpdate>();
|
||||||
private commandName = `localBackedSessionStorage_${this.partitionName}`;
|
readonly valuesRequireDeserialization = true;
|
||||||
private encKey = `localEncryptionKey_${this.partitionName}`;
|
updates$ = this.updatesSubject.asObservable();
|
||||||
private sessionKey = `session_${this.partitionName}`;
|
|
||||||
private cachedSession: Record<string, unknown> = {};
|
|
||||||
private _ports: Set<chrome.runtime.Port> = new Set([]);
|
|
||||||
private knownNullishCacheKeys: Set<string> = new Set([]);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private logService: LogService,
|
private readonly sessionKey: Lazy<Promise<SymmetricCryptoKey>>,
|
||||||
private encryptService: EncryptService,
|
private readonly localStorage: AbstractStorageService,
|
||||||
private keyGenerationService: KeyGenerationService,
|
private readonly encryptService: EncryptService,
|
||||||
private localStorage: AbstractStorageService,
|
private readonly platformUtilsService: PlatformUtilsService,
|
||||||
private sessionStorage: AbstractStorageService,
|
private readonly logService: LogService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
|
||||||
private partitionName: string,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
BrowserApi.addListener(chrome.runtime.onConnect, (port) => {
|
BrowserApi.addListener(chrome.runtime.onConnect, (port) => {
|
||||||
if (port.name !== `${portName(chrome.storage.session)}_${partitionName}`) {
|
if (port.name !== portName(chrome.storage.session)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ports.add(port);
|
this.ports.add(port);
|
||||||
|
|
||||||
const listenerCallback = this.onMessageFromForeground.bind(this);
|
const listenerCallback = this.onMessageFromForeground.bind(this);
|
||||||
port.onDisconnect.addListener(() => {
|
port.onDisconnect.addListener(() => {
|
||||||
this._ports.delete(port);
|
this.ports.delete(port);
|
||||||
port.onMessage.removeListener(listenerCallback);
|
port.onMessage.removeListener(listenerCallback);
|
||||||
});
|
});
|
||||||
port.onMessage.addListener(listenerCallback);
|
port.onMessage.addListener(listenerCallback);
|
||||||
// Initialize the new memory storage service with existing data
|
// Initialize the new memory storage service with existing data
|
||||||
this.sendMessageTo(port, {
|
this.sendMessageTo(port, {
|
||||||
action: "initialization",
|
action: "initialization",
|
||||||
data: Array.from(Object.keys(this.cachedSession)),
|
data: Array.from(Object.keys(this.cache)),
|
||||||
|
});
|
||||||
|
this.updates$.subscribe((update) => {
|
||||||
|
this.broadcastMessage({
|
||||||
|
action: "subject_update",
|
||||||
|
data: update,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.updates$.subscribe((update) => {
|
|
||||||
this.broadcastMessage({
|
|
||||||
action: "subject_update",
|
|
||||||
data: update,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get valuesRequireDeserialization(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get updates$() {
|
|
||||||
return this.updatesSubject.asObservable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async get<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T> {
|
async get<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T> {
|
||||||
if (this.cachedSession[key] != null) {
|
if (this.cache[key] !== undefined) {
|
||||||
return this.cachedSession[key] as T;
|
return this.cache[key] as T;
|
||||||
}
|
|
||||||
|
|
||||||
if (this.knownNullishCacheKeys.has(key)) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.getBypassCache(key, options);
|
return await this.getBypassCache(key, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBypassCache<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T> {
|
async getBypassCache<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T> {
|
||||||
const session = await this.getLocalSession(await this.getSessionEncKey());
|
let value = await this.getLocalSessionValue(await this.sessionKey.get(), key);
|
||||||
if (session[key] == null) {
|
|
||||||
this.knownNullishCacheKeys.add(key);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = session[key];
|
|
||||||
if (options?.deserializer != null) {
|
if (options?.deserializer != null) {
|
||||||
value = options.deserializer(value as Jsonify<T>);
|
value = options.deserializer(value as Jsonify<T>);
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.save(key, value);
|
this.cache[key] = value;
|
||||||
return value as T;
|
return value as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +91,7 @@ export class LocalBackedSessionStorageService
|
|||||||
async save<T>(key: string, obj: T): Promise<void> {
|
async save<T>(key: string, obj: T): Promise<void> {
|
||||||
// This is for observation purposes only. At some point, we don't want to write to local session storage if the value is the same.
|
// This is for observation purposes only. At some point, we don't want to write to local session storage if the value is the same.
|
||||||
if (this.platformUtilsService.isDev()) {
|
if (this.platformUtilsService.isDev()) {
|
||||||
const existingValue = this.cachedSession[key] as T;
|
const existingValue = this.cache[key] as T;
|
||||||
if (this.compareValues<T>(existingValue, obj)) {
|
if (this.compareValues<T>(existingValue, obj)) {
|
||||||
this.logService.warning(`Possible unnecessary write to local session storage. Key: ${key}`);
|
this.logService.warning(`Possible unnecessary write to local session storage. Key: ${key}`);
|
||||||
this.logService.warning(obj as any);
|
this.logService.warning(obj as any);
|
||||||
@ -125,128 +102,42 @@ export class LocalBackedSessionStorageService
|
|||||||
return await this.remove(key);
|
return await this.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.knownNullishCacheKeys.delete(key);
|
this.cache[key] = obj;
|
||||||
this.cachedSession[key] = obj;
|
|
||||||
await this.updateLocalSessionValue(key, obj);
|
await this.updateLocalSessionValue(key, obj);
|
||||||
this.updatesSubject.next({ key, updateType: "save" });
|
this.updatesSubject.next({ key, updateType: "save" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(key: string): Promise<void> {
|
async remove(key: string): Promise<void> {
|
||||||
this.knownNullishCacheKeys.add(key);
|
this.cache[key] = null;
|
||||||
delete this.cachedSession[key];
|
|
||||||
await this.updateLocalSessionValue(key, null);
|
await this.updateLocalSessionValue(key, null);
|
||||||
this.updatesSubject.next({ key, updateType: "remove" });
|
this.updatesSubject.next({ key, updateType: "remove" });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateLocalSessionValue<T>(key: string, obj: T) {
|
private async getLocalSessionValue(encKey: SymmetricCryptoKey, key: string): Promise<unknown> {
|
||||||
const sessionEncKey = await this.getSessionEncKey();
|
const local = await this.localStorage.get<string>(this.sessionStorageKey(key));
|
||||||
const localSession = (await this.getLocalSession(sessionEncKey)) ?? {};
|
|
||||||
localSession[key] = obj;
|
|
||||||
void this.setLocalSession(localSession, sessionEncKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLocalSession(encKey: SymmetricCryptoKey): Promise<Record<string, unknown>> {
|
|
||||||
if (Object.keys(this.cachedSession).length > 0) {
|
|
||||||
return this.cachedSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cachedSession = {};
|
|
||||||
const local = await this.localStorage.get<string>(this.sessionKey);
|
|
||||||
if (local == null) {
|
if (local == null) {
|
||||||
return this.cachedSession;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (devFlagEnabled("storeSessionDecrypted")) {
|
const valueJson = await this.encryptService.decryptToUtf8(new EncString(local), encKey);
|
||||||
return local as any as Record<string, unknown>;
|
if (valueJson == null) {
|
||||||
|
// error with decryption, value is lost, delete state and start over
|
||||||
|
await this.localStorage.remove(this.sessionStorageKey(key));
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionJson = await this.encryptService.decryptToUtf8(new EncString(local), encKey);
|
return JSON.parse(valueJson);
|
||||||
if (sessionJson == null) {
|
|
||||||
// Error with decryption -- session is lost, delete state and key and start over
|
|
||||||
await this.setSessionEncKey(null);
|
|
||||||
await this.localStorage.remove(this.sessionKey);
|
|
||||||
return this.cachedSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cachedSession = JSON.parse(sessionJson);
|
|
||||||
return this.cachedSession;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setLocalSession(session: Record<string, unknown>, key: SymmetricCryptoKey) {
|
private async updateLocalSessionValue(key: string, value: unknown): Promise<void> {
|
||||||
if (devFlagEnabled("storeSessionDecrypted")) {
|
if (value == null) {
|
||||||
await this.setDecryptedLocalSession(session);
|
await this.localStorage.remove(this.sessionStorageKey(key));
|
||||||
} else {
|
return;
|
||||||
await this.setEncryptedLocalSession(session, key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@devFlag("storeSessionDecrypted")
|
|
||||||
async setDecryptedLocalSession(session: Record<string, unknown>): Promise<void> {
|
|
||||||
// Make sure we're storing the jsonified version of the session
|
|
||||||
const jsonSession = JSON.parse(JSON.stringify(session));
|
|
||||||
if (session == null) {
|
|
||||||
await this.localStorage.remove(this.sessionKey);
|
|
||||||
} else {
|
|
||||||
await this.localStorage.save(this.sessionKey, jsonSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setEncryptedLocalSession(session: Record<string, unknown>, key: SymmetricCryptoKey) {
|
|
||||||
const jsonSession = JSON.stringify(session);
|
|
||||||
const encSession = await this.encryptService.encrypt(jsonSession, key);
|
|
||||||
|
|
||||||
if (encSession == null) {
|
|
||||||
return await this.localStorage.remove(this.sessionKey);
|
|
||||||
}
|
|
||||||
await this.localStorage.save(this.sessionKey, encSession.encryptedString);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSessionEncKey(): Promise<SymmetricCryptoKey> {
|
|
||||||
let storedKey = await this.sessionStorage.get<SymmetricCryptoKey>(this.encKey);
|
|
||||||
if (storedKey == null || Object.keys(storedKey).length == 0) {
|
|
||||||
const generatedKey = await this.keyGenerationService.createKeyWithPurpose(
|
|
||||||
128,
|
|
||||||
"ephemeral",
|
|
||||||
"bitwarden-ephemeral",
|
|
||||||
);
|
|
||||||
storedKey = generatedKey.derivedKey;
|
|
||||||
await this.setSessionEncKey(storedKey);
|
|
||||||
return storedKey;
|
|
||||||
} else {
|
|
||||||
return SymmetricCryptoKey.fromJSON(storedKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setSessionEncKey(input: SymmetricCryptoKey): Promise<void> {
|
|
||||||
if (input == null) {
|
|
||||||
await this.sessionStorage.remove(this.encKey);
|
|
||||||
} else {
|
|
||||||
await this.sessionStorage.save(this.encKey, input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private compareValues<T>(value1: T, value2: T): boolean {
|
|
||||||
if (value1 == null && value2 == null) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value1 && value2 == null) {
|
const valueJson = JSON.stringify(value);
|
||||||
return false;
|
const encValue = await this.encryptService.encrypt(valueJson, await this.sessionKey.get());
|
||||||
}
|
await this.localStorage.save(this.sessionStorageKey(key), encValue.encryptedString);
|
||||||
|
|
||||||
if (value1 == null && value2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value1 !== "object" || typeof value2 !== "object") {
|
|
||||||
return value1 === value2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JSON.stringify(value1) === JSON.stringify(value2)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.entries(value1).sort().toString() === Object.entries(value2).sort().toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onMessageFromForeground(
|
private async onMessageFromForeground(
|
||||||
@ -282,7 +173,7 @@ export class LocalBackedSessionStorageService
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected broadcastMessage(data: Omit<MemoryStoragePortMessage, "originator">) {
|
protected broadcastMessage(data: Omit<MemoryStoragePortMessage, "originator">) {
|
||||||
this._ports.forEach((port) => {
|
this.ports.forEach((port) => {
|
||||||
this.sendMessageTo(port, data);
|
this.sendMessageTo(port, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -296,4 +187,32 @@ export class LocalBackedSessionStorageService
|
|||||||
originator: "background",
|
originator: "background",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sessionStorageKey(key: string) {
|
||||||
|
return `session_${key}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private compareValues<T>(value1: T, value2: T): boolean {
|
||||||
|
if (value1 == null && value2 == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value1 && value2 == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value1 == null && value2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value1 !== "object" || typeof value2 !== "object") {
|
||||||
|
return value1 === value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON.stringify(value1) === JSON.stringify(value2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(value1).sort().toString() === Object.entries(value2).sort().toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AbstractStorageService,
|
||||||
|
ObservableStorageService,
|
||||||
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
|
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client
|
// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client
|
||||||
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
||||||
@ -12,11 +16,14 @@ export class BackgroundDerivedStateProvider extends DefaultDerivedStateProvider
|
|||||||
parentState$: Observable<TFrom>,
|
parentState$: Observable<TFrom>,
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
dependencies: TDeps,
|
dependencies: TDeps,
|
||||||
|
storageLocation: [string, AbstractStorageService & ObservableStorageService],
|
||||||
): DerivedState<TTo> {
|
): DerivedState<TTo> {
|
||||||
|
const [cacheKey, storageService] = storageLocation;
|
||||||
return new BackgroundDerivedState(
|
return new BackgroundDerivedState(
|
||||||
parentState$,
|
parentState$,
|
||||||
deriveDefinition,
|
deriveDefinition,
|
||||||
this.memoryStorage,
|
storageService,
|
||||||
|
cacheKey,
|
||||||
dependencies,
|
dependencies,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,10 @@ export class BackgroundDerivedState<
|
|||||||
parentState$: Observable<TFrom>,
|
parentState$: Observable<TFrom>,
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
memoryStorage: AbstractStorageService & ObservableStorageService,
|
memoryStorage: AbstractStorageService & ObservableStorageService,
|
||||||
|
portName: string,
|
||||||
dependencies: TDeps,
|
dependencies: TDeps,
|
||||||
) {
|
) {
|
||||||
super(parentState$, deriveDefinition, memoryStorage, dependencies);
|
super(parentState$, deriveDefinition, memoryStorage, dependencies);
|
||||||
const portName = deriveDefinition.buildCacheKey();
|
|
||||||
|
|
||||||
// listen for foreground derived states to connect
|
// listen for foreground derived states to connect
|
||||||
BrowserApi.addListener(chrome.runtime.onConnect, (port) => {
|
BrowserApi.addListener(chrome.runtime.onConnect, (port) => {
|
||||||
|
@ -38,14 +38,21 @@ describe("foreground background derived state interactions", () => {
|
|||||||
let memoryStorage: FakeStorageService;
|
let memoryStorage: FakeStorageService;
|
||||||
const initialParent = "2020-01-01";
|
const initialParent = "2020-01-01";
|
||||||
const ngZone = mock<NgZone>();
|
const ngZone = mock<NgZone>();
|
||||||
|
const portName = "testPort";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockPorts();
|
mockPorts();
|
||||||
parentState$ = new Subject<string>();
|
parentState$ = new Subject<string>();
|
||||||
memoryStorage = new FakeStorageService();
|
memoryStorage = new FakeStorageService();
|
||||||
|
|
||||||
background = new BackgroundDerivedState(parentState$, deriveDefinition, memoryStorage, {});
|
background = new BackgroundDerivedState(
|
||||||
foreground = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone);
|
parentState$,
|
||||||
|
deriveDefinition,
|
||||||
|
memoryStorage,
|
||||||
|
portName,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
foreground = new ForegroundDerivedState(deriveDefinition, memoryStorage, portName, ngZone);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -65,7 +72,12 @@ describe("foreground background derived state interactions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize a late-connected foreground", async () => {
|
it("should initialize a late-connected foreground", async () => {
|
||||||
const newForeground = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone);
|
const newForeground = new ForegroundDerivedState(
|
||||||
|
deriveDefinition,
|
||||||
|
memoryStorage,
|
||||||
|
portName,
|
||||||
|
ngZone,
|
||||||
|
);
|
||||||
const backgroundEmissions = trackEmissions(background.state$);
|
const backgroundEmissions = trackEmissions(background.state$);
|
||||||
parentState$.next(initialParent);
|
parentState$.next(initialParent);
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
@ -82,8 +94,6 @@ describe("foreground background derived state interactions", () => {
|
|||||||
const dateString = "2020-12-12";
|
const dateString = "2020-12-12";
|
||||||
const emissions = trackEmissions(background.state$);
|
const emissions = trackEmissions(background.state$);
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
await foreground.forceValue(new Date(dateString));
|
await foreground.forceValue(new Date(dateString));
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
@ -99,9 +109,7 @@ describe("foreground background derived state interactions", () => {
|
|||||||
|
|
||||||
expect(foreground["port"]).toBeDefined();
|
expect(foreground["port"]).toBeDefined();
|
||||||
const newDate = new Date();
|
const newDate = new Date();
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await foreground.forceValue(newDate);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
foreground.forceValue(newDate);
|
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
expect(connectMock.mock.calls.length).toBe(initialConnectCalls);
|
expect(connectMock.mock.calls.length).toBe(initialConnectCalls);
|
||||||
@ -114,9 +122,7 @@ describe("foreground background derived state interactions", () => {
|
|||||||
|
|
||||||
expect(foreground["port"]).toBeUndefined();
|
expect(foreground["port"]).toBeUndefined();
|
||||||
const newDate = new Date();
|
const newDate = new Date();
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await foreground.forceValue(newDate);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
foreground.forceValue(newDate);
|
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
expect(connectMock.mock.calls.length).toBe(initialConnectCalls + 1);
|
expect(connectMock.mock.calls.length).toBe(initialConnectCalls + 1);
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
|
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client
|
// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client
|
||||||
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
||||||
@ -14,16 +15,18 @@ import { ForegroundDerivedState } from "./foreground-derived-state";
|
|||||||
|
|
||||||
export class ForegroundDerivedStateProvider extends DefaultDerivedStateProvider {
|
export class ForegroundDerivedStateProvider extends DefaultDerivedStateProvider {
|
||||||
constructor(
|
constructor(
|
||||||
memoryStorage: AbstractStorageService & ObservableStorageService,
|
storageServiceProvider: StorageServiceProvider,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
) {
|
) {
|
||||||
super(memoryStorage);
|
super(storageServiceProvider);
|
||||||
}
|
}
|
||||||
override buildDerivedState<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
override buildDerivedState<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
||||||
_parentState$: Observable<TFrom>,
|
_parentState$: Observable<TFrom>,
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
_dependencies: TDeps,
|
_dependencies: TDeps,
|
||||||
|
storageLocation: [string, AbstractStorageService & ObservableStorageService],
|
||||||
): DerivedState<TTo> {
|
): DerivedState<TTo> {
|
||||||
return new ForegroundDerivedState(deriveDefinition, this.memoryStorage, this.ngZone);
|
const [cacheKey, storageService] = storageLocation;
|
||||||
|
return new ForegroundDerivedState(deriveDefinition, storageService, cacheKey, this.ngZone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,14 @@ jest.mock("../browser/run-inside-angular.operator", () => {
|
|||||||
describe("ForegroundDerivedState", () => {
|
describe("ForegroundDerivedState", () => {
|
||||||
let sut: ForegroundDerivedState<Date>;
|
let sut: ForegroundDerivedState<Date>;
|
||||||
let memoryStorage: FakeStorageService;
|
let memoryStorage: FakeStorageService;
|
||||||
|
const portName = "testPort";
|
||||||
const ngZone = mock<NgZone>();
|
const ngZone = mock<NgZone>();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
memoryStorage = new FakeStorageService();
|
memoryStorage = new FakeStorageService();
|
||||||
memoryStorage.internalUpdateValuesRequireDeserialization(true);
|
memoryStorage.internalUpdateValuesRequireDeserialization(true);
|
||||||
mockPorts();
|
mockPorts();
|
||||||
sut = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone);
|
sut = new ForegroundDerivedState(deriveDefinition, memoryStorage, portName, ngZone);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -35,6 +35,7 @@ export class ForegroundDerivedState<TTo> implements DerivedState<TTo> {
|
|||||||
constructor(
|
constructor(
|
||||||
private deriveDefinition: DeriveDefinition<unknown, TTo, DerivedStateDependencies>,
|
private deriveDefinition: DeriveDefinition<unknown, TTo, DerivedStateDependencies>,
|
||||||
private memoryStorage: AbstractStorageService & ObservableStorageService,
|
private memoryStorage: AbstractStorageService & ObservableStorageService,
|
||||||
|
private portName: string,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
) {
|
) {
|
||||||
this.storageKey = deriveDefinition.storageKey;
|
this.storageKey = deriveDefinition.storageKey;
|
||||||
@ -88,7 +89,7 @@ export class ForegroundDerivedState<TTo> implements DerivedState<TTo> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.port = chrome.runtime.connect({ name: this.deriveDefinition.buildCacheKey() });
|
this.port = chrome.runtime.connect({ name: this.portName });
|
||||||
|
|
||||||
this.backgroundResponses$ = fromChromeEvent(this.port.onMessage).pipe(
|
this.backgroundResponses$ = fromChromeEvent(this.port.onMessage).pipe(
|
||||||
map(([message]) => message as DerivedStateMessage),
|
map(([message]) => message as DerivedStateMessage),
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
AbstractStorageService,
|
||||||
|
ObservableStorageService,
|
||||||
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import {
|
||||||
|
PossibleLocation,
|
||||||
|
StorageServiceProvider,
|
||||||
|
} from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
|
// eslint-disable-next-line import/no-restricted-paths
|
||||||
|
import { ClientLocations } from "@bitwarden/common/platform/state/state-definition";
|
||||||
|
|
||||||
|
export class BrowserStorageServiceProvider extends StorageServiceProvider {
|
||||||
|
constructor(
|
||||||
|
diskStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
|
limitedMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
|
private largeObjectMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
|
) {
|
||||||
|
super(diskStorageService, limitedMemoryStorageService);
|
||||||
|
}
|
||||||
|
|
||||||
|
override get(
|
||||||
|
defaultLocation: PossibleLocation,
|
||||||
|
overrides: Partial<ClientLocations>,
|
||||||
|
): [location: PossibleLocation, service: AbstractStorageService & ObservableStorageService] {
|
||||||
|
const location = overrides["browser"] ?? defaultLocation;
|
||||||
|
switch (location) {
|
||||||
|
case "memory-large-object":
|
||||||
|
return ["memory-large-object", this.largeObjectMemoryStorageService];
|
||||||
|
default:
|
||||||
|
// Pass in computed location to super because they could have
|
||||||
|
// override default "disk" with web "memory".
|
||||||
|
return super.get(location, overrides);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -71,6 +71,7 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta
|
|||||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||||
import {
|
import {
|
||||||
DerivedStateProvider,
|
DerivedStateProvider,
|
||||||
@ -108,6 +109,7 @@ import { DefaultBrowserStateService } from "../../platform/services/default-brow
|
|||||||
import I18nService from "../../platform/services/i18n.service";
|
import I18nService from "../../platform/services/i18n.service";
|
||||||
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
||||||
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
||||||
|
import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider";
|
||||||
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
||||||
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
|
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
|
||||||
import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service";
|
import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service";
|
||||||
@ -120,6 +122,10 @@ import { InitService } from "./init.service";
|
|||||||
import { PopupCloseWarningService } from "./popup-close-warning.service";
|
import { PopupCloseWarningService } from "./popup-close-warning.service";
|
||||||
import { PopupSearchService } from "./popup-search.service";
|
import { PopupSearchService } from "./popup-search.service";
|
||||||
|
|
||||||
|
const OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE = new SafeInjectionToken<
|
||||||
|
AbstractStorageService & ObservableStorageService
|
||||||
|
>("OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE");
|
||||||
|
|
||||||
const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired();
|
const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired();
|
||||||
const isPrivateMode = BrowserPopupUtils.inPrivateMode();
|
const isPrivateMode = BrowserPopupUtils.inPrivateMode();
|
||||||
const mainBackground: MainBackground = needsBackgroundInit
|
const mainBackground: MainBackground = needsBackgroundInit
|
||||||
@ -380,6 +386,21 @@ const safeProviders: SafeProvider[] = [
|
|||||||
},
|
},
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
||||||
|
useFactory: (
|
||||||
|
regularMemoryStorageService: AbstractMemoryStorageService & ObservableStorageService,
|
||||||
|
) => {
|
||||||
|
if (BrowserApi.isManifestVersion(2)) {
|
||||||
|
return regularMemoryStorageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBgService<AbstractStorageService & ObservableStorageService>(
|
||||||
|
"largeObjectMemoryStorageForStateProviders",
|
||||||
|
)();
|
||||||
|
},
|
||||||
|
deps: [OBSERVABLE_MEMORY_STORAGE],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: OBSERVABLE_DISK_STORAGE,
|
provide: OBSERVABLE_DISK_STORAGE,
|
||||||
useExisting: AbstractStorageService,
|
useExisting: AbstractStorageService,
|
||||||
@ -466,7 +487,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DerivedStateProvider,
|
provide: DerivedStateProvider,
|
||||||
useClass: ForegroundDerivedStateProvider,
|
useClass: ForegroundDerivedStateProvider,
|
||||||
deps: [OBSERVABLE_MEMORY_STORAGE, NgZone],
|
deps: [StorageServiceProvider, NgZone],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: AutofillSettingsServiceAbstraction,
|
provide: AutofillSettingsServiceAbstraction,
|
||||||
@ -542,6 +563,15 @@ const safeProviders: SafeProvider[] = [
|
|||||||
},
|
},
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: StorageServiceProvider,
|
||||||
|
useClass: BrowserStorageServiceProvider,
|
||||||
|
deps: [
|
||||||
|
OBSERVABLE_DISK_STORAGE,
|
||||||
|
OBSERVABLE_MEMORY_STORAGE,
|
||||||
|
OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
||||||
|
],
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -121,55 +121,55 @@
|
|||||||
<value>Bitwarden Passwort-Manager</value>
|
<value>Bitwarden Passwort-Manager</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Summary" xml:space="preserve">
|
<data name="Summary" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<value>Zu Hause, am Arbeitsplatz oder unterwegs schützt Bitwarden einfach alle deine Passwörter, Passkeys und vertraulichen Informationen.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Description" xml:space="preserve">
|
<data name="Description" xml:space="preserve">
|
||||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
<value>Ausgezeichnet als bester Passwortmanager von PCMag, WIRED, The Verge, CNET, G2 und vielen anderen!
|
||||||
|
|
||||||
SECURE YOUR DIGITAL LIFE
|
SCHÜTZE DEIN DIGITALES LEBEN
|
||||||
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.
|
Sicher dein digitales Leben und schütze dich vor Passwortdiebstählen, indem du individuelle, sichere Passwörter für jedes Konto erstellest und speicherst. Verwalte alles in einem Ende-zu-Ende verschlüsselten Passwort-Tresor, auf den nur du Zugriff hast.
|
||||||
|
|
||||||
ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE
|
ZUGRIFF AUF DEINE DATEN, ÜBERALL, JEDERZEIT UND AUF JEDEM GERÄT
|
||||||
Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions.
|
Verwalte, speichere, sichere und teile einfach eine unbegrenzte Anzahl von Passwörtern auf einer unbegrenzten Anzahl von Geräten ohne Einschränkungen.
|
||||||
|
|
||||||
EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE
|
JEDER SOLLTE DIE MÖGLICHKEIT HABEN, ONLINE GESCHÜTZT ZU BLEIBEN
|
||||||
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.
|
Verwende Bitwarden kostenlos, ohne Werbung oder Datenverkauf. Bitwarden glaubt, dass jeder die Möglichkeit haben sollte, online geschützt zu bleiben. Premium-Abos bieten Zugang zu erweiterten Funktionen.
|
||||||
|
|
||||||
EMPOWER YOUR TEAMS WITH BITWARDEN
|
STÄRKE DEINE TEAMS MIT 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.
|
Tarife für Teams und Enterprise enthalten professionelle Business-Funktionen. Einige Beispiele sind SSO-Integration, Selbst-Hosting, Directory-Integration und SCIM-Bereitstellung, globale Richtlinien, API-Zugang, Ereignisprotokolle und mehr.
|
||||||
|
|
||||||
Use Bitwarden to secure your workforce and share sensitive information with colleagues.
|
Nutze Bitwarden, um deine Mitarbeiter abzusichern und sensible Informationen mit Kollegen zu teilen.
|
||||||
|
|
||||||
|
|
||||||
More reasons to choose Bitwarden:
|
Weitere Gründe, Bitwarden zu wählen:
|
||||||
|
|
||||||
World-Class Encryption
|
Weltklasse-Verschlüsselung
|
||||||
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.
|
Passwörter werden mit fortschrittlicher Ende-zu-Ende-Verschlüsselung (AES-256 bit, salted hashtag und PBKDF2 SHA-256) geschützt, damit deine Daten sicher und geheim bleiben.
|
||||||
|
|
||||||
3rd-party Audits
|
3rd-Party-Prüfungen
|
||||||
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.
|
Bitwarden führt regelmäßig umfassende Sicherheitsprüfungen durch Dritte von namhaften Sicherheitsfirmen durch. Diese jährlichen Prüfungen umfassen Quellcode-Bewertungen und Penetration-Tests für Bitwarden-IPs, Server und Webanwendungen.
|
||||||
|
|
||||||
Advanced 2FA
|
Erweiterte 2FA
|
||||||
Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey.
|
Schütze deine Zugangsdaten mit einem Authentifikator eines Drittanbieters, per E-Mail verschickten Codes oder FIDO2 WebAuthn-Zugangsadaten wie einem Hardware-Sicherheitsschlüssel oder Passkey.
|
||||||
|
|
||||||
Bitwarden Send
|
Bitwarden Send
|
||||||
Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure.
|
Übertrage Daten direkt an andere, während die Ende-zu-Ende-Verschlüsselung beibehalten wird und die Verbreitung begrenzt werden kann.
|
||||||
|
|
||||||
Built-in Generator
|
Eingebauter Generator
|
||||||
Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy.
|
Erstelle lange, komplexe und eindeutige Passwörter und eindeutige Benutzernamen für jede Website, die du besuchst. Integriere E-Mail-Alias-Anbieter für zusätzlichen Datenschutz.
|
||||||
|
|
||||||
Global Translations
|
Globale Übersetzungen
|
||||||
Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin.
|
Es gibt Bitwarden-Übersetzungen für mehr als 60 Sprachen, die von der weltweiten Community über Crowdin übersetzt werden.
|
||||||
|
|
||||||
Cross-Platform Applications
|
Plattformübergreifende Anwendungen
|
||||||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more.
|
Schütze und teile sensible Daten in deinem Bitwarden Tresor von jedem Browser, mobilen Gerät oder Desktop-Betriebssystem und mehr.
|
||||||
|
|
||||||
Bitwarden secures more than just passwords
|
Bitwarden schützt mehr als nur Passwörter
|
||||||
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!
|
Ende-zu-Ende verschlüsselte Zugangsverwaltungs-Lösungen von Bitwarden ermöglicht es Organisationen, alles zu sichern, einschließlich Entwicklergeheimnissen und Passkeys. Besuche Bitwarden.com, um mehr über den Bitwarden Secrets Manager und Bitwarden Passwordless.dev zu erfahren!
|
||||||
</value>
|
</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AssetTitle" xml:space="preserve">
|
<data name="AssetTitle" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<value>Zu Hause, am Arbeitsplatz oder unterwegs schützt Bitwarden einfach alle deine Passwörter, Passkeys und vertraulichen Informationen.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScreenshotSync" xml:space="preserve">
|
<data name="ScreenshotSync" xml:space="preserve">
|
||||||
<value>Synchronisiere und greife auf deinen Tresor von unterschiedlichen Geräten aus zu</value>
|
<value>Synchronisiere und greife auf deinen Tresor von unterschiedlichen Geräten aus zu</value>
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
<value>Bitwarden Password Manager</value>
|
<value>Bitwarden Password Manager</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Summary" xml:space="preserve">
|
<data name="Summary" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<value>집에서도, 직장에서도, 이동 중에도 Bitwarden은 비밀번호, 패스키, 민감 정보를 쉽게 보호합니다.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Description" xml:space="preserve">
|
<data name="Description" xml:space="preserve">
|
||||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
||||||
@ -169,7 +169,7 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga
|
|||||||
</value>
|
</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AssetTitle" xml:space="preserve">
|
<data name="AssetTitle" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<value>집에서도, 직장에서도, 이동 중에도 Bitwarden은 비밀번호, 패스키, 민감 정보를 쉽게 보호합니다.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScreenshotSync" xml:space="preserve">
|
<data name="ScreenshotSync" xml:space="preserve">
|
||||||
<value>여러 기기에서 보관함에 접근하고 동기화할 수 있습니다.</value>
|
<value>여러 기기에서 보관함에 접근하고 동기화할 수 있습니다.</value>
|
||||||
|
@ -118,58 +118,58 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Name" xml:space="preserve">
|
<data name="Name" xml:space="preserve">
|
||||||
<value>Bitwarden Password Manager</value>
|
<value>Menedżer Haseł Bitwarden</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Summary" xml:space="preserve">
|
<data name="Summary" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<value>W domu, w pracy, lub w ruchu, Bitwarden z łatwością zabezpiecza wszystkie Twoje hasła, passkeys i poufne informacje.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Description" xml:space="preserve">
|
<data name="Description" xml:space="preserve">
|
||||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
<value>Uznany za najlepszego menedżera haseł przez PCMag, WIRED, The Verge, CNET, G2 i wielu innych!
|
||||||
|
|
||||||
SECURE YOUR DIGITAL LIFE
|
ZABEZPIECZ SWOJE CYFROWE ŻYCIE
|
||||||
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.
|
Zabezpiecz swoje cyfrowe życie i chroń przed naruszeniami danych, generując i zapisując unikalne, silne hasła do każdego konta. Przechowuj wszystko w zaszyfrowanym end-to-end magazynie haseł, do którego tylko Ty masz dostęp.
|
||||||
|
|
||||||
ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE
|
DOSTĘP DO SWOICH DANYCH W KAŻDYM MIEJSCU, W DOWOLNYM CZASIE, NA KAŻDYM URZĄDZENIU
|
||||||
Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions.
|
Z łatwością zarządzaj, przechowuj, zabezpieczaj i udostępniaj nieograniczoną liczbę haseł na nieograniczonej liczbie urządzeń.
|
||||||
|
|
||||||
EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE
|
KAŻDY POWINIEN POSIADAĆ NARZĘDZIA ABY ZACHOWAĆ BEZPIECZEŃSTWO W INTERNECIE
|
||||||
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.
|
Korzystaj z Bitwarden za darmo, bez reklam i sprzedawania Twoich danych. Bitwarden wierzy, że każdy powinien mieć możliwość zachowania bezpieczeństwa w Internecie. Plany premium oferują dostęp do zaawansowanych funkcji.
|
||||||
|
|
||||||
EMPOWER YOUR TEAMS WITH BITWARDEN
|
WZMOCNIJ SWOJE ZESPOŁY DZIĘKI 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.
|
Plany dla Zespołów i Enterprise oferują profesjonalne funkcje biznesowe. Na przykład obejmują integrację z SSO, własny hosting, integrację katalogów i udostępnianie SCIM, zasady globalne, dostęp do API, dzienniki zdarzeń i inne.
|
||||||
|
|
||||||
Use Bitwarden to secure your workforce and share sensitive information with colleagues.
|
Użyj Bitwarden, aby zabezpieczyć swoich pracowników i udostępniać poufne informacje współpracownikom.
|
||||||
|
|
||||||
|
|
||||||
More reasons to choose Bitwarden:
|
Więcej powodów, aby wybrać Bitwarden:
|
||||||
|
|
||||||
World-Class Encryption
|
Szyfrowanie na światowym poziomie
|
||||||
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.
|
Hasła są chronione za pomocą zaawansowanego, kompleksowego szyfrowania (AES-256-bitowy, solony hashtag i PBKDF2 SHA-256), dzięki czemu Twoje dane pozostają bezpieczne i prywatne.
|
||||||
|
|
||||||
3rd-party Audits
|
Audyty stron trzecich
|
||||||
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.
|
Bitwarden regularnie przeprowadza kompleksowe audyty bezpieczeństwa stron trzecich we współpracy ze znanymi firmami security. Te coroczne audyty obejmują ocenę kodu źródłowego i testy penetracyjne adresów IP Bitwarden, serwerów i aplikacji internetowych.
|
||||||
|
|
||||||
Advanced 2FA
|
Zaawansowane 2FA
|
||||||
Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey.
|
Zabezpiecz swój login za pomocą zewnętrznego narzędzia uwierzytelniającego, kodów przesłanych pocztą elektroniczną lub poświadczeń FIDO2 WebAuthn, takich jak sprzętowy klucz bezpieczeństwa lub hasło.
|
||||||
|
|
||||||
Bitwarden Send
|
Bitwarden Wyślij
|
||||||
Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure.
|
Przesyłaj dane bezpośrednio do innych, zachowując kompleksowe szyfrowane bezpieczeństwo i ograniczając ryzyko.
|
||||||
|
|
||||||
Built-in Generator
|
Wbudowany generator
|
||||||
Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy.
|
Twórz długie, złożone i różne hasła oraz unikalne nazwy użytkowników dla każdej odwiedzanej witryny. Zintegruj się z dostawcami aliasów e-mail, aby uzyskać dodatkową prywatność.
|
||||||
|
|
||||||
Global Translations
|
Tłumaczenia globalne
|
||||||
Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin.
|
Istnieją tłumaczenia Bitwarden na ponad 60 języków, tłumaczone przez globalną społeczność za pośrednictwem Crowdin.
|
||||||
|
|
||||||
Cross-Platform Applications
|
Aplikacje wieloplatformowe
|
||||||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more.
|
Zabezpiecz i udostępniaj poufne dane w swoim Sejfie Bitwarden z dowolnej przeglądarki, urządzenia mobilnego lub systemu operacyjnego na komputerze stacjonarnym i nie tylko.
|
||||||
|
|
||||||
Bitwarden secures more than just passwords
|
Bitwarden zabezpiecza nie tylko hasła
|
||||||
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!
|
Rozwiązania do zarządzania danymi zaszyfrownaymi end-to-end od firmy Bitwarden umożliwiają organizacjom zabezpieczanie wszystkiego, w tym tajemnic programistów i kluczy dostępu. Odwiedź Bitwarden.com, aby dowiedzieć się więcej o Mendżerze Sekretów Bitwarden i Bitwarden Passwordless.dev!
|
||||||
</value>
|
</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AssetTitle" xml:space="preserve">
|
<data name="AssetTitle" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<value>W domu, w pracy, lub w ruchu, Bitwarden z łatwością zabezpiecza wszystkie Twoje hasła, passkeys i poufne informacje.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScreenshotSync" xml:space="preserve">
|
<data name="ScreenshotSync" xml:space="preserve">
|
||||||
<value>Synchronizacja i dostęp do sejfu z różnych urządzeń</value>
|
<value>Synchronizacja i dostęp do sejfu z różnych urządzeń</value>
|
||||||
|
@ -118,10 +118,10 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Name" xml:space="preserve">
|
<data name="Name" xml:space="preserve">
|
||||||
<value>Bitwarden Password Manager</value>
|
<value>Gerenciador de Senhas Bitwarden</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Summary" xml:space="preserve">
|
<data name="Summary" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<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>
|
||||||
<data name="Description" xml:space="preserve">
|
<data name="Description" xml:space="preserve">
|
||||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
||||||
@ -169,7 +169,7 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga
|
|||||||
</value>
|
</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AssetTitle" xml:space="preserve">
|
<data name="AssetTitle" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<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>
|
||||||
<data name="ScreenshotSync" xml:space="preserve">
|
<data name="ScreenshotSync" xml:space="preserve">
|
||||||
<value>Sincronize e acesse o seu cofre através de múltiplos dispositivos</value>
|
<value>Sincronize e acesse o seu cofre através de múltiplos dispositivos</value>
|
||||||
|
@ -118,58 +118,56 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="Name" xml:space="preserve">
|
<data name="Name" xml:space="preserve">
|
||||||
<value>Bitwarden Password Manager</value>
|
<value>Bitwarden 密码管理器</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Summary" xml:space="preserve">
|
<data name="Summary" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<value>无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Description" xml:space="preserve">
|
<data name="Description" xml:space="preserve">
|
||||||
<value>Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more!
|
<value>被 PCMag、WIRED、The Verge、CNET、G2 等评为最佳密码管理器!
|
||||||
|
|
||||||
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.
|
通过为每个账户生成并保存独特而强大的密码,保护您的数字生活并防范数据泄露。所有内容保存在只有您可以访问的端对端加密的密码库中。
|
||||||
|
|
||||||
ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE
|
随时随地在任何设备上访问您的数据
|
||||||
Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions.
|
不受任何限制跨无限数量的设备轻松管理、存储、保护和分享不限数量的密码。
|
||||||
|
|
||||||
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.
|
使用 Bitwarden 是免费的,没有广告,不会出售数据。Bitwarden 相信每个人都应该拥有保持在线安全的能力。高级计划提供了堆高级功能的访问。
|
||||||
|
|
||||||
EMPOWER YOUR TEAMS WITH BITWARDEN
|
通过 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.
|
团队和企业计划具有专业的商业功能。例如 SSO 集成、自托管、目录集成和 SCIM 配置、全局策略、API 访问、事件日志等。
|
||||||
|
|
||||||
Use Bitwarden to secure your workforce and share sensitive information with colleagues.
|
使用 Bitwarden 保护您的团队,并与同事共享敏感信息。
|
||||||
|
|
||||||
|
选择 Bitwarden 的更多理由:
|
||||||
|
|
||||||
More reasons to choose Bitwarden:
|
世界级加密
|
||||||
|
密码受到先进的端对端加密(AES-256 位、加盐哈希标签和 PBKDF2 SHA-256)保护,使您的数据保持安全和私密。
|
||||||
|
|
||||||
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.
|
Bitwarden 定期与知名的安全公司进行全面的第三方安全审计。这些年度审核包括对 Bitwarden IP、服务器和 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.
|
使用第三方身份验证器、通过电子邮件发送代码或 FIDO2 WebAuthn 凭据(如硬件安全钥匙或通行密钥)保护您的登录。
|
||||||
|
|
||||||
Advanced 2FA
|
|
||||||
Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey.
|
|
||||||
|
|
||||||
Bitwarden Send
|
Bitwarden Send
|
||||||
Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure.
|
直接传输数据给他人,同时保持端对端加密的安全性并防止曝露。
|
||||||
|
|
||||||
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.
|
为您访问的每个网站创建长、复杂且独特的密码和用户名。与电子邮件别名提供商集成,增加隐私保护。
|
||||||
|
|
||||||
Global Translations
|
全球翻译
|
||||||
Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin.
|
Bitwarden 的翻译涵盖 60 多种语言,由全球社区通过 Crowdin 翻译。
|
||||||
|
|
||||||
Cross-Platform Applications
|
跨平台应用程序
|
||||||
Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more.
|
从任何浏览器、移动设备或桌面操作系统中安全地访问和共享 Bitwarden 密码库中的敏感数据。
|
||||||
|
|
||||||
Bitwarden secures more than just passwords
|
Bitwarden 保护的不仅仅是密码
|
||||||
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 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 了解更多关于Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev 的信息!</value>
|
||||||
</value>
|
|
||||||
</data>
|
</data>
|
||||||
<data name="AssetTitle" xml:space="preserve">
|
<data name="AssetTitle" xml:space="preserve">
|
||||||
<value>At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information.</value>
|
<value>无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScreenshotSync" xml:space="preserve">
|
<data name="ScreenshotSync" xml:space="preserve">
|
||||||
<value>从多台设备同步和访问密码库</value>
|
<value>从多台设备同步和访问密码库</value>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/cli",
|
"name": "@bitwarden/cli",
|
||||||
"description": "A secure and free password manager for all of your devices.",
|
"description": "A secure and free password manager for all of your devices.",
|
||||||
"version": "2024.3.1",
|
"version": "2024.4.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitwarden",
|
"bitwarden",
|
||||||
"password",
|
"password",
|
||||||
|
@ -309,9 +309,7 @@ export class Main {
|
|||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.derivedStateProvider = new DefaultDerivedStateProvider(
|
this.derivedStateProvider = new DefaultDerivedStateProvider(storageServiceProvider);
|
||||||
this.memoryStorageForStateProviders,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.stateProvider = new DefaultStateProvider(
|
this.stateProvider = new DefaultStateProvider(
|
||||||
this.activeUserStateProvider,
|
this.activeUserStateProvider,
|
||||||
@ -633,7 +631,6 @@ export class Main {
|
|||||||
this.avatarService,
|
this.avatarService,
|
||||||
async (expired: boolean) => await this.logout(),
|
async (expired: boolean) => await this.logout(),
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
this.tokenService,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.totpService = new TotpService(this.cryptoFunctionService, this.logService);
|
this.totpService = new TotpService(this.cryptoFunctionService, this.logService);
|
||||||
|
@ -228,7 +228,8 @@
|
|||||||
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
||||||
},
|
},
|
||||||
"snap": {
|
"snap": {
|
||||||
"summary": "After installation enable required `password-manager-service` by running `sudo snap connect bitwarden:password-manager-service`.",
|
"summary": "Bitwarden is a secure and free password manager for all of your devices.",
|
||||||
|
"description": "**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.",
|
||||||
"autoStart": true,
|
"autoStart": true,
|
||||||
"base": "core22",
|
"base": "core22",
|
||||||
"confinement": "strict",
|
"confinement": "strict",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@bitwarden/desktop",
|
"name": "@bitwarden/desktop",
|
||||||
"description": "A secure and free password manager for all of your devices.",
|
"description": "A secure and free password manager for all of your devices.",
|
||||||
"version": "2024.4.2",
|
"version": "2024.4.3",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitwarden",
|
"bitwarden",
|
||||||
"password",
|
"password",
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "تنسيقات مشتركة",
|
"message": "تنسيقات مشتركة",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Ortaq formatlar",
|
"message": "Ortaq formatlar",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Problemlərin aradan qaldırılması"
|
"message": "Problemlərin aradan qaldırılması"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Често използвани формати",
|
"message": "Често използвани формати",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Отстраняване на проблеми"
|
"message": "Отстраняване на проблеми"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Formats comuns",
|
"message": "Formats comuns",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Resolució de problemes"
|
"message": "Resolució de problemes"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Společné formáty",
|
"message": "Společné formáty",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Řešení problémů"
|
"message": "Řešení problémů"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Almindelige formater",
|
"message": "Almindelige formater",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Fejlfinding"
|
"message": "Fejlfinding"
|
||||||
},
|
},
|
||||||
|
@ -1633,10 +1633,10 @@
|
|||||||
"message": "Browser-Integration wird nicht unterstützt"
|
"message": "Browser-Integration wird nicht unterstützt"
|
||||||
},
|
},
|
||||||
"browserIntegrationErrorTitle": {
|
"browserIntegrationErrorTitle": {
|
||||||
"message": "Error enabling browser integration"
|
"message": "Fehler beim Aktivieren der Browser-Integration"
|
||||||
},
|
},
|
||||||
"browserIntegrationErrorDesc": {
|
"browserIntegrationErrorDesc": {
|
||||||
"message": "An error has occurred while enabling browser integration."
|
"message": "Beim Aktivieren der Browser-Integration ist ein Fehler aufgetreten."
|
||||||
},
|
},
|
||||||
"browserIntegrationMasOnlyDesc": {
|
"browserIntegrationMasOnlyDesc": {
|
||||||
"message": "Leider wird die Browser-Integration derzeit nur in der Mac App Store Version unterstützt."
|
"message": "Leider wird die Browser-Integration derzeit nur in der Mac App Store Version unterstützt."
|
||||||
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Gängigste Formate",
|
"message": "Gängigste Formate",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Problembehandlung"
|
"message": "Problembehandlung"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Κοινές μορφές",
|
"message": "Κοινές μορφές",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Αντιμετώπιση Προβλημάτων"
|
"message": "Αντιμετώπιση Προβλημάτων"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "فرمتهای رایج",
|
"message": "فرمتهای رایج",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Yleiset muodot",
|
"message": "Yleiset muodot",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Vianetsintä"
|
"message": "Vianetsintä"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Formats communs",
|
"message": "Formats communs",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Résolution de problèmes"
|
"message": "Résolution de problèmes"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "תסדירים נפוצים",
|
"message": "תסדירים נפוצים",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Általános formátumok",
|
"message": "Általános formátumok",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Hibaelhárítás"
|
"message": "Hibaelhárítás"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Formati comuni",
|
"message": "Formati comuni",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Successo"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Risoluzione problemi"
|
"message": "Risoluzione problemi"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "一般的な形式",
|
"message": "一般的な形式",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "成功"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "トラブルシューティング"
|
"message": "トラブルシューティング"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Dažni formatai",
|
"message": "Dažni formatai",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Izplatīti veidoli",
|
"message": "Izplatīti veidoli",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Sarežģījumu novēršana"
|
"message": "Sarežģījumu novēršana"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Vanlige formater",
|
"message": "Vanlige formater",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Veelvoorkomende formaten",
|
"message": "Veelvoorkomende formaten",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Probleemoplossing"
|
"message": "Probleemoplossing"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Popularne formaty",
|
"message": "Popularne formaty",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Sukces"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Rozwiązywanie problemów"
|
"message": "Rozwiązywanie problemów"
|
||||||
},
|
},
|
||||||
|
@ -561,10 +561,10 @@
|
|||||||
"message": "A sua nova conta foi criada! Agora você pode iniciar a sessão."
|
"message": "A sua nova conta foi criada! Agora você pode iniciar a sessão."
|
||||||
},
|
},
|
||||||
"youSuccessfullyLoggedIn": {
|
"youSuccessfullyLoggedIn": {
|
||||||
"message": "You successfully logged in"
|
"message": "Você logou na sua conta com sucesso"
|
||||||
},
|
},
|
||||||
"youMayCloseThisWindow": {
|
"youMayCloseThisWindow": {
|
||||||
"message": "You may close this window"
|
"message": "Você pode fechar esta janela"
|
||||||
},
|
},
|
||||||
"masterPassSent": {
|
"masterPassSent": {
|
||||||
"message": "Enviamos um e-mail com a dica da sua senha mestra."
|
"message": "Enviamos um e-mail com a dica da sua senha mestra."
|
||||||
@ -801,10 +801,10 @@
|
|||||||
"message": "Alterar Senha Mestra"
|
"message": "Alterar Senha Mestra"
|
||||||
},
|
},
|
||||||
"continueToWebApp": {
|
"continueToWebApp": {
|
||||||
"message": "Continue to web app?"
|
"message": "Continuar no aplicativo web?"
|
||||||
},
|
},
|
||||||
"changeMasterPasswordOnWebConfirmation": {
|
"changeMasterPasswordOnWebConfirmation": {
|
||||||
"message": "You can change your master password on the Bitwarden web app."
|
"message": "Você pode alterar a sua senha mestra no aplicativo web Bitwarden."
|
||||||
},
|
},
|
||||||
"fingerprintPhrase": {
|
"fingerprintPhrase": {
|
||||||
"message": "Frase biométrica",
|
"message": "Frase biométrica",
|
||||||
@ -1402,7 +1402,7 @@
|
|||||||
"message": "Código PIN inválido."
|
"message": "Código PIN inválido."
|
||||||
},
|
},
|
||||||
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
"tooManyInvalidPinEntryAttemptsLoggingOut": {
|
||||||
"message": "Too many invalid PIN entry attempts. Logging out."
|
"message": "Muitas tentativas de entrada de PIN inválidas. Desconectando."
|
||||||
},
|
},
|
||||||
"unlockWithWindowsHello": {
|
"unlockWithWindowsHello": {
|
||||||
"message": "Desbloquear com o Windows Hello"
|
"message": "Desbloquear com o Windows Hello"
|
||||||
@ -1557,7 +1557,7 @@
|
|||||||
"description": "Used as a card title description on the set password page to explain why the user is there"
|
"description": "Used as a card title description on the set password page to explain why the user is there"
|
||||||
},
|
},
|
||||||
"verificationRequired": {
|
"verificationRequired": {
|
||||||
"message": "Verification required",
|
"message": "Verificação necessária",
|
||||||
"description": "Default title for the user verification dialog."
|
"description": "Default title for the user verification dialog."
|
||||||
},
|
},
|
||||||
"currentMasterPass": {
|
"currentMasterPass": {
|
||||||
@ -1633,10 +1633,10 @@
|
|||||||
"message": "Integração com o navegador não suportado"
|
"message": "Integração com o navegador não suportado"
|
||||||
},
|
},
|
||||||
"browserIntegrationErrorTitle": {
|
"browserIntegrationErrorTitle": {
|
||||||
"message": "Error enabling browser integration"
|
"message": "Erro ao ativar a integração do navegador"
|
||||||
},
|
},
|
||||||
"browserIntegrationErrorDesc": {
|
"browserIntegrationErrorDesc": {
|
||||||
"message": "An error has occurred while enabling browser integration."
|
"message": "Ocorreu um erro ao permitir a integração do navegador."
|
||||||
},
|
},
|
||||||
"browserIntegrationMasOnlyDesc": {
|
"browserIntegrationMasOnlyDesc": {
|
||||||
"message": "Infelizmente, por ora, a integração do navegador só é suportada na versão da Mac App Store."
|
"message": "Infelizmente, por ora, a integração do navegador só é suportada na versão da Mac App Store."
|
||||||
@ -1654,10 +1654,10 @@
|
|||||||
"message": "Ative uma camada adicional de segurança, exigindo validação de frase de impressão digital ao estabelecer uma ligação entre o computador e o navegador. Quando ativado, isto requer intervenção do usuário e verificação cada vez que uma conexão é estabelecida."
|
"message": "Ative uma camada adicional de segurança, exigindo validação de frase de impressão digital ao estabelecer uma ligação entre o computador e o navegador. Quando ativado, isto requer intervenção do usuário e verificação cada vez que uma conexão é estabelecida."
|
||||||
},
|
},
|
||||||
"enableHardwareAcceleration": {
|
"enableHardwareAcceleration": {
|
||||||
"message": "Use hardware acceleration"
|
"message": "Utilizar aceleração de hardware"
|
||||||
},
|
},
|
||||||
"enableHardwareAccelerationDesc": {
|
"enableHardwareAccelerationDesc": {
|
||||||
"message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required."
|
"message": "Por padrão esta configuração está ativada. Desligar apenas se tiver problemas gráficos. Reiniciar é necessário."
|
||||||
},
|
},
|
||||||
"approve": {
|
"approve": {
|
||||||
"message": "Aprovar"
|
"message": "Aprovar"
|
||||||
@ -1898,40 +1898,40 @@
|
|||||||
"message": "A sua senha mestra não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestra agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora."
|
"message": "A sua senha mestra não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestra agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora."
|
||||||
},
|
},
|
||||||
"tryAgain": {
|
"tryAgain": {
|
||||||
"message": "Try again"
|
"message": "Tentar novamente"
|
||||||
},
|
},
|
||||||
"verificationRequiredForActionSetPinToContinue": {
|
"verificationRequiredForActionSetPinToContinue": {
|
||||||
"message": "Verification required for this action. Set a PIN to continue."
|
"message": "Verificação necessária para esta ação. Defina um PIN para continuar."
|
||||||
},
|
},
|
||||||
"setPin": {
|
"setPin": {
|
||||||
"message": "Set PIN"
|
"message": "Definir PIN"
|
||||||
},
|
},
|
||||||
"verifyWithBiometrics": {
|
"verifyWithBiometrics": {
|
||||||
"message": "Verify with biometrics"
|
"message": "Verificiar com biometria"
|
||||||
},
|
},
|
||||||
"awaitingConfirmation": {
|
"awaitingConfirmation": {
|
||||||
"message": "Awaiting confirmation"
|
"message": "Aguardando confirmação"
|
||||||
},
|
},
|
||||||
"couldNotCompleteBiometrics": {
|
"couldNotCompleteBiometrics": {
|
||||||
"message": "Could not complete biometrics."
|
"message": "Não foi possível completar a biometria."
|
||||||
},
|
},
|
||||||
"needADifferentMethod": {
|
"needADifferentMethod": {
|
||||||
"message": "Need a different method?"
|
"message": "Precisa de um método diferente?"
|
||||||
},
|
},
|
||||||
"useMasterPassword": {
|
"useMasterPassword": {
|
||||||
"message": "Use master password"
|
"message": "Usar a senha mestra"
|
||||||
},
|
},
|
||||||
"usePin": {
|
"usePin": {
|
||||||
"message": "Use PIN"
|
"message": "Usar PIN"
|
||||||
},
|
},
|
||||||
"useBiometrics": {
|
"useBiometrics": {
|
||||||
"message": "Use biometrics"
|
"message": "Usar biometria"
|
||||||
},
|
},
|
||||||
"enterVerificationCodeSentToEmail": {
|
"enterVerificationCodeSentToEmail": {
|
||||||
"message": "Enter the verification code that was sent to your email."
|
"message": "Digite o código de verificação que foi enviado para o seu e-mail."
|
||||||
},
|
},
|
||||||
"resendCode": {
|
"resendCode": {
|
||||||
"message": "Resend code"
|
"message": "Reenviar código"
|
||||||
},
|
},
|
||||||
"hours": {
|
"hours": {
|
||||||
"message": "Horas"
|
"message": "Horas"
|
||||||
@ -2541,13 +2541,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"launchDuoAndFollowStepsToFinishLoggingIn": {
|
"launchDuoAndFollowStepsToFinishLoggingIn": {
|
||||||
"message": "Launch Duo and follow the steps to finish logging in."
|
"message": "Inicie o Duo e siga os passos para finalizar o login."
|
||||||
},
|
},
|
||||||
"duoRequiredByOrgForAccount": {
|
"duoRequiredByOrgForAccount": {
|
||||||
"message": "Duo two-step login is required for your account."
|
"message": "A autenticação em duas etapas do Duo é necessária para sua conta."
|
||||||
},
|
},
|
||||||
"launchDuo": {
|
"launchDuo": {
|
||||||
"message": "Launch Duo in Browser"
|
"message": "Iniciar o Duo no navegador"
|
||||||
},
|
},
|
||||||
"importFormatError": {
|
"importFormatError": {
|
||||||
"message": "Os dados não estão formatados corretamente. Por favor, verifique o seu arquivo de importação e tente novamente."
|
"message": "Os dados não estão formatados corretamente. Por favor, verifique o seu arquivo de importação e tente novamente."
|
||||||
@ -2630,13 +2630,13 @@
|
|||||||
"message": "Nome de usuário ou senha incorretos"
|
"message": "Nome de usuário ou senha incorretos"
|
||||||
},
|
},
|
||||||
"incorrectPassword": {
|
"incorrectPassword": {
|
||||||
"message": "Incorrect password"
|
"message": "Senha incorreta"
|
||||||
},
|
},
|
||||||
"incorrectCode": {
|
"incorrectCode": {
|
||||||
"message": "Incorrect code"
|
"message": "Código incorreto"
|
||||||
},
|
},
|
||||||
"incorrectPin": {
|
"incorrectPin": {
|
||||||
"message": "Incorrect PIN"
|
"message": "PIN incorreto"
|
||||||
},
|
},
|
||||||
"multifactorAuthenticationFailed": {
|
"multifactorAuthenticationFailed": {
|
||||||
"message": "Falha na autenticação de múltiplos fatores"
|
"message": "Falha na autenticação de múltiplos fatores"
|
||||||
@ -2697,25 +2697,28 @@
|
|||||||
"message": "Formatos comuns",
|
"message": "Formatos comuns",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Solução de problemas"
|
||||||
},
|
},
|
||||||
"disableHardwareAccelerationRestart": {
|
"disableHardwareAccelerationRestart": {
|
||||||
"message": "Disable hardware acceleration and restart"
|
"message": "Desativar aceleração de hardware e reiniciar"
|
||||||
},
|
},
|
||||||
"enableHardwareAccelerationRestart": {
|
"enableHardwareAccelerationRestart": {
|
||||||
"message": "Enable hardware acceleration and restart"
|
"message": "Ativar aceleração de hardware e reiniciar"
|
||||||
},
|
},
|
||||||
"removePasskey": {
|
"removePasskey": {
|
||||||
"message": "Remove passkey"
|
"message": "Remover senha"
|
||||||
},
|
},
|
||||||
"passkeyRemoved": {
|
"passkeyRemoved": {
|
||||||
"message": "Passkey removed"
|
"message": "Chave de acesso removida"
|
||||||
},
|
},
|
||||||
"errorAssigningTargetCollection": {
|
"errorAssigningTargetCollection": {
|
||||||
"message": "Error assigning target collection."
|
"message": "Erro ao atribuir coleção de destino."
|
||||||
},
|
},
|
||||||
"errorAssigningTargetFolder": {
|
"errorAssigningTargetFolder": {
|
||||||
"message": "Error assigning target folder."
|
"message": "Erro ao atribuir pasta de destino."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Formatos comuns",
|
"message": "Formatos comuns",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Resolução de problemas"
|
"message": "Resolução de problemas"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Основные форматы",
|
"message": "Основные форматы",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Успешно"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Устранение проблем"
|
"message": "Устранение проблем"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Bežné formáty",
|
"message": "Bežné formáty",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Riešenie problémov"
|
"message": "Riešenie problémov"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -801,10 +801,10 @@
|
|||||||
"message": "Промени главну лозинку"
|
"message": "Промени главну лозинку"
|
||||||
},
|
},
|
||||||
"continueToWebApp": {
|
"continueToWebApp": {
|
||||||
"message": "Continue to web app?"
|
"message": "Ићи на веб апликацију?"
|
||||||
},
|
},
|
||||||
"changeMasterPasswordOnWebConfirmation": {
|
"changeMasterPasswordOnWebConfirmation": {
|
||||||
"message": "You can change your master password on the Bitwarden web app."
|
"message": "Можете променити главну лозинку на Bitwarden веб апликацији."
|
||||||
},
|
},
|
||||||
"fingerprintPhrase": {
|
"fingerprintPhrase": {
|
||||||
"message": "Сигурносна Фраза Сефа",
|
"message": "Сигурносна Фраза Сефа",
|
||||||
@ -1633,10 +1633,10 @@
|
|||||||
"message": "Интеграција са претраживачем није подржана"
|
"message": "Интеграција са претраживачем није подржана"
|
||||||
},
|
},
|
||||||
"browserIntegrationErrorTitle": {
|
"browserIntegrationErrorTitle": {
|
||||||
"message": "Error enabling browser integration"
|
"message": "Грешка при омогућавању интеграције прегледача"
|
||||||
},
|
},
|
||||||
"browserIntegrationErrorDesc": {
|
"browserIntegrationErrorDesc": {
|
||||||
"message": "An error has occurred while enabling browser integration."
|
"message": "Дошло је до грешке при омогућавању интеграције прегледача."
|
||||||
},
|
},
|
||||||
"browserIntegrationMasOnlyDesc": {
|
"browserIntegrationMasOnlyDesc": {
|
||||||
"message": "Нажалост, интеграција прегледача за сада је подржана само у верзији Mac App Store."
|
"message": "Нажалост, интеграција прегледача за сада је подржана само у верзији Mac App Store."
|
||||||
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Уобичајени формати",
|
"message": "Уобичајени формати",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Решавање проблема"
|
"message": "Решавање проблема"
|
||||||
},
|
},
|
||||||
@ -2713,9 +2716,9 @@
|
|||||||
"message": "Приступачни кључ је уклоњен"
|
"message": "Приступачни кључ је уклоњен"
|
||||||
},
|
},
|
||||||
"errorAssigningTargetCollection": {
|
"errorAssigningTargetCollection": {
|
||||||
"message": "Error assigning target collection."
|
"message": "Грешка при додељивању циљне колекције."
|
||||||
},
|
},
|
||||||
"errorAssigningTargetFolder": {
|
"errorAssigningTargetFolder": {
|
||||||
"message": "Error assigning target folder."
|
"message": "Грешка при додељивању циљне фасцикле."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Vanliga format",
|
"message": "Vanliga format",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Felsökning"
|
"message": "Felsökning"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Sorun giderme"
|
"message": "Sorun giderme"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Поширені формати",
|
"message": "Поширені формати",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Успішно"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Усунення проблем"
|
"message": "Усунення проблем"
|
||||||
},
|
},
|
||||||
|
@ -2697,6 +2697,9 @@
|
|||||||
"message": "Common formats",
|
"message": "Common formats",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "Troubleshooting"
|
"message": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
@ -801,10 +801,10 @@
|
|||||||
"message": "修改主密码"
|
"message": "修改主密码"
|
||||||
},
|
},
|
||||||
"continueToWebApp": {
|
"continueToWebApp": {
|
||||||
"message": "Continue to web app?"
|
"message": "前往网页 App 吗?"
|
||||||
},
|
},
|
||||||
"changeMasterPasswordOnWebConfirmation": {
|
"changeMasterPasswordOnWebConfirmation": {
|
||||||
"message": "You can change your master password on the Bitwarden web app."
|
"message": "您可以在 Bitwarden 网页应用上更改您的主密码。"
|
||||||
},
|
},
|
||||||
"fingerprintPhrase": {
|
"fingerprintPhrase": {
|
||||||
"message": "指纹短语",
|
"message": "指纹短语",
|
||||||
@ -2697,6 +2697,9 @@
|
|||||||
"message": "常规格式",
|
"message": "常规格式",
|
||||||
"description": "Label indicating the most common import formats"
|
"description": "Label indicating the most common import formats"
|
||||||
},
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Success"
|
||||||
|
},
|
||||||
"troubleshooting": {
|
"troubleshooting": {
|
||||||
"message": "故障排除"
|
"message": "故障排除"
|
||||||
},
|
},
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user