diff --git a/apps/browser/package.json b/apps/browser/package.json index 278a3b6c529..1f54bd64aca 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -2,10 +2,10 @@ "name": "@bitwarden/browser", "version": "2024.5.0", "scripts": { - "build": "webpack", - "build:mv3": "cross-env MANIFEST_VERSION=3 webpack", - "build:watch": "webpack --watch", - "build:watch:mv3": "cross-env MANIFEST_VERSION=3 webpack --watch", + "build": "cross-env MANIFEST_VERSION=3 webpack", + "build:mv2": "webpack", + "build:watch": "cross-env MANIFEST_VERSION=3 webpack --watch", + "build:watch:mv2": "webpack --watch", "build:prod": "cross-env NODE_ENV=production webpack", "build:prod:beta": "cross-env BETA_BUILD=1 NODE_ENV=production webpack", "build:prod:watch": "cross-env NODE_ENV=production webpack --watch", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 6c83d771e9c..42c16140223 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -374,12 +374,21 @@ "other": { "message": "الأخرى" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "أعدنّ طريقة إلغاء القُفْل لتغيير إجراء مهلة المخزن الخاص بك." }, "unlockMethodNeeded": { "message": "إعداد طريقة إلغاء القفل في الإعدادات" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "قيِّم هذه الإضافة" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 18fc6acca8b..6ac5c64ddd2 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden Parol Meneceri", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Bitwarden evdə və ya işdə olarkən bütün parol, keçid açarı və həssas məlumatlarınızı asanlıqla qoruyur", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -374,12 +374,21 @@ "other": { "message": "Digər" }, + "unlockMethods": { + "message": "Kilid açma seçimləri" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Anbar vaxt bitməsi əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun." }, "unlockMethodNeeded": { "message": "Ayarlarda bir kilid açma üsulu qurun" }, + "sessionTimeoutHeader": { + "message": "Seans vaxt bitməsi" + }, + "otherOptions": { + "message": "Digər seçimlər" + }, "rateExtension": { "message": "Uzantını qiymətləndir" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Konsolu" }, + "accountSecurity": { + "message": "Hesab güvənliyi" + }, + "notifications": { + "message": "Bildirişlər" + }, + "appearance": { + "message": "Görünüş" + }, "errorAssigningTargetCollection": { "message": "Hədəf kolleksiyaya təyin etmə xətası." }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 44dc85d2b9e..fb99a0a95d7 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Iншае" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Наладзіць метад разблакіроўкі для змянення дзеяння часу чакання вашага сховішча." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Ацаніць пашырэнне" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index cb12459a44c..609131a66a1 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -3,11 +3,11 @@ "message": "Битуорден (Bitwarden)" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden — управител на пароли", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "У дома, на работа или на път – Битуорден защитава всички Ваши пароли, секретни ключове и лична информация", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -374,12 +374,21 @@ "other": { "message": "Други" }, + "unlockMethods": { + "message": "Настройки за отключване" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Задайте метод за отключване, за да може да промените действието при изтичане на времето за достъп до трезора." }, "unlockMethodNeeded": { "message": "Задайте метод за отключване в Настройките" }, + "sessionTimeoutHeader": { + "message": "Изтичане на времето за сесията" + }, + "otherOptions": { + "message": "Други настройки" + }, "rateExtension": { "message": "Оценяване на разширението" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Административна конзола" }, + "accountSecurity": { + "message": "Защита на регистрацията" + }, + "notifications": { + "message": "Известия" + }, + "appearance": { + "message": "Външен вид" + }, "errorAssigningTargetCollection": { "message": "Грешка при задаването на целева колекция." }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 8156e3c6f19..c0ccd92d7a8 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -374,12 +374,21 @@ "other": { "message": "অন্যান্য" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "এক্সটেনশনটি মূল্যায়ন করুন" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index a7334319b5c..53c5c4ed44e 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index d9cd14102e8..2e282e30ee1 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Altres" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configura un mètode de desbloqueig per canviar l'acció del temps d'espera de la caixa forta." }, "unlockMethodNeeded": { "message": "Configura un mètode de desbloqueig a Configuració" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Valora aquesta extensió" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Consola d'administració" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "S'ha produït un error en assignar la col·lecció de destinació." }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 058378ff170..794e2d995da 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Ostatní" }, + "unlockMethods": { + "message": "Volby odemknutí" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Nastavte metodu odemknutí, abyste změnili časový limit Vašeho trezoru." }, "unlockMethodNeeded": { "message": "Nastavit metodu odemknutí v Nastavení" }, + "sessionTimeoutHeader": { + "message": "Časový limit relace" + }, + "otherOptions": { + "message": "Další volby" + }, "rateExtension": { "message": "Ohodnotit rozšíření" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Konzole správce" }, + "accountSecurity": { + "message": "Zabezpečení účtu" + }, + "notifications": { + "message": "Oznámení" + }, + "appearance": { + "message": "Vzhled" + }, "errorAssigningTargetCollection": { "message": "Chyba při přiřazování cílové kolekce." }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 5ec7b9f483c..d43cb2b25dc 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Gosodiadau eraill" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rhoi eich barn ar yr estyniad" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 4c90522e1e2..57f87d7a8eb 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden Adgangskodehåndtering", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Hjemme, på arbejde eller på farten sikrer Bitwarden nemt alle adgangskoder, adgangskort og sensitive oplysninger", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -374,12 +374,21 @@ "other": { "message": "Andre" }, + "unlockMethods": { + "message": "Oplåsningsmuligheder" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Opsæt en oplåsningsmetode til at ændre bokstimeouthandlingen." }, "unlockMethodNeeded": { "message": "Opsæt en oplåsningsmetode i Indstillinger" }, + "sessionTimeoutHeader": { + "message": "Sessionstimeout" + }, + "otherOptions": { + "message": "Andre innstillinger" + }, "rateExtension": { "message": "Bedøm udvidelsen" }, @@ -2390,7 +2399,7 @@ "message": "Generelt" }, "display": { - "message": "Display" + "message": "Skærm" }, "accountSuccessfullyCreated": { "message": "Konto oprettet!" @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin-konsol" }, + "accountSecurity": { + "message": "Kontosikkerhed" + }, + "notifications": { + "message": "Notifikationer" + }, + "appearance": { + "message": "Udseende" + }, "errorAssigningTargetCollection": { "message": "Fejl ved tildeling af målsamling." }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 35100a77d27..58e0f17053e 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "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 Passwörter, Passkeys und vertrauliche Informationen", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -374,12 +374,21 @@ "other": { "message": "Sonstige" }, + "unlockMethods": { + "message": "Entsperroptionen" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Richte eine Entsperrmethode ein, um deine Aktion bei Timeout-Timeout zu ändern." }, "unlockMethodNeeded": { "message": "Lege eine Entsperrmethode in den Einstellungen fest" }, + "sessionTimeoutHeader": { + "message": "Sitzungs-Timeout" + }, + "otherOptions": { + "message": "Andere Optionen" + }, "rateExtension": { "message": "Erweiterung bewerten" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Administrator-Konsole" }, + "accountSecurity": { + "message": "Kontosicherheit" + }, + "notifications": { + "message": "Benachrichtigungen" + }, + "appearance": { + "message": "Aussehen" + }, "errorAssigningTargetCollection": { "message": "Fehler beim Zuweisen der Ziel-Sammlung." }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index de0cfb3f6ca..60659eeb657 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Άλλες" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Ρυθμίστε μια μέθοδο ξεκλειδώματος για να αλλάξετε την ενέργεια χρονικού ορίου θησαυ/κιου." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Βαθμολογήστε την επέκταση" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 9360524da1d..828cca3c855 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2177,6 +2177,108 @@ "forwardedEmailDesc": { "message": "Generate an email alias with an external forwarding service." }, + "forwarderError": { + "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "description": "Reports an error returned by a forwarding service to the user.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Invalid characters in domain name." + } + } + }, + "forwarderGeneratedBy": { + "message": "Generated by Bitwarden.", + "description": "Displayed with the address on the forwarding service's configuration screen." + }, + "forwarderGeneratedByWithWebsite": { + "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "description": "Displayed with the address on the forwarding service's configuration screen.", + "placeholders": { + "WEBSITE": { + "content": "$1", + "example": "www.example.com" + } + } + }, + "forwaderInvalidToken": { + "message": "Invalid $SERVICENAME$ API token", + "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidTokenWithMessage": { + "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, + "forwarderNoAccountId": { + "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "description": "Displayed when the forwarding service fails to return an account ID.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderNoDomain": { + "message": "Invalid $SERVICENAME$ domain.", + "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderNoUrl": { + "message": "Invalid $SERVICENAME$ url.", + "description": "Displayed when the url of the forwarding service wasn't supplied.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderUnknownError": { + "message": "Unknown $SERVICENAME$ error occurred.", + "description": "Displayed when the forwarding service failed due to an unknown error.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderUnknownForwarder": { + "message": "Unknown forwarder: '$SERVICENAME$'.", + "description": "Displayed when the forwarding service is not supported.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "JustTrust.us" + } + } + }, "hostname": { "message": "Hostname", "description": "Part of a URL." @@ -3037,7 +3139,7 @@ }, "notifications": { "message": "Notifications" - }, + }, "appearance": { "message": "Appearance" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index a2ba76b3a67..bbaf77b714c 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index a95f11ffa1e..9e154437ffc 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 4ffbb147be8..d460a54ac88 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -7,7 +7,7 @@ "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "En casa, el trabajo o el viaje, Bitwarden asegura todas sus contraseñas, claves e información confidencial", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -374,12 +374,21 @@ "other": { "message": "Otros" }, + "unlockMethods": { + "message": "Opciones de desbloqueo" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configura un método de desbloqueo para cambiar tu acción de cierre de la bóveda." }, "unlockMethodNeeded": { "message": "Configure un método de desbloqueo en Configuración" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Otras opciones" + }, "rateExtension": { "message": "Valora la extensión" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Consola de administrador" }, + "accountSecurity": { + "message": "Seguridad de la cuenta" + }, + "notifications": { + "message": "Notificaciones" + }, + "appearance": { + "message": "Apariencia" + }, "errorAssigningTargetCollection": { "message": "Error al asignar la colección de destino." }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 0f5b1647176..40b127f0591 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Muu" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Hoidla ajalõpu tegevuse muutmiseks vali esmalt lahtilukustamise meetod." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Hinda seda laiendust" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 4bc13732015..0d4790b2711 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Bestelakoak" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Baloratu gehigarria" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 8b09cb224fe..6620f33c1e0 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -374,12 +374,21 @@ "other": { "message": "ساير" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "یک روش بازگشایی برای پایان زمان مجاز تنظیم کنید." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "به این افزونه امتیاز دهید" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 95c77e0c09a..7af0c95a1d2 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Muut" }, + "unlockMethods": { + "message": "Avausasetukset" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Muuta holvisi aikakatkaisutoimintoa määrittämällä lukituksen avaustapa." }, "unlockMethodNeeded": { "message": "Määritä avaustapa asetuksista" }, + "sessionTimeoutHeader": { + "message": "Istunnon aikakatkaisu" + }, + "otherOptions": { + "message": "Muut asetukset" + }, "rateExtension": { "message": "Arvioi laajennus" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Hallintapaneelista" }, + "accountSecurity": { + "message": "Tilin suojaus" + }, + "notifications": { + "message": "Ilmoitukset" + }, + "appearance": { + "message": "Ulkoasu" + }, "errorAssigningTargetCollection": { "message": "Virhe määritettäessä kohdekokoelmaa." }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index b536fa9c196..6e52afd7db6 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Iba pa" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Mag-set up ng paraan ng pag-unlock upang baguhin ang iyong pagkilos sa pag-timeout ng vault." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "I-rate ang extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 541541ab548..d53961be291 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Autre" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configurez une méthode de déverrouillage pour changer le délai d'expiration de votre coffre." }, "unlockMethodNeeded": { "message": "Configurer une méthode de déverrouillage dans les Paramètres" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Noter l'extension" }, @@ -3010,7 +3019,7 @@ "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." }, "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": "Remarque : À partir du 16 mai 2024, les éléments d'organisation non assignés ne seront plus visibles dans votre vue Tous les coffres sur les appareils et ne seront maintenant accessibles que via la Console Admin." }, "unassignedItemsBannerCTAPartOne": { "message": "Ajouter ces éléments à une collection depuis la", @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Console Admin" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index d23f0377bcd..0c2ce6114a3 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -3,15 +3,15 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden - Xestor de contrasinais", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Na casa, no traballo ou en ruta, Bitwarden protexe os teus contrasinais, chaves de acceso e datos sensíbeis", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { - "message": "Log in or create a new account to access your secure vault." + "message": "Rexístrate ou crea unha nova conta para acceder á túa caixa forte." }, "createAccount": { "message": "Crea unha conta" @@ -20,7 +20,7 @@ "message": "Iniciar sesión" }, "enterpriseSingleSignOn": { - "message": "Enterprise single sign-on" + "message": "Inicio de sesión único empresarial" }, "cancel": { "message": "Cancelar" @@ -29,7 +29,7 @@ "message": "Pechar" }, "submit": { - "message": "Submit" + "message": "Enviar" }, "emailAddress": { "message": "Enderezo de correo electrónico" @@ -38,142 +38,142 @@ "message": "Contrasinal mestre" }, "masterPassDesc": { - "message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it." + "message": "O contrasinal mestre é a chave que empregas para acceder á túa caixa forte. É moi importante que non esquezas o teu contrasinal mestre. Non hai xeito de recuperala se a esqueces." }, "masterPassHintDesc": { - "message": "A master password hint can help you remember your password if you forget it." + "message": "Unha pista do contrasinal mestre pode axudarte a lembrar o teu contrasinal se o esqueces." }, "reTypeMasterPass": { - "message": "Re-type master password" + "message": "Reescriba o contrasinal mestre" }, "masterPassHint": { - "message": "Master password hint (optional)" + "message": "Pista do contrasinal mestre (opcional)" }, "tab": { - "message": "Tab" + "message": "Separador" }, "vault": { - "message": "Vault" + "message": "Caixa forte" }, "myVault": { - "message": "My vault" + "message": "A miña caixa forte" }, "allVaults": { - "message": "All vaults" + "message": "Todas as caixas fortes" }, "tools": { - "message": "Tools" + "message": "Ferramentas" }, "settings": { - "message": "Settings" + "message": "Axustes" }, "currentTab": { - "message": "Current tab" + "message": "Separador actual" }, "copyPassword": { - "message": "Copy password" + "message": "Copiar contrasinal" }, "copyNote": { - "message": "Copy note" + "message": "Copiar nota" }, "copyUri": { - "message": "Copy URI" + "message": "Copiar URI" }, "copyUsername": { - "message": "Copy username" + "message": "Copiar nome de usuario" }, "copyNumber": { - "message": "Copy number" + "message": "Copiar número" }, "copySecurityCode": { - "message": "Copy security code" + "message": "Copiar código de seguranza" }, "autoFill": { - "message": "Auto-fill" + "message": "Auto-encher" }, "autoFillLogin": { - "message": "Auto-fill login" + "message": "Encher automaticamente inicio de sesión" }, "autoFillCard": { - "message": "Auto-fill card" + "message": "Encher automaticamente tarxeta" }, "autoFillIdentity": { - "message": "Auto-fill identity" + "message": "Encher automaticamente identidade" }, "generatePasswordCopied": { - "message": "Generate password (copied)" + "message": "Xerar contrasinal (copiado)" }, "copyElementIdentifier": { - "message": "Copy custom field name" + "message": "Copiar nome de campo personalizado" }, "noMatchingLogins": { - "message": "No matching logins" + "message": "Sen inicios de sesión coincidentes" }, "noCards": { - "message": "No cards" + "message": "Sen tarxetas" }, "noIdentities": { - "message": "No identities" + "message": "Sen identidades" }, "addLoginMenu": { - "message": "Add login" + "message": "Engadir inicio de sesión" }, "addCardMenu": { - "message": "Add card" + "message": "Engadir tarxeta" }, "addIdentityMenu": { - "message": "Add identity" + "message": "Engadir identidade" }, "unlockVaultMenu": { - "message": "Unlock your vault" + "message": "Desbloquear a súa caixa forte" }, "loginToVaultMenu": { - "message": "Log in to your vault" + "message": "Rexistrarse na súa caixa forte" }, "autoFillInfo": { - "message": "There are no logins available to auto-fill for the current browser tab." + "message": "Non hai inicios de sesión dispoñíbeis para encher automaticamente para o separador actual do navegador." }, "addLogin": { - "message": "Add a login" + "message": "Engadir inicio de sesión" }, "addItem": { - "message": "Add item" + "message": "Engadir elemento" }, "passwordHint": { - "message": "Password hint" + "message": "Pista do contrasinal" }, "enterEmailToGetHint": { - "message": "Enter your account email address to receive your master password hint." + "message": "Introduce a dirección de correo da túa conta para recibir a pista do contrasinal mestre." }, "getMasterPasswordHint": { - "message": "Get master password hint" + "message": "Obter pista do contrasinal mestre" }, "continue": { - "message": "Continue" + "message": "Continuar" }, "sendVerificationCode": { - "message": "Send a verification code to your email" + "message": "Envía un código de verificación ao teu correo" }, "sendCode": { - "message": "Send code" + "message": "Enviar código" }, "codeSent": { - "message": "Code sent" + "message": "Código enviado" }, "verificationCode": { - "message": "Verification code" + "message": "Código de verificación" }, "confirmIdentity": { - "message": "Confirm your identity to continue." + "message": "Confirma a túa identidade para continuar." }, "account": { - "message": "Account" + "message": "Conta" }, "changeMasterPassword": { - "message": "Change master password" + "message": "Cambiar o contrasinal mestre" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Continuar á aplicación web?" }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 0a9d8a8106a..d8b9b1ec9ee 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -374,12 +374,21 @@ "other": { "message": "אחר" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "דירוג הרחבה" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 042ef96ea20..8b08a8a5c21 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -374,12 +374,21 @@ "other": { "message": "अन्य" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the Extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 09388672fff..d00e32b1397 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Ostalo" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Za promjenu vremena isteka trezora, odredi način otključavanja." }, "unlockMethodNeeded": { "message": "Postavi način otključavanja u Postavkama" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Ocijeni proširenje" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 5647f5d97de..65342fa51b0 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Egyéb" }, + "unlockMethods": { + "message": "Feloldási opciók" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Állítsunk be egy feloldási módot a széf időkifutási műveletének módosításához." }, "unlockMethodNeeded": { "message": "Feloldási mód beállítása a Beállításokban" }, + "sessionTimeoutHeader": { + "message": "Munkamenet időkifutás" + }, + "otherOptions": { + "message": "Egyéb opciók" + }, "rateExtension": { "message": "Bővítmény értékelése" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Adminisztrátori konzol" }, + "accountSecurity": { + "message": "Fiókbiztonság" + }, + "notifications": { + "message": "Értesítések" + }, + "appearance": { + "message": "Megjelenés" + }, "errorAssigningTargetCollection": { "message": "Hiba történt a célgyűjtemény hozzárendelése során." }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 37961ba9a3f..d0c317a1773 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Lainnya" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Nilai Ekstensi" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 9420bca6ef9..94e0e479151 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Altro" }, + "unlockMethods": { + "message": "Opzioni di sblocco" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Imposta un metodo di sblocco per modificare l'azione timeout cassaforte." }, "unlockMethodNeeded": { "message": "Imposta un metodo di sblocco in Impostazioni" }, + "sessionTimeoutHeader": { + "message": "Timeout della sessione" + }, + "otherOptions": { + "message": "Altre opzioni" + }, "rateExtension": { "message": "Valuta l'estensione" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Console di amministrazione" }, + "accountSecurity": { + "message": "Sicurezza dell'account" + }, + "notifications": { + "message": "Notifiche" + }, + "appearance": { + "message": "Aspetto" + }, "errorAssigningTargetCollection": { "message": "Errore nell'assegnazione della raccolta di destinazione." }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index d1429a40e06..0bfe8a84b3d 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -374,12 +374,21 @@ "other": { "message": "その他" }, + "unlockMethods": { + "message": "ロック解除オプション" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "保管庫のタイムアウト動作を変更するには、ロック解除方法を設定してください。" }, "unlockMethodNeeded": { "message": "設定でロック解除方法をセットアップ" }, + "sessionTimeoutHeader": { + "message": "セッションタイムアウト" + }, + "otherOptions": { + "message": "その他のオプション" + }, "rateExtension": { "message": "拡張機能の評価" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "管理コンソール" }, + "accountSecurity": { + "message": "アカウントのセキュリティ" + }, + "notifications": { + "message": "通知" + }, + "appearance": { + "message": "外観" + }, "errorAssigningTargetCollection": { "message": "ターゲットコレクションの割り当てに失敗しました。" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 416018cc92c..1ee37ad5de7 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -374,12 +374,21 @@ "other": { "message": "სხვა" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index fec6ab713cc..b45fdef39e8 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index f5e3b8d334f..275723e0cf4 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -374,12 +374,21 @@ "other": { "message": "ಇತರೆ" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "ವಿಸ್ತರಣೆಯನ್ನು ರೇಟ್ ಮಾಡಿ" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 8b34a768333..116c87a3116 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -374,12 +374,21 @@ "other": { "message": "기타" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "잠금 해제 방법을 설정하여 보관함의 시간 초과 동작을 변경하세요." }, "unlockMethodNeeded": { "message": "설정에서 잠금 해제 수단 설정하기" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "확장 프로그램 평가" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 6ffbf522d34..c160ae5788b 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Kita" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Nustatyk atrakinimo būdą, kad pakeistum saugyklos laiko limito veiksmą." }, "unlockMethodNeeded": { "message": "Nustatykite nustatymuose atrakinimo metodą" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Įvertinkite šį plėtinį" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Administratoriaus konsolės" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Klaida priskiriant tikslinę kolekciją." }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index d8e42a1c50d..c7ec65403d9 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Cits" }, + "unlockMethods": { + "message": "Atslēgšanas iespējas" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Jāuzstāda atslēgšanas veids, lai mainītu glabātavas noildzes darbību." }, "unlockMethodNeeded": { "message": "Jāuzstāda atslēgšanas veids iestatījumos" }, + "sessionTimeoutHeader": { + "message": "Sesijas noildze" + }, + "otherOptions": { + "message": "Citas iespējas" + }, "rateExtension": { "message": "Novērtēt paplašinājumu" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "pārvaldības konsolē," }, + "accountSecurity": { + "message": "Konta drošība" + }, + "notifications": { + "message": "Paziņojumi" + }, + "appearance": { + "message": "Izskats" + }, "errorAssigningTargetCollection": { "message": "Kļūda mērķa krājuma piešķiršanā." }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index aa4fea96e39..0d7049b8552 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -374,12 +374,21 @@ "other": { "message": "മറ്റുള്ളവ" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "എക്സ്റ്റൻഷൻ റേറ്റ് ചെയ്യുക " }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 463309789de..f44649d1c30 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -374,12 +374,21 @@ "other": { "message": "इतर" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "विस्तारकाचे मूल्यांकन करा" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index fec6ab713cc..b45fdef39e8 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index ea7b939f636..8d54140b0e6 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Annet" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Gi denne utvidelsen en vurdering" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index fec6ab713cc..b45fdef39e8 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index aa01ef2c67c..0319dea3a41 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Overig" }, + "unlockMethods": { + "message": "Ontgrendelopties" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Stel een ontgrendelingsmethode in om je kluis time-out actie te wijzigen." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Sessietime-out" + }, + "otherOptions": { + "message": "Andere opties" + }, "rateExtension": { "message": "Deze extensie beoordelen" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Accountbeveiliging" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Voorkomen" + }, "errorAssigningTargetCollection": { "message": "Fout bij toewijzen doelverzameling." }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index fec6ab713cc..b45fdef39e8 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index fec6ab713cc..b45fdef39e8 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 83e19315e8d..b34ce5bf669 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Inne" }, + "unlockMethods": { + "message": "Odblokuj Opcje" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Ustaw metodę odblokowania, aby zmienić czas blokowania sejfu." }, "unlockMethodNeeded": { "message": "Ustaw metodę odblokowania w Ustawieniach" }, + "sessionTimeoutHeader": { + "message": "Limit czasu sesji" + }, + "otherOptions": { + "message": "Pozostałe opcje" + }, "rateExtension": { "message": "Oceń rozszerzenie" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Konsola Administracyjna" }, + "accountSecurity": { + "message": "Bezpieczeństwo konta" + }, + "notifications": { + "message": "Powiadomienia" + }, + "appearance": { + "message": "Wygląd" + }, "errorAssigningTargetCollection": { "message": "Wystąpił błąd podczas przypisywania kolekcji." }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 207c8901304..c9ade42eb5d 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Outros" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configure um método de desbloqueio para alterar o tempo limite do cofre." }, "unlockMethodNeeded": { "message": "Configure um método de desbloqueio nas Configurações" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Avaliar a Extensão" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Painel de administração" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Erro ao atribuir coleção de destino." }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 26828b348af..3cb55214d6b 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Outros" }, + "unlockMethods": { + "message": "Opções de desbloqueio" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configure um método de desbloqueio para alterar a ação de tempo limite do seu cofre." }, "unlockMethodNeeded": { "message": "Definir um método de desbloqueio nas Definições" }, + "sessionTimeoutHeader": { + "message": "Tempo limite da sessão" + }, + "otherOptions": { + "message": "Outras opções" + }, "rateExtension": { "message": "Avaliar a extensão" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Consola de administração" }, + "accountSecurity": { + "message": "Segurança da conta" + }, + "notifications": { + "message": "Notificações" + }, + "appearance": { + "message": "Aparência" + }, "errorAssigningTargetCollection": { "message": "Erro ao atribuir a coleção de destino." }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 8bcf1a84303..8f02fd076db 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Altele" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configurați metoda de deblocare care să schimbe acțiunea de expirare a seifului." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Evaluare extensie" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 23d69f44ba0..8f0d211056f 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden - Менеджер паролей", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Дома, на работе или в пути - Bitwarden всегда защитит ваши пароли, passkeys и конфиденциальную информацию", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -374,12 +374,21 @@ "other": { "message": "Прочее" }, + "unlockMethods": { + "message": "Настройки разблокировки" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Настройте способ разблокировки для изменения действия по тайм-ауту хранилища." }, "unlockMethodNeeded": { "message": "Установите способ разблокировки в настройках" }, + "sessionTimeoutHeader": { + "message": "Тайм-аут сессии" + }, + "otherOptions": { + "message": "Прочие настройки" + }, "rateExtension": { "message": "Оценить расширение" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "консоли администратора" }, + "accountSecurity": { + "message": "Безопасность аккаунта" + }, + "notifications": { + "message": "Уведомления" + }, + "appearance": { + "message": "Внешний вид" + }, "errorAssigningTargetCollection": { "message": "Ошибка при назначении целевой коллекции." }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index f225e854aa4..f4f13a4f559 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -374,12 +374,21 @@ "other": { "message": "වෙනත්" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "දිගුව අනුපාතය" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 7bcffc04ac5..db11a308559 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Ostatné" }, + "unlockMethods": { + "message": "Možnosti odomknutia" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Nastavte metódu odomknutia, aby ste zmenili akciu pri vypršaní času trezoru." }, "unlockMethodNeeded": { "message": "Nastavte metódu odomknutia v Nastaveniach" }, + "sessionTimeoutHeader": { + "message": "Časový limit relácie" + }, + "otherOptions": { + "message": "Ďalšie možnosti" + }, "rateExtension": { "message": "Ohodnotiť rozšírenie" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Správcovská konzola" }, + "accountSecurity": { + "message": "Zabezpečenie účtu" + }, + "notifications": { + "message": "Upozornenia" + }, + "appearance": { + "message": "Vzhľad" + }, "errorAssigningTargetCollection": { "message": "Chyba pri priraďovaní cieľovej kolekcie." }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 170ee146f76..b098546de2d 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Drugo" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Da spremenite časovne omejitve trezorja, nastavite metodo odklepanja." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Ocenite to razširitev" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 3827a9deddc..621bd5953f3 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Остало" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Подесите метод откључавања да бисте променили радњу временског ограничења сефа." }, "unlockMethodNeeded": { "message": "Подесите метод откључавања у подешавањима" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Оцени овај додатак" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Администраторска конзола" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Грешка при додељивању циљне колекције." }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 0939ac0280c..96776e70601 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Annat" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Ställ in en upplåsningsmetod i Inställningar" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Betygsätt tillägget" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Utseende" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index fec6ab713cc..b45fdef39e8 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Other" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 009685b14b6..6318d27189e 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -374,12 +374,21 @@ "other": { "message": "อื่น ๆ" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, "unlockMethodNeeded": { "message": "Set up an unlock method in Settings" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Rate the Extension" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 866125dbec1..e91397a6e21 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Diğer" }, + "unlockMethods": { + "message": "Kilit açma seçenekleri" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Kasa zaman aşımı eyleminizi değiştirmek için kilit açma yönteminizi ayarlayın." }, "unlockMethodNeeded": { "message": "Ayarlar'da bir kilit açma yöntemi ayarlayın" }, + "sessionTimeoutHeader": { + "message": "Oturum zaman aşımı" + }, + "otherOptions": { + "message": "Diğer seçenekler" + }, "rateExtension": { "message": "Uzantıyı değerlendirin" }, @@ -3023,10 +3032,19 @@ "adminConsole": { "message": "Yönetici Konsolu" }, + "accountSecurity": { + "message": "Hesap güvenliği" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Hedef koleksiyonu atama hatası." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Hedef klasörü atama hatası." } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 919066188a2..a92a68571e7 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden – менеджер паролів", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "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, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -374,12 +374,21 @@ "other": { "message": "Інше" }, + "unlockMethods": { + "message": "Налаштування розблокування" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Налаштуйте спосіб розблокування, щоб змінити час очікування сховища." }, "unlockMethodNeeded": { "message": "Встановіть спосіб розблокування в налаштуваннях" }, + "sessionTimeoutHeader": { + "message": "Час очікування сеансу" + }, + "otherOptions": { + "message": "Інші налаштування" + }, "rateExtension": { "message": "Оцінити розширення" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "консолі адміністратора," }, + "accountSecurity": { + "message": "Безпека облікового запису" + }, + "notifications": { + "message": "Сповіщення" + }, + "appearance": { + "message": "Вигляд" + }, "errorAssigningTargetCollection": { "message": "Помилка призначення цільової збірки." }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index f7a0d50bd5c..77ce3ea507a 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -374,12 +374,21 @@ "other": { "message": "Khác" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Thiết lập phương thức mở khóa để thay đổi hành động hết thời gian chờ của vault." }, "unlockMethodNeeded": { "message": "Thiết lập phương pháp mở khóa trong Cài đặt" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "Đánh giá tiện ích mở rộng" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Bảng điều khiển dành cho quản trị viên" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 706bf7a851c..bedd9933838 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -374,12 +374,21 @@ "other": { "message": "其他" }, + "unlockMethods": { + "message": "解锁选项" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "设置一个解锁方式以更改您的密码库超时动作。" }, "unlockMethodNeeded": { "message": "在设置中设置一个解锁方式" }, + "sessionTimeoutHeader": { + "message": "会话超时" + }, + "otherOptions": { + "message": "其他选项" + }, "rateExtension": { "message": "为本扩展打分" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "管理控制台" }, + "accountSecurity": { + "message": "账户安全" + }, + "notifications": { + "message": "通知" + }, + "appearance": { + "message": "外观" + }, "errorAssigningTargetCollection": { "message": "分配目标集合时出错。" }, diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 93002897954..4519e879818 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -374,12 +374,21 @@ "other": { "message": "其他" }, + "unlockMethods": { + "message": "Unlock options" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" }, "unlockMethodNeeded": { "message": "設定中設定解鎖" }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "otherOptions": { + "message": "Other options" + }, "rateExtension": { "message": "為本套件評分" }, @@ -3023,6 +3032,15 @@ "adminConsole": { "message": "Admin Console" }, + "accountSecurity": { + "message": "Account security" + }, + "notifications": { + "message": "Notifications" + }, + "appearance": { + "message": "Appearance" + }, "errorAssigningTargetCollection": { "message": "Error assigning target collection." }, diff --git a/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts b/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts index c4143004319..83ebcaa11e6 100644 --- a/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/login-strategy-service.factory.ts @@ -4,6 +4,10 @@ import { policyServiceFactory, PolicyServiceInitOptions, } from "../../../admin-console/background/service-factories/policy-service.factory"; +import { + vaultTimeoutSettingsServiceFactory, + VaultTimeoutSettingsServiceInitOptions, +} from "../../../background/service-factories/vault-timeout-settings-service.factory"; import { apiServiceFactory, ApiServiceInitOptions, @@ -108,6 +112,7 @@ export type LoginStrategyServiceInitOptions = LoginStrategyServiceFactoryOptions UserDecryptionOptionsServiceInitOptions & GlobalStateProviderInitOptions & BillingAccountProfileStateServiceInitOptions & + VaultTimeoutSettingsServiceInitOptions & KdfConfigServiceInitOptions; export function loginStrategyServiceFactory( @@ -142,6 +147,7 @@ export function loginStrategyServiceFactory( await internalUserDecryptionOptionServiceFactory(cache, opts), await globalStateProviderFactory(cache, opts), await billingAccountProfileStateServiceFactory(cache, opts), + await vaultTimeoutSettingsServiceFactory(cache, opts), await kdfConfigServiceFactory(cache, opts), ), ); diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index e56a2d5c387..fbb9075156f 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts @@ -1,7 +1,7 @@ import { Location } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { Router } from "@angular/router"; -import { Subject, firstValueFrom, map, switchMap, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, map, of, switchMap, takeUntil } from "rxjs"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; @@ -49,7 +49,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { readonly currentAccount$ = this.accountService.activeAccount$.pipe( switchMap((a) => a == null - ? null + ? of(null) : this.authService.activeAccountStatus$.pipe(map((s) => ({ ...a, status: s }))), ), ); @@ -106,12 +106,14 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { }); if (confirmed) { - this.messagingService.send("logout", { userId }); + const result = await this.accountSwitcherService.logoutAccount(userId); + // unlocked logout responses need to be navigated out of the account switcher. + // other responses will be handled by background and app.component + if (result?.status === AuthenticationStatus.Unlocked) { + this.location.back(); + } } - - // 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 - this.router.navigate(["home"]); + this.loading = false; } ngOnDestroy() { diff --git a/apps/browser/src/auth/popup/account-switching/account.component.ts b/apps/browser/src/auth/popup/account-switching/account.component.ts index 3fe83f2414d..3f152d61b92 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account.component.ts @@ -1,10 +1,10 @@ import { CommonModule, Location } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AvatarModule } from "@bitwarden/components"; import { AccountSwitcherService, AvailableAccount } from "./services/account-switcher.service"; @@ -21,9 +21,9 @@ export class AccountComponent { constructor( private accountSwitcherService: AccountSwitcherService, - private router: Router, private location: Location, private i18nService: I18nService, + private logService: LogService, ) {} get specialAccountAddId() { @@ -32,15 +32,19 @@ export class AccountComponent { async selectAccount(id: string) { this.loading.emit(true); - await this.accountSwitcherService.selectAccount(id); + let result; + try { + result = await this.accountSwitcherService.selectAccount(id); + } catch (e) { + this.logService.error("Error selecting account", e); + } - if (id === this.specialAccountAddId) { - // 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 - this.router.navigate(["home"]); - } else { + // Navigate out of account switching for unlocked accounts + // locked or logged out account statuses are handled by background and app.component + if (result?.status === AuthenticationStatus.Unlocked) { this.location.back(); } + this.loading.emit(false); } get status() { diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.spec.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.spec.ts index d27410a5d01..be12d249441 100644 --- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.spec.ts +++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.spec.ts @@ -186,4 +186,35 @@ describe("AccountSwitcherService", () => { expect(removeListenerSpy).toBeCalledTimes(1); }); }); + + describe("logout", () => { + const userId1 = "1" as UserId; + const userId2 = "2" as UserId; + it("initiates logout", async () => { + let listener: ( + message: { command: string; userId: UserId; status: AuthenticationStatus }, + sender: unknown, + sendResponse: unknown, + ) => void; + jest.spyOn(chrome.runtime.onMessage, "addListener").mockImplementation((addedListener) => { + listener = addedListener; + }); + + const removeListenerSpy = jest.spyOn(chrome.runtime.onMessage, "removeListener"); + + const logoutPromise = accountSwitcherService.logoutAccount(userId1); + + listener( + { command: "switchAccountFinish", userId: userId2, status: AuthenticationStatus.Unlocked }, + undefined, + undefined, + ); + + const result = await logoutPromise; + + expect(messagingService.send).toHaveBeenCalledWith("logout", { userId: userId1 }); + expect(result).toEqual({ newUserId: userId2, status: AuthenticationStatus.Unlocked }); + expect(removeListenerSpy).toBeCalledTimes(1); + }); + }); }); diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts index e5a3b8f8f54..2650c2db4e4 100644 --- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts +++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts @@ -41,7 +41,7 @@ export class AccountSwitcherService { SPECIAL_ADD_ACCOUNT_ID = "addAccount"; availableAccounts$: Observable; - switchAccountFinished$: Observable; + switchAccountFinished$: Observable<{ userId: UserId; status: AuthenticationStatus }>; constructor( private accountService: AccountService, @@ -111,11 +111,11 @@ export class AccountSwitcherService { ); // Create a reusable observable that listens to the switchAccountFinish message and returns the userId from the message - this.switchAccountFinished$ = fromChromeEvent<[message: { command: string; userId: string }]>( - chrome.runtime.onMessage, - ).pipe( + this.switchAccountFinished$ = fromChromeEvent< + [message: { command: string; userId: UserId; status: AuthenticationStatus }] + >(chrome.runtime.onMessage).pipe( filter(([message]) => message.command === "switchAccountFinish"), - map(([message]) => message.userId), + map(([message]) => ({ userId: message.userId, status: message.status })), ); } @@ -127,12 +127,46 @@ export class AccountSwitcherService { if (id === this.SPECIAL_ADD_ACCOUNT_ID) { id = null; } + const userId = id as UserId; // Creates a subscription to the switchAccountFinished observable but further // filters it to only care about the current userId. - const switchAccountFinishedPromise = firstValueFrom( + const switchAccountFinishedPromise = this.listenForSwitchAccountFinish(userId); + + // Initiate the actions required to make account switching happen + await this.accountService.switchAccount(userId); + this.messagingService.send("switchAccount", { userId }); // This message should cause switchAccountFinish to be sent + + // Wait until we receive the switchAccountFinished message + return await switchAccountFinishedPromise; + } + + /** + * + * @param userId the user id to logout + * @returns the userId and status of the that has been switch to due to the logout. null on errors. + */ + async logoutAccount( + userId: UserId, + ): Promise<{ newUserId: UserId; status: AuthenticationStatus } | null> { + // logout creates an account switch to the next up user, which may be null + const switchPromise = this.listenForSwitchAccountFinish(null); + + await this.messagingService.send("logout", { userId }); + + // wait for account switch to happen, the result will be the new user id and status + const result = await switchPromise; + return { newUserId: result.userId, status: result.status }; + } + + // Listens for the switchAccountFinish message and returns the userId from the message + // Optionally filters switchAccountFinish to an expected userId + private listenForSwitchAccountFinish( + expectedUserId: UserId | null, + ): Promise<{ userId: UserId; status: AuthenticationStatus } | null> { + return firstValueFrom( this.switchAccountFinished$.pipe( - filter((userId) => userId === id), + filter(({ userId }) => (expectedUserId ? userId === expectedUserId : true)), timeout({ // Much longer than account switching is expected to take for normal accounts // but the account switching process includes a possible full sync so we need to account @@ -143,20 +177,13 @@ export class AccountSwitcherService { throwError(() => new Error(AccountSwitcherService.incompleteAccountSwitchError)), }), ), - ); - - // Initiate the actions required to make account switching happen - await this.accountService.switchAccount(id as UserId); - this.messagingService.send("switchAccount", { userId: id }); // This message should cause switchAccountFinish to be sent - - // Wait until we recieve the switchAccountFinished message - await switchAccountFinishedPromise.catch((err) => { + ).catch((err) => { if ( err instanceof Error && err.message === AccountSwitcherService.incompleteAccountSwitchError ) { this.logService.warning("message 'switchAccount' never responded."); - return; + return null; } throw err; }); diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 51959ba03c8..32cfbe416d1 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -31,6 +31,11 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; +import { + VaultTimeout, + VaultTimeoutOption, + VaultTimeoutStringType, +} from "@bitwarden/common/types/vault-timeout.type"; import { DialogService } from "@bitwarden/components"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; @@ -50,7 +55,7 @@ export class AccountSecurityComponent implements OnInit { protected readonly VaultTimeoutAction = VaultTimeoutAction; availableVaultTimeoutActions: VaultTimeoutAction[] = []; - vaultTimeoutOptions: any[]; + vaultTimeoutOptions: VaultTimeoutOption[]; vaultTimeoutPolicyCallout: Observable<{ timeout: { hours: number; minutes: number }; action: VaultTimeoutAction; @@ -60,7 +65,7 @@ export class AccountSecurityComponent implements OnInit { accountSwitcherEnabled = false; form = this.formBuilder.group({ - vaultTimeout: [null as number | null], + vaultTimeout: [null as VaultTimeout | null], vaultTimeoutAction: [VaultTimeoutAction.Lock], pin: [null as boolean | null], biometric: false, @@ -118,20 +123,31 @@ export class AccountSecurityComponent implements OnInit { { name: this.i18nService.t("thirtyMinutes"), value: 30 }, { name: this.i18nService.t("oneHour"), value: 60 }, { name: this.i18nService.t("fourHours"), value: 240 }, - // { name: i18nService.t('onIdle'), value: -4 }, - // { name: i18nService.t('onSleep'), value: -3 }, ]; if (showOnLocked) { - this.vaultTimeoutOptions.push({ name: this.i18nService.t("onLocked"), value: -2 }); + this.vaultTimeoutOptions.push({ + name: this.i18nService.t("onLocked"), + value: VaultTimeoutStringType.OnLocked, + }); } - this.vaultTimeoutOptions.push({ name: this.i18nService.t("onRestart"), value: -1 }); - this.vaultTimeoutOptions.push({ name: this.i18nService.t("never"), value: null }); + this.vaultTimeoutOptions.push({ + name: this.i18nService.t("onRestart"), + value: VaultTimeoutStringType.OnRestart, + }); + this.vaultTimeoutOptions.push({ + name: this.i18nService.t("never"), + value: VaultTimeoutStringType.Never, + }); - let timeout = await this.vaultTimeoutSettingsService.getVaultTimeout(); - if (timeout === -2 && !showOnLocked) { - timeout = -1; + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + + let timeout = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(activeAccount.id), + ); + if (timeout === VaultTimeoutStringType.OnLocked && !showOnLocked) { + timeout = VaultTimeoutStringType.OnRestart; } this.form.controls.vaultTimeout.valueChanges @@ -159,7 +175,7 @@ export class AccountSecurityComponent implements OnInit { const initialValues = { vaultTimeout: timeout, vaultTimeoutAction: await firstValueFrom( - this.vaultTimeoutSettingsService.vaultTimeoutAction$(), + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), ), pin: await this.pinService.isPinSet(userId), biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(), @@ -203,7 +219,7 @@ export class AccountSecurityComponent implements OnInit { switchMap(() => combineLatest([ this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(), - this.vaultTimeoutSettingsService.vaultTimeoutAction$(), + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), ]), ), takeUntil(this.destroy$), @@ -237,8 +253,8 @@ export class AccountSecurityComponent implements OnInit { }); } - async saveVaultTimeout(previousValue: number, newValue: number) { - if (newValue == null) { + async saveVaultTimeout(previousValue: VaultTimeout, newValue: VaultTimeout) { + if (newValue === VaultTimeoutStringType.Never) { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "warning" }, content: { key: "neverLockWarning" }, @@ -262,11 +278,18 @@ export class AccountSecurityComponent implements OnInit { return; } - await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( - newValue, - await firstValueFrom(this.vaultTimeoutSettingsService.vaultTimeoutAction$()), + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + + const vaultTimeoutAction = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), ); - if (newValue == null) { + + await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( + activeAccount.id, + newValue, + vaultTimeoutAction, + ); + if (newValue === VaultTimeoutStringType.Never) { this.messagingService.send("bgReseedStorage"); } } @@ -296,7 +319,10 @@ export class AccountSecurityComponent implements OnInit { return; } + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( + activeAccount.id, this.form.value.vaultTimeout, newValue, ); diff --git a/apps/browser/src/autofill/spec/fido2-testing-utils.ts b/apps/browser/src/autofill/spec/fido2-testing-utils.ts index c9b39c16cc4..5c739235dc7 100644 --- a/apps/browser/src/autofill/spec/fido2-testing-utils.ts +++ b/apps/browser/src/autofill/spec/fido2-testing-utils.ts @@ -3,7 +3,7 @@ import { mock } from "jest-mock-extended"; import { AssertCredentialResult, CreateCredentialResult, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; export function createCredentialCreationOptionsMock( customFields: Partial = {}, diff --git a/apps/browser/src/autofill/types/index.ts b/apps/browser/src/autofill/types/index.ts index 8ed893e7337..a14ef1330cc 100644 --- a/apps/browser/src/autofill/types/index.ts +++ b/apps/browser/src/autofill/types/index.ts @@ -1,5 +1,6 @@ import { Region } from "@bitwarden/common/platform/abstractions/environment.service"; import { VaultTimeoutAction } from "@bitwarden/common/src/enums/vault-timeout-action.enum"; +import { VaultTimeout } from "@bitwarden/common/types/vault-timeout.type"; import { CipherType } from "@bitwarden/common/vault/enums"; export type UserSettings = { @@ -31,7 +32,7 @@ export type UserSettings = { utcDate: string; version: string; }; - vaultTimeout: number; + vaultTimeout: VaultTimeout; vaultTimeoutAction: VaultTimeoutAction; }; diff --git a/apps/browser/src/background/idle.background.ts b/apps/browser/src/background/idle.background.ts index 7b273459ad9..eef033b364b 100644 --- a/apps/browser/src/background/idle.background.ts +++ b/apps/browser/src/background/idle.background.ts @@ -1,9 +1,11 @@ import { firstValueFrom } from "rxjs"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { BrowserStateService } from "../platform/services/abstractions/browser-state.service"; @@ -19,6 +21,7 @@ export default class IdleBackground { private stateService: BrowserStateService, private notificationsService: NotificationsService, private accountService: AccountService, + private vaultTimeoutSettingsService: VaultTimeoutSettingsService, ) { this.idle = chrome.idle || (browser != null ? browser.idle : null); } @@ -54,10 +57,14 @@ export default class IdleBackground { const allUsers = await firstValueFrom(this.accountService.accounts$); for (const userId in allUsers) { // If the screen is locked or the screensaver activates - const timeout = await this.stateService.getVaultTimeout({ userId: userId }); - if (timeout === -2) { + const timeout = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(userId), + ); + if (timeout === VaultTimeoutStringType.OnLocked) { // On System Lock vault timeout option - const action = await this.stateService.getVaultTimeoutAction({ userId: userId }); + const action = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(userId), + ); if (action === VaultTimeoutAction.LogOut) { await this.vaultTimeoutService.logOut(userId); } else { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 7287e9b2857..c3722f2a480 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -78,6 +78,9 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; +import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; @@ -97,6 +100,7 @@ import { Message, MessageListener, MessageSender } from "@bitwarden/common/platf // eslint-disable-next-line no-restricted-imports -- Used for dependency creation import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; import { Lazy } from "@bitwarden/common/platform/misc/lazy"; +import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize"; 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"; @@ -105,6 +109,8 @@ import { DefaultConfigService } from "@bitwarden/common/platform/services/config import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; +import { Fido2AuthenticatorService } from "@bitwarden/common/platform/services/fido2/fido2-authenticator.service"; +import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service"; import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; @@ -125,6 +131,7 @@ import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider"; import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider"; import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider"; +import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; /* eslint-enable import/no-restricted-paths */ import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; @@ -153,11 +160,9 @@ import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-st import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/vault/abstractions/collection.service"; -import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-authenticator.service.abstraction"; -import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; -import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/vault/abstractions/fido2/fido2-user-interface.service.abstraction"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -168,8 +173,6 @@ import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwar import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/services/collection.service"; -import { Fido2AuthenticatorService } from "@bitwarden/common/vault/services/fido2/fido2-authenticator.service"; -import { Fido2ClientService } from "@bitwarden/common/vault/services/fido2/fido2-client.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -224,7 +227,6 @@ import I18nService from "../platform/services/i18n.service"; import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service"; import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; -import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider"; 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"; @@ -495,7 +497,7 @@ export default class MainBackground { this.accountService, this.singleUserStateProvider, ); - this.derivedStateProvider = new BackgroundDerivedStateProvider(); + this.derivedStateProvider = new InlineDerivedStateProvider(); this.stateProvider = new DefaultStateProvider( this.activeUserStateProvider, this.singleUserStateProvider, @@ -581,12 +583,30 @@ export default class MainBackground { ); this.appIdService = new AppIdService(this.globalStateProvider); + + this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); + this.organizationService = new OrganizationService(this.stateProvider); + this.policyService = new PolicyService(this.stateProvider, this.organizationService); + + this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService( + this.accountService, + this.pinService, + this.userDecryptionOptionsService, + this.cryptoService, + this.tokenService, + this.policyService, + this.biometricStateService, + this.stateProvider, + this.logService, + VaultTimeoutStringType.OnRestart, // default vault timeout + ); + this.apiService = new ApiService( this.tokenService, this.platformUtilsService, this.environmentService, this.appIdService, - this.stateService, + this.vaultTimeoutSettingsService, (expired: boolean) => this.logout(expired), ); this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); @@ -603,8 +623,7 @@ export default class MainBackground { this.stateProvider, ); this.syncNotifierService = new SyncNotifierService(); - this.organizationService = new OrganizationService(this.stateProvider); - this.policyService = new PolicyService(this.stateProvider, this.organizationService); + this.autofillSettingsService = new AutofillSettingsService( this.stateProvider, this.policyService, @@ -710,17 +729,6 @@ export default class MainBackground { ); this.folderApiService = new FolderApiService(this.folderService, this.apiService); - this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService( - this.accountService, - this.pinService, - this.userDecryptionOptionsService, - this.cryptoService, - this.tokenService, - this.policyService, - this.stateService, - this.biometricStateService, - ); - this.userVerificationService = new UserVerificationService( this.stateService, this.cryptoService, @@ -808,6 +816,7 @@ export default class MainBackground { logoutCallback, this.billingAccountProfileStateService, this.tokenService, + this.authService, ); this.eventUploadService = new EventUploadService( this.apiService, @@ -1056,6 +1065,7 @@ export default class MainBackground { this.stateService, this.notificationsService, this.accountService, + this.vaultTimeoutSettingsService, ); this.usernameGenerationService = new UsernameGenerationService( @@ -1172,6 +1182,7 @@ export default class MainBackground { * Switch accounts to indicated userId -- null is no active user */ async switchAccount(userId: UserId) { + let nextAccountStatus: AuthenticationStatus; try { const currentlyActiveAccount = await firstValueFrom( this.accountService.activeAccount$.pipe(map((account) => account?.id)), @@ -1179,6 +1190,8 @@ export default class MainBackground { // can be removed once password generation history is migrated to state providers await this.stateService.clearDecryptedData(currentlyActiveAccount); await this.accountService.switchAccount(userId); + // Clear sequentialized caches + clearCaches(); if (userId == null) { this.loginEmailService.setRememberEmail(false); @@ -1186,11 +1199,12 @@ export default class MainBackground { await this.refreshBadge(); await this.refreshMenu(); - await this.overlayBackground.updateOverlayCiphers(); + await this.overlayBackground?.updateOverlayCiphers(); // null in popup only contexts + this.messagingService.send("goHome"); return; } - const status = await this.authService.getAuthStatus(userId); + nextAccountStatus = await this.authService.getAuthStatus(userId); const forcePasswordReset = (await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId))) != ForceSetPasswordReason.None; @@ -1198,7 +1212,9 @@ export default class MainBackground { await this.systemService.clearPendingClipboard(); await this.notificationsService.updateConnection(false); - if (status === AuthenticationStatus.Locked) { + if (nextAccountStatus === AuthenticationStatus.LoggedOut) { + this.messagingService.send("goHome"); + } else if (nextAccountStatus === AuthenticationStatus.Locked) { this.messagingService.send("locked", { userId: userId }); } else if (forcePasswordReset) { this.messagingService.send("update-temp-password", { userId: userId }); @@ -1206,11 +1222,14 @@ export default class MainBackground { this.messagingService.send("unlocked", { userId: userId }); await this.refreshBadge(); await this.refreshMenu(); - await this.overlayBackground.updateOverlayCiphers(); + await this.overlayBackground?.updateOverlayCiphers(); // null in popup only contexts await this.syncService.fullSync(false); } } finally { - this.messagingService.send("switchAccountFinish", { userId: userId }); + this.messagingService.send("switchAccountFinish", { + userId: userId, + status: nextAccountStatus, + }); } } @@ -1231,6 +1250,13 @@ export default class MainBackground { await this.eventUploadService.uploadEvents(userBeingLoggedOut); + const newActiveUser = + userBeingLoggedOut === activeUserId + ? await firstValueFrom(this.accountService.nextUpAccount$.pipe(map((a) => a?.id))) + : null; + + await this.switchAccount(newActiveUser); + // HACK: We shouldn't wait for the authentication status to change but instead subscribe to the // authentication status to do various actions. const logoutPromise = firstValueFrom( @@ -1263,12 +1289,7 @@ export default class MainBackground { ]); //Needs to be checked before state is cleaned - const needStorageReseed = await this.needsStorageReseed(); - - const newActiveUser = - userBeingLoggedOut === activeUserId - ? await firstValueFrom(this.accountService.nextUpAccount$.pipe(map((a) => a?.id))) - : null; + const needStorageReseed = await this.needsStorageReseed(userId); await this.stateService.clean({ userId: userBeingLoggedOut }); await this.accountService.clean(userBeingLoggedOut); @@ -1278,16 +1299,10 @@ export default class MainBackground { // HACK: Wait for the user logging outs authentication status to transition to LoggedOut await logoutPromise; - await this.switchAccount(newActiveUser); - if (newActiveUser != null) { - // we have a new active user, do not continue tearing down application - this.messagingService.send("switchAccountFinish"); - } else { - this.messagingService.send("doneLoggingOut", { - expired: expired, - userId: userBeingLoggedOut, - }); - } + this.messagingService.send("doneLoggingOut", { + expired: expired, + userId: userBeingLoggedOut, + }); if (needStorageReseed) { await this.reseedStorage(); @@ -1300,16 +1315,16 @@ export default class MainBackground { } await this.refreshBadge(); await this.mainContextMenuHandler?.noAccess(); - // 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 - this.notificationsService.updateConnection(false); + await this.notificationsService.updateConnection(false); await this.systemService.clearPendingClipboard(); await this.systemService.startProcessReload(this.authService); } - private async needsStorageReseed(): Promise { - const currentVaultTimeout = await this.stateService.getVaultTimeout(); - return currentVaultTimeout == null ? false : true; + private async needsStorageReseed(userId: UserId): Promise { + const currentVaultTimeout = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(userId), + ); + return currentVaultTimeout == VaultTimeoutStringType.Never ? false : true; } async collectPageDetailsForContentScript(tab: any, sender: string, frameId: number = null) { diff --git a/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts b/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts index 6c5ea63eba6..5f98d9764c3 100644 --- a/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts +++ b/apps/browser/src/background/service-factories/vault-timeout-settings-service.factory.ts @@ -1,5 +1,6 @@ import { VaultTimeoutSettingsService as AbstractVaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { policyServiceFactory, @@ -35,9 +36,13 @@ import { FactoryOptions, } from "../../platform/background/service-factories/factory-options"; import { - StateServiceInitOptions, - stateServiceFactory, -} from "../../platform/background/service-factories/state-service.factory"; + logServiceFactory, + LogServiceInitOptions, +} from "../../platform/background/service-factories/log-service.factory"; +import { + StateProviderInitOptions, + stateProviderFactory, +} from "../../platform/background/service-factories/state-provider.factory"; type VaultTimeoutSettingsServiceFactoryOptions = FactoryOptions; @@ -48,8 +53,9 @@ export type VaultTimeoutSettingsServiceInitOptions = VaultTimeoutSettingsService CryptoServiceInitOptions & TokenServiceInitOptions & PolicyServiceInitOptions & - StateServiceInitOptions & - BiometricStateServiceInitOptions; + BiometricStateServiceInitOptions & + StateProviderInitOptions & + LogServiceInitOptions; export function vaultTimeoutSettingsServiceFactory( cache: { vaultTimeoutSettingsService?: AbstractVaultTimeoutSettingsService } & CachedServices, @@ -67,8 +73,10 @@ export function vaultTimeoutSettingsServiceFactory( await cryptoServiceFactory(cache, opts), await tokenServiceFactory(cache, opts), await policyServiceFactory(cache, opts), - await stateServiceFactory(cache, opts), await biometricStateServiceFactory(cache, opts), + await stateProviderFactory(cache, opts), + await logServiceFactory(cache, opts), + VaultTimeoutStringType.OnRestart, // default vault timeout ), ); } diff --git a/apps/browser/src/models/account.ts b/apps/browser/src/models/account.ts index 57d7844fde6..519f1bda6bb 100644 --- a/apps/browser/src/models/account.ts +++ b/apps/browser/src/models/account.ts @@ -1,28 +1,12 @@ import { Jsonify } from "type-fest"; -import { - Account as BaseAccount, - AccountSettings as BaseAccountSettings, -} from "@bitwarden/common/platform/models/domain/account"; +import { Account as BaseAccount } from "@bitwarden/common/platform/models/domain/account"; import { BrowserComponentState } from "./browserComponentState"; import { BrowserGroupingsComponentState } from "./browserGroupingsComponentState"; import { BrowserSendComponentState } from "./browserSendComponentState"; -export class AccountSettings extends BaseAccountSettings { - vaultTimeout = -1; // On Restart - - static fromJSON(json: Jsonify): AccountSettings { - if (json == null) { - return null; - } - - return Object.assign(new AccountSettings(), json, super.fromJSON(json)); - } -} - export class Account extends BaseAccount { - settings?: AccountSettings = new AccountSettings(); groupings?: BrowserGroupingsComponentState; send?: BrowserSendComponentState; ciphers?: BrowserComponentState; @@ -30,10 +14,7 @@ export class Account extends BaseAccount { constructor(init: Partial) { super(init); - Object.assign(this.settings, { - ...new AccountSettings(), - ...this.settings, - }); + this.groupings = init?.groupings ?? new BrowserGroupingsComponentState(); this.send = init?.send ?? new BrowserSendComponentState(); this.ciphers = init?.ciphers ?? new BrowserComponentState(); @@ -46,7 +27,6 @@ export class Account extends BaseAccount { } return Object.assign(new Account({}), json, super.fromJSON(json), { - settings: AccountSettings.fromJSON(json.settings), groupings: BrowserGroupingsComponentState.fromJSON(json.groupings), send: BrowserSendComponentState.fromJSON(json.send), ciphers: BrowserComponentState.fromJSON(json.ciphers), diff --git a/apps/browser/src/platform/background/service-factories/api-service.factory.ts b/apps/browser/src/platform/background/service-factories/api-service.factory.ts index 57cd5004413..bfae93f3d8a 100644 --- a/apps/browser/src/platform/background/service-factories/api-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/api-service.factory.ts @@ -5,6 +5,10 @@ import { tokenServiceFactory, TokenServiceInitOptions, } from "../../../auth/background/service-factories/token-service.factory"; +import { + vaultTimeoutSettingsServiceFactory, + VaultTimeoutSettingsServiceInitOptions, +} from "../../../background/service-factories/vault-timeout-settings-service.factory"; import { CachedServices, factory, @@ -20,7 +24,6 @@ import { PlatformUtilsServiceInitOptions, platformUtilsServiceFactory, } from "./platform-utils-service.factory"; -import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; type ApiServiceFactoryOptions = FactoryOptions & { apiServiceOptions: { @@ -34,7 +37,7 @@ export type ApiServiceInitOptions = ApiServiceFactoryOptions & PlatformUtilsServiceInitOptions & EnvironmentServiceInitOptions & AppIdServiceInitOptions & - StateServiceInitOptions; + VaultTimeoutSettingsServiceInitOptions; export function apiServiceFactory( cache: { apiService?: AbstractApiService } & CachedServices, @@ -50,7 +53,7 @@ export function apiServiceFactory( await platformUtilsServiceFactory(cache, opts), await environmentServiceFactory(cache, opts), await appIdServiceFactory(cache, opts), - await stateServiceFactory(cache, opts), + await vaultTimeoutSettingsServiceFactory(cache, opts), opts.apiServiceOptions.logoutCallback, opts.apiServiceOptions.customUserAgent, ), diff --git a/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts b/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts index 3c3900144bb..2c5f8f24194 100644 --- a/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts +++ b/apps/browser/src/platform/background/service-factories/derived-state-provider.factory.ts @@ -1,6 +1,6 @@ import { DerivedStateProvider } from "@bitwarden/common/platform/state"; - -import { BackgroundDerivedStateProvider } from "../../state/background-derived-state.provider"; +// eslint-disable-next-line import/no-restricted-paths -- For dependency creation +import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; import { CachedServices, FactoryOptions, factory } from "./factory-options"; @@ -12,10 +12,5 @@ export async function derivedStateProviderFactory( cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices, opts: DerivedStateProviderInitOptions, ): Promise { - return factory( - cache, - "derivedStateProvider", - opts, - async () => new BackgroundDerivedStateProvider(), - ); + return factory(cache, "derivedStateProvider", opts, async () => new InlineDerivedStateProvider()); } diff --git a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts index 259d6f154aa..2bdc2fd0d44 100644 --- a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts +++ b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts @@ -1,4 +1,4 @@ -import { mergeMap } from "rxjs"; +import { filter, mergeMap } from "rxjs"; import { AbstractStorageService, @@ -34,6 +34,11 @@ export default abstract class AbstractChromeStorageService constructor(protected chromeStorageApi: chrome.storage.StorageArea) { this.updates$ = fromChromeEvent(this.chromeStorageApi.onChanged).pipe( + filter(([changes]) => { + // Our storage services support changing only one key at a time. If more are changed, it's due to + // reseeding storage and we should ignore the changes. + return Object.keys(changes).length === 1; + }), mergeMap(([changes]) => { return Object.entries(changes).map(([key, change]) => { // The `newValue` property isn't on the StorageChange object diff --git a/apps/browser/src/platform/state/background-derived-state.provider.ts b/apps/browser/src/platform/state/background-derived-state.provider.ts deleted file mode 100644 index cbc5a34b37b..00000000000 --- a/apps/browser/src/platform/state/background-derived-state.provider.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Observable } from "rxjs"; - -import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state"; -// 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 { DerivedStateDependencies } from "@bitwarden/common/src/types/state"; - -import { BackgroundDerivedState } from "./background-derived-state"; - -export class BackgroundDerivedStateProvider extends DefaultDerivedStateProvider { - override buildDerivedState( - parentState$: Observable, - deriveDefinition: DeriveDefinition, - dependencies: TDeps, - ): DerivedState { - return new BackgroundDerivedState( - parentState$, - deriveDefinition, - deriveDefinition.buildCacheKey(), - dependencies, - ); - } -} diff --git a/apps/browser/src/platform/state/background-derived-state.ts b/apps/browser/src/platform/state/background-derived-state.ts deleted file mode 100644 index 61768cb970c..00000000000 --- a/apps/browser/src/platform/state/background-derived-state.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Observable, Subscription, concatMap } from "rxjs"; -import { Jsonify } from "type-fest"; - -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DeriveDefinition } from "@bitwarden/common/platform/state"; -// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client -import { DefaultDerivedState } from "@bitwarden/common/platform/state/implementations/default-derived-state"; -import { DerivedStateDependencies } from "@bitwarden/common/types/state"; - -import { BrowserApi } from "../browser/browser-api"; - -export class BackgroundDerivedState< - TFrom, - TTo, - TDeps extends DerivedStateDependencies, -> extends DefaultDerivedState { - private portSubscriptions: Map = new Map(); - - constructor( - parentState$: Observable, - deriveDefinition: DeriveDefinition, - portName: string, - dependencies: TDeps, - ) { - super(parentState$, deriveDefinition, dependencies); - - // listen for foreground derived states to connect - BrowserApi.addListener(chrome.runtime.onConnect, (port) => { - if (port.name !== portName) { - return; - } - - const listenerCallback = this.onMessageFromForeground.bind(this); - port.onDisconnect.addListener(() => { - this.portSubscriptions.get(port)?.unsubscribe(); - this.portSubscriptions.delete(port); - port.onMessage.removeListener(listenerCallback); - }); - port.onMessage.addListener(listenerCallback); - - const stateSubscription = this.state$ - .pipe( - concatMap(async (state) => { - await this.sendMessage( - { - action: "nextState", - data: JSON.stringify(state), - id: Utils.newGuid(), - }, - port, - ); - }), - ) - .subscribe(); - - this.portSubscriptions.set(port, stateSubscription); - }); - } - - private async onMessageFromForeground(message: DerivedStateMessage, port: chrome.runtime.Port) { - if (message.originator === "background") { - return; - } - - switch (message.action) { - case "nextState": { - const dataObj = JSON.parse(message.data) as Jsonify; - const data = this.deriveDefinition.deserialize(dataObj); - await this.forceValue(data); - await this.sendResponse( - message, - { - action: "resolve", - }, - port, - ); - break; - } - } - } - - private async sendResponse( - originalMessage: DerivedStateMessage, - response: Omit, - port: chrome.runtime.Port, - ) { - // 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 - this.sendMessage( - { - ...response, - id: originalMessage.id, - }, - port, - ); - } - - private async sendMessage( - message: Omit, - port: chrome.runtime.Port, - ) { - port.postMessage({ - ...message, - originator: "background", - }); - } -} diff --git a/apps/browser/src/platform/state/derived-state-interactions.spec.ts b/apps/browser/src/platform/state/derived-state-interactions.spec.ts deleted file mode 100644 index 823c071a4c5..00000000000 --- a/apps/browser/src/platform/state/derived-state-interactions.spec.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * need to update test environment so structuredClone works appropriately - * @jest-environment ../../libs/shared/test.environment.ts - */ - -import { NgZone } from "@angular/core"; -import { mock } from "jest-mock-extended"; -import { Subject, firstValueFrom } from "rxjs"; - -import { DeriveDefinition } from "@bitwarden/common/platform/state"; -// eslint-disable-next-line import/no-restricted-paths -- needed to define a derive definition -import { StateDefinition } from "@bitwarden/common/platform/state/state-definition"; -import { awaitAsync, trackEmissions, ObservableTracker } from "@bitwarden/common/spec"; - -import { mockPorts } from "../../../spec/mock-port.spec-util"; - -import { BackgroundDerivedState } from "./background-derived-state"; -import { ForegroundDerivedState } from "./foreground-derived-state"; - -const stateDefinition = new StateDefinition("test", "memory"); -const deriveDefinition = new DeriveDefinition(stateDefinition, "test", { - derive: (dateString: string) => (dateString == null ? null : new Date(dateString)), - deserializer: (dateString: string) => (dateString == null ? null : new Date(dateString)), - cleanupDelayMs: 1000, -}); - -// Mock out the runInsideAngular operator so we don't have to deal with zone.js -jest.mock("../browser/run-inside-angular.operator", () => { - return { - runInsideAngular: (ngZone: any) => (source: any) => source, - }; -}); - -describe("foreground background derived state interactions", () => { - let foreground: ForegroundDerivedState; - let background: BackgroundDerivedState>; - let parentState$: Subject; - const initialParent = "2020-01-01"; - const ngZone = mock(); - const portName = "testPort"; - - beforeEach(() => { - mockPorts(); - parentState$ = new Subject(); - - background = new BackgroundDerivedState(parentState$, deriveDefinition, portName, {}); - foreground = new ForegroundDerivedState(deriveDefinition, portName, ngZone); - }); - - afterEach(() => { - parentState$.complete(); - jest.resetAllMocks(); - }); - - it("should connect between foreground and background", async () => { - const foregroundEmissions = trackEmissions(foreground.state$); - const backgroundEmissions = trackEmissions(background.state$); - - parentState$.next(initialParent); - await awaitAsync(10); - - expect(backgroundEmissions).toEqual([new Date(initialParent)]); - expect(foregroundEmissions).toEqual([new Date(initialParent)]); - }); - - it("should initialize a late-connected foreground", async () => { - const newForeground = new ForegroundDerivedState(deriveDefinition, portName, ngZone); - const backgroundTracker = new ObservableTracker(background.state$); - parentState$.next(initialParent); - const foregroundTracker = new ObservableTracker(newForeground.state$); - - expect(await backgroundTracker.expectEmission()).toEqual(new Date(initialParent)); - expect(await foregroundTracker.expectEmission()).toEqual(new Date(initialParent)); - }); - - describe("forceValue", () => { - it("should force the value to the background", async () => { - const dateString = "2020-12-12"; - const emissions = trackEmissions(background.state$); - - await foreground.forceValue(new Date(dateString)); - await awaitAsync(); - - expect(emissions).toEqual([new Date(dateString)]); - }); - - it("should not create new ports if already connected", async () => { - // establish port with subscription - trackEmissions(foreground.state$); - - const connectMock = chrome.runtime.connect as jest.Mock; - const initialConnectCalls = connectMock.mock.calls.length; - - expect(foreground["port"]).toBeDefined(); - const newDate = new Date(); - await foreground.forceValue(newDate); - await awaitAsync(); - - expect(connectMock.mock.calls.length).toBe(initialConnectCalls); - expect(await firstValueFrom(background.state$)).toEqual(newDate); - }); - - it("should create a port if not connected", async () => { - const connectMock = chrome.runtime.connect as jest.Mock; - const initialConnectCalls = connectMock.mock.calls.length; - - expect(foreground["port"]).toBeUndefined(); - const newDate = new Date(); - await foreground.forceValue(newDate); - await awaitAsync(); - - expect(connectMock.mock.calls.length).toBe(initialConnectCalls + 1); - expect(foreground["port"]).toBeNull(); - expect(await firstValueFrom(background.state$)).toEqual(newDate); - }); - }); -}); diff --git a/apps/browser/src/platform/state/foreground-derived-state.provider.ts b/apps/browser/src/platform/state/foreground-derived-state.provider.ts deleted file mode 100644 index 8b8d82b9143..00000000000 --- a/apps/browser/src/platform/state/foreground-derived-state.provider.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgZone } from "@angular/core"; -import { Observable } from "rxjs"; - -import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state"; -// 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 { DerivedStateDependencies } from "@bitwarden/common/src/types/state"; - -import { ForegroundDerivedState } from "./foreground-derived-state"; - -export class ForegroundDerivedStateProvider extends DefaultDerivedStateProvider { - constructor(private ngZone: NgZone) { - super(); - } - override buildDerivedState( - _parentState$: Observable, - deriveDefinition: DeriveDefinition, - _dependencies: TDeps, - ): DerivedState { - return new ForegroundDerivedState( - deriveDefinition, - deriveDefinition.buildCacheKey(), - this.ngZone, - ); - } -} diff --git a/apps/browser/src/platform/state/foreground-derived-state.spec.ts b/apps/browser/src/platform/state/foreground-derived-state.spec.ts deleted file mode 100644 index ee224540c13..00000000000 --- a/apps/browser/src/platform/state/foreground-derived-state.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { NgZone } from "@angular/core"; -import { awaitAsync } from "@bitwarden/common/../spec"; -import { mock } from "jest-mock-extended"; - -import { DeriveDefinition } from "@bitwarden/common/platform/state"; -// eslint-disable-next-line import/no-restricted-paths -- needed to define a derive definition -import { StateDefinition } from "@bitwarden/common/platform/state/state-definition"; - -import { mockPorts } from "../../../spec/mock-port.spec-util"; - -import { ForegroundDerivedState } from "./foreground-derived-state"; - -const stateDefinition = new StateDefinition("test", "memory"); -const deriveDefinition = new DeriveDefinition(stateDefinition, "test", { - derive: (dateString: string) => (dateString == null ? null : new Date(dateString)), - deserializer: (dateString: string) => (dateString == null ? null : new Date(dateString)), - cleanupDelayMs: 1, -}); - -// Mock out the runInsideAngular operator so we don't have to deal with zone.js -jest.mock("../browser/run-inside-angular.operator", () => { - return { - runInsideAngular: (ngZone: any) => (source: any) => source, - }; -}); - -describe("ForegroundDerivedState", () => { - let sut: ForegroundDerivedState; - const portName = "testPort"; - const ngZone = mock(); - - beforeEach(() => { - mockPorts(); - sut = new ForegroundDerivedState(deriveDefinition, portName, ngZone); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("should not connect a port until subscribed", async () => { - expect(sut["port"]).toBeUndefined(); - const subscription = sut.state$.subscribe(); - - expect(sut["port"]).toBeDefined(); - subscription.unsubscribe(); - }); - - it("should disconnect its port when unsubscribed", async () => { - const subscription = sut.state$.subscribe(); - - expect(sut["port"]).toBeDefined(); - const disconnectSpy = jest.spyOn(sut["port"], "disconnect"); - subscription.unsubscribe(); - // wait for the cleanup delay - await awaitAsync(deriveDefinition.cleanupDelayMs * 2); - - expect(disconnectSpy).toHaveBeenCalled(); - expect(sut["port"]).toBeNull(); - }); -}); diff --git a/apps/browser/src/platform/state/foreground-derived-state.ts b/apps/browser/src/platform/state/foreground-derived-state.ts deleted file mode 100644 index 6abe3638764..00000000000 --- a/apps/browser/src/platform/state/foreground-derived-state.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { NgZone } from "@angular/core"; -import { - Observable, - ReplaySubject, - defer, - filter, - firstValueFrom, - map, - of, - share, - switchMap, - tap, - timer, -} from "rxjs"; -import { Jsonify } from "type-fest"; - -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state"; -import { DerivedStateDependencies } from "@bitwarden/common/types/state"; - -import { fromChromeEvent } from "../browser/from-chrome-event"; -import { runInsideAngular } from "../browser/run-inside-angular.operator"; - -export class ForegroundDerivedState implements DerivedState { - private port: chrome.runtime.Port; - private backgroundResponses$: Observable; - state$: Observable; - - constructor( - private deriveDefinition: DeriveDefinition, - private portName: string, - private ngZone: NgZone, - ) { - const latestValueFromPort$ = (port: chrome.runtime.Port) => { - return fromChromeEvent(port.onMessage).pipe( - map(([message]) => message as DerivedStateMessage), - filter((message) => message.originator === "background" && message.action === "nextState"), - map((message) => { - const json = JSON.parse(message.data) as Jsonify; - return this.deriveDefinition.deserialize(json); - }), - ); - }; - - this.state$ = defer(() => of(this.initializePort())).pipe( - switchMap(() => latestValueFromPort$(this.port)), - share({ - connector: () => new ReplaySubject(1), - resetOnRefCountZero: () => - timer(this.deriveDefinition.cleanupDelayMs).pipe(tap(() => this.tearDownPort())), - }), - runInsideAngular(this.ngZone), - ); - } - - async forceValue(value: TTo): Promise { - let cleanPort = false; - if (this.port == null) { - this.initializePort(); - cleanPort = true; - } - await this.delegateToBackground("nextState", value); - if (cleanPort) { - this.tearDownPort(); - } - return value; - } - - private initializePort() { - if (this.port != null) { - return; - } - - this.port = chrome.runtime.connect({ name: this.portName }); - - this.backgroundResponses$ = fromChromeEvent(this.port.onMessage).pipe( - map(([message]) => message as DerivedStateMessage), - filter((message) => message.originator === "background"), - ); - return this.backgroundResponses$; - } - - private async delegateToBackground(action: DerivedStateActions, data: TTo): Promise { - const id = Utils.newGuid(); - // listen for response before request - const response = firstValueFrom( - this.backgroundResponses$.pipe(filter((message) => message.id === id)), - ); - - this.sendMessage({ - id, - action, - data: JSON.stringify(data), - }); - - await response; - } - - private sendMessage(message: Omit) { - this.port.postMessage({ - ...message, - originator: "foreground", - }); - } - - private tearDownPort() { - if (this.port == null) { - return; - } - - this.port.disconnect(); - this.port = null; - this.backgroundResponses$ = null; - } -} diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 25fac444501..7e94e84ef5d 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -91,13 +91,9 @@ export class AppComponent implements OnInit, OnDestroy { message: this.i18nService.t("loginExpired"), }); } - - // 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 - this.router.navigate(["home"]); }); this.changeDetectorRef.detectChanges(); - } else if (msg.command === "authBlocked") { + } else if (msg.command === "authBlocked" || msg.command === "goHome") { // 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 this.router.navigate(["home"]); @@ -137,9 +133,6 @@ export class AppComponent implements OnInit, OnDestroy { // 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 this.router.navigate(["/remove-password"]); - } else if (msg.command === "switchAccountFinish") { - // TODO: unset loading? - // this.loading = false; } else if (msg.command == "update-temp-password") { // 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 diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index de2fc727474..0b9c8f6fe68 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -12,6 +12,7 @@ import { OBSERVABLE_MEMORY_STORAGE, SYSTEM_THEME_OBSERVABLE, SafeInjectionToken, + DEFAULT_VAULT_TIMEOUT, INTRAPROCESS_MESSAGING_SUBJECT, CLIENT_TYPE, } from "@bitwarden/angular/services/injection-tokens"; @@ -78,8 +79,11 @@ import { GlobalStateProvider, StateProvider, } from "@bitwarden/common/platform/state"; +// eslint-disable-next-line import/no-restricted-paths -- Used for dependency injection +import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -112,7 +116,6 @@ import { BrowserScriptInjectorService } from "../../platform/services/browser-sc import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service"; import I18nService from "../../platform/services/i18n.service"; import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service"; -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 { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; @@ -160,6 +163,10 @@ const safeProviders: SafeProvider[] = [ safeProvider(DebounceNavigationService), safeProvider(DialogService), safeProvider(PopupCloseWarningService), + safeProvider({ + provide: DEFAULT_VAULT_TIMEOUT, + useValue: VaultTimeoutStringType.OnRestart, + }), safeProvider({ provide: APP_INITIALIZER as SafeInjectionToken<() => Promise>, useFactory: (initService: InitService) => initService.init(), @@ -512,8 +519,8 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: DerivedStateProvider, - useClass: ForegroundDerivedStateProvider, - deps: [NgZone], + useClass: InlineDerivedStateProvider, + deps: [], }), safeProvider({ provide: AutofillSettingsServiceAbstraction, diff --git a/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts b/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts index 49f248c7b81..d77a60d3c7b 100644 --- a/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts +++ b/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts @@ -3,7 +3,7 @@ import { AssertCredentialResult, CreateCredentialParams, CreateCredentialResult, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; type SharedFido2ScriptInjectionDetails = { runAt: browser.contentScripts.RegisteredContentScriptOptions["runAt"]; diff --git a/apps/browser/src/vault/fido2/background/fido2.background.spec.ts b/apps/browser/src/vault/fido2/background/fido2.background.spec.ts index 534d8a99c5b..5da51618ac5 100644 --- a/apps/browser/src/vault/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/vault/fido2/background/fido2.background.spec.ts @@ -1,13 +1,13 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AssertCredentialParams, CreateCredentialParams, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Fido2ClientService } from "@bitwarden/common/platform/services/fido2/fido2-client.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { Fido2ClientService } from "@bitwarden/common/vault/services/fido2/fido2-client.service"; import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks"; import { diff --git a/apps/browser/src/vault/fido2/background/fido2.background.ts b/apps/browser/src/vault/fido2/background/fido2.background.ts index 5e51e05d776..0666f804f28 100644 --- a/apps/browser/src/vault/fido2/background/fido2.background.ts +++ b/apps/browser/src/vault/fido2/background/fido2.background.ts @@ -1,14 +1,14 @@ import { firstValueFrom, startWith } from "rxjs"; import { pairwise } from "rxjs/operators"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AssertCredentialParams, AssertCredentialResult, CreateCredentialParams, CreateCredentialResult, Fido2ClientService, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; diff --git a/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts b/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts index 55bf2468d60..d4ad7209b79 100644 --- a/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/vault/fido2/browser-fido2-user-interface.service.ts @@ -16,14 +16,14 @@ import { import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserRequestedFallbackAbortReason } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { UserRequestedFallbackAbortReason } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction, Fido2UserInterfaceSession, NewCredentialParams, PickCredentialParams, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-user-interface.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { BrowserApi } from "../../platform/browser/browser-api"; import { closeFido2Popout, openFido2Popout } from "../popup/utils/vault-popout-window"; diff --git a/apps/browser/src/vault/fido2/content/content-script.spec.ts b/apps/browser/src/vault/fido2/content/content-script.spec.ts index 0c2a52ed101..c9f970a30c6 100644 --- a/apps/browser/src/vault/fido2/content/content-script.spec.ts +++ b/apps/browser/src/vault/fido2/content/content-script.spec.ts @@ -1,6 +1,6 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { CreateCredentialResult } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { CreateCredentialResult } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks"; import { triggerPortOnDisconnectEvent } from "../../../autofill/spec/testing-utils"; diff --git a/apps/browser/src/vault/fido2/content/content-script.ts b/apps/browser/src/vault/fido2/content/content-script.ts index fe3aafe9fb8..ad9f526f6ce 100644 --- a/apps/browser/src/vault/fido2/content/content-script.ts +++ b/apps/browser/src/vault/fido2/content/content-script.ts @@ -1,7 +1,7 @@ import { AssertCredentialParams, CreateCredentialParams, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { sendExtensionMessage } from "../../../autofill/utils"; import { Fido2PortName } from "../enums/fido2-port-name.enum"; diff --git a/apps/browser/src/vault/fido2/content/messaging/message.ts b/apps/browser/src/vault/fido2/content/messaging/message.ts index b803b97f92e..d42c10a5d88 100644 --- a/apps/browser/src/vault/fido2/content/messaging/message.ts +++ b/apps/browser/src/vault/fido2/content/messaging/message.ts @@ -3,7 +3,7 @@ import { CreateCredentialResult, AssertCredentialParams, AssertCredentialResult, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; export enum MessageType { CredentialCreationRequest, diff --git a/apps/browser/src/vault/fido2/content/messaging/messenger.ts b/apps/browser/src/vault/fido2/content/messaging/messenger.ts index f05c138eab0..ea4049ac64c 100644 --- a/apps/browser/src/vault/fido2/content/messaging/messenger.ts +++ b/apps/browser/src/vault/fido2/content/messaging/messenger.ts @@ -1,4 +1,4 @@ -import { FallbackRequestedError } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { Message, MessageType } from "./message"; diff --git a/apps/browser/src/vault/fido2/content/page-script.ts b/apps/browser/src/vault/fido2/content/page-script.ts index 5b04f7c1dd8..5898dbd04df 100644 --- a/apps/browser/src/vault/fido2/content/page-script.ts +++ b/apps/browser/src/vault/fido2/content/page-script.ts @@ -1,4 +1,4 @@ -import { FallbackRequestedError } from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; +import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; import { WebauthnUtils } from "../webauthn-utils"; diff --git a/apps/browser/src/vault/fido2/webauthn-utils.ts b/apps/browser/src/vault/fido2/webauthn-utils.ts index 6ac7af4ce3e..618e692aadf 100644 --- a/apps/browser/src/vault/fido2/webauthn-utils.ts +++ b/apps/browser/src/vault/fido2/webauthn-utils.ts @@ -1,8 +1,8 @@ import { CreateCredentialResult, AssertCredentialResult, -} from "@bitwarden/common/vault/abstractions/fido2/fido2-client.service.abstraction"; -import { Fido2Utils } from "@bitwarden/common/vault/services/fido2/fido2-utils"; +} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; +import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils"; import { InsecureAssertCredentialParams, diff --git a/apps/browser/store/locales/az/copy.resx b/apps/browser/store/locales/az/copy.resx index 2a3d507df2c..ddbc7ac23f4 100644 --- a/apps/browser/store/locales/az/copy.resx +++ b/apps/browser/store/locales/az/copy.resx @@ -118,58 +118,55 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden Parol Meneceri - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Bitwarden evdə və ya işdə olarkən bütün parol, keçid açarı və həssas məlumatlarınızı asanlıqla qoruyur. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + PCMag, WIRED, The Verge, CNET, G2 və daha çoxu tərəfindən ən yaxşı parol meneceri olaraq tanındı! -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. +RƏQƏMSAL HƏYATINIZI GÜVƏNDƏ SAXLAYIN +Hər hesab üçün unikal, güclü parollar yaradıb saxlayaraq rəqəmsal həyatınızı güvəndə saxlatyın və məlumat pozuntularına qarşı qorunun. Hər şeyi yalnız sizin müraciət edə biləcəyiniz ucdan uca şifrələnmiş parol anbarında saxlayın. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +DATANIZA HƏR YERDƏN, HƏR CİHAZDAN, İSTƏNİLƏN VAXT MÜRACİƏT EDİN +Limitsiz parolları limitsiz cihazlarda məhdudiyyət olmadan asanlıqla idarə edin, saxlayın, güvənlə qoruyun və paylaşın. -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. +HƏR KƏS İNTERNETDƏ GÜVƏNDƏ QALMAQ ÜÇÜN ALƏTLƏRƏ SAHİB OLMALIDIR +Bitwarden-i heç bir reklam və ya satış datası olmadan ödənişsiz istifadə edin. Bitwarden hesab edir ki, hər kəs internetdə güvəndə qalmaq bacarığına sahib olmalıdır. Premium planlar qabaqcıl özəlliklərə müraciət təklif edir. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +BITWARDEN İLƏ KOMANDALARINIZI GÜCLƏNDİRİN +Komanda və Müəssisələr üçün planlar, professional biznes özəllikləri ilə birgə gəlir. Bəzi nümunələrə SSO inteqrasiyası, öz-özünə sahiblik, kataloq inteqrasiyası və SCIM təqdim etmə, qlobal siyasətlər, API müraciəti, olay jurnalları və daha çoxu daxildir. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +İş gücünüzün güvənliyini qorumaq və həssas məlumatları həmkarlarınızla paylaşmaq üçün Bitwarden-i istifadə edin. +Bitwarden-i seçmək üçün daha çox səbəb: +Dünya səviyyəli şifrələmə +Parollar, qabaqcıl ucdan uca şifrələmə (AES-256 bit, salted hashtag və PBKDF2 SHA-256) ilə qorunur, beləliklə datanız güvəndə və məxfi qalır. -More reasons to choose Bitwarden: +3-cü tərəf auditləri +Bitwarden, müntəzəm olaraq tanınmış təhlükəsizlik firmaları ilə hərtərəfli üçüncü tərəf təhlükəsizlik auditləri keçirir. Bu illik auditlərə Bitwarden IP-ləri, serverləri və veb tətbiqləri arasında mənbə kodu qiymətləndirmələri və nüfuz testi daxildir. -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. - -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. - -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Qabaqcıl 2FA +Giriş məlumatlarınızı üçüncü tərəf autentifikatoru, e-poçtla göndərilən kodlar və ya avadanlıq güvənlik açarı və ya keçid açarı kimi FIDO2 WebAuthn kimlik məlumatları ilə güvəndə saxlayın. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Ucdan-uca şifrələmə güvənliyini qoruyarkən və pozuntunu məhdudlaşdırarkən dataları birbaşa başqalarına göndərin. -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. +Daxili Generator +Ziyarət etdiyiniz hər sayt üçün uzun, mürəkkəb və fərqli parollar və unikal istifadəçi adları yaradın. Əlavə məxfilik üçün e-poçt ləqəb provayderləri ilə inteqrasiya edin. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Qlobal Tərcümələr +Bitwarden tərcümələri 60-dan çox dildə mövcuddur, Crowdin vasitəsilə qlobal icma tərəfindən tərcümə edilmişdir. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Çarpaz Platforma Tətbiqləri +İstənilən brauzerdən, mobil cihazdan və ya masaüstü əməliyyat sistemindən və daha çoxundan Bitwarden Anbarınızdakı həssas dataları güvəndə saxlayın və paylaşın. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! - +Bitwarden parollardan daha çox qoruyur +Bitwarden-nin ucdan-uca şifrələnmiş kimlik məlumatlarını idarəetmə həlləri, təşkilatlara hər şeyi, o cümlədən tərtibatçı sirrlərini və keçid açarı təcrübələrini qorumaq gücü verir. Bitwarden Sirr Meneceri və Bitwarden Passwordless.dev haqqında daha ətraflı öyrənmək üçün Bitwarden.com saytını ziyarət edin! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Bitwarden evdə və ya işdə olarkən bütün parol, keçid açarı və həssas məlumatlarınızı asanlıqla qoruyur. Anbarınıza bir neçə cihazdan eyniləşdirərək müraciət edin diff --git a/apps/browser/store/locales/bg/copy.resx b/apps/browser/store/locales/bg/copy.resx index bc08f6a107a..060ca51b21b 100644 --- a/apps/browser/store/locales/bg/copy.resx +++ b/apps/browser/store/locales/bg/copy.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden — управител на пароли - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + У дома, на работа или на път – Битуорден защитава всички Ваши пароли, секретни ключове и лична информация. 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 - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + У дома, на работа или на път – Битуорден защитава всички Ваши пароли, секретни ключове и лична информация. Удобен достъп до трезора, който се синхронизира от всички устройства diff --git a/apps/browser/store/locales/da/copy.resx b/apps/browser/store/locales/da/copy.resx index 775a3edd818..928e54d8edd 100644 --- a/apps/browser/store/locales/da/copy.resx +++ b/apps/browser/store/locales/da/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden Adgangskodehåndtering - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Hjemme, på arbejde eller på farten sikrer Bitwarden nemt alle adgangskoder, adgangskort og sensitive oplysninger. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Anerkendt som den bedste adgangskodehåndtering af PCMag, WIRED, The Verge, CNET, G2 og flere! -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. +SIKR DIT DIGITALE LIV +Sikr dit digitale liv og vær beskyttet mod dataindbrud ved at generere og gemme unikke, stærke adgangskoder til hver konto. Vedligehold alt i en ende-til-ende krypteret adgangskodeboks, som kun du har adgang til. -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +FÅ ADGANG TIL DATAENE, hvor som helst, når som helst, PÅ ENHVER ENHED +Håndtér, gem, beskyt og del nemt et ubegrænset antal adgangskoder på tværs af et ubegrænset antal enheder uden restriktioner. -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. +ALLE BØR HAVE VÆRKTØJERNE TIL AT KUNNE VÆRE SIKKER ONLINE +Brug Bitwarden gratis uden annoncer eller salgsdata. Bitwarden mener, at alle bør have muligheden for at forblive sikre online. Premium-abonnementer giver adgang til avancerede funktioner. -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +STYRK DINE HOLD MED BITWARDEN +Abonnementer til Teams og Enterprise kommer med professionelle forretningsfunktioner, f.eks. SSO-integration, selvhosting, katalogintegration og SCIM-klargøring, globale politikker, API-adgang, hændelseslogfiler og mere. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Brug Bitwarden til at sikre arbejdsstyrken og dele sensitive oplysninger med kolleger. -More reasons to choose Bitwarden: +Flere grunde til at vælge Bitwarden: -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Kryptering i verdensklasse +Adgangskoder er beskyttet med avanceret ende-til-ende-kryptering (AES-256 bit, saltet hashtag og PBKDF2 SHA-256), så dataene forbliver sikre og private. -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. +Tredjepartsrevisioner +Bitwarden udfører regelmæssigt omfattende tredjeparts sikkerhedsrevisioner med velrenommerede sikkerhedsfirmaer. Disse årlige revisioner inkluderer kildekodevurderinger og penetrationstest på tværs af Bitwarden IP'er, servere og webapplikationer. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Avanceret 2FA +Sikre dit login med en tredjepartsgodkendelse, e-mailede koder eller FIDO2 WebAuthn-legitimationsoplysninger, såsom en hardwaresikkerhedsnøgle eller adgangsnøgle. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +Overfør data direkte til andre, mens ende-til-ende krypteret sikkerhed opretholdes og begrænser eksponeringen. -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. +Indbygget generator +Opret lange, komplekse og distinkte adgangskoder og unikke brugernavne til hvert websted, som besøges. Integrér med udbydere af e-mailalias for yderligere fortrolighed. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +Globale oversættelser +Bitwarden-lokaliseringer findes til flere end 60 sprog, oversat af det globale fællesskab via Crowdin. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +Applikationer på tværs af platforme +Sikr og del følsomme data i Bitwarden Vault fra enhver webbrowser, mobilenhed eller computer-OS og mere. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden sikrer mere end blot adgangskoder +Ende-til-ende krypterede legitimationshåndteringsløsninger fra Bitwarden giver organisationer mulighed for at sikre alt, herunder udviklerhemmeligheder og adgangsnøgleoplevelser. Besøg Bitwarden.com for at læse mere om Bitwarden Secrets Manager og Bitwarden Passwordless.dev! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Hjemme, på arbejde eller på farten sikrer Bitwarden nemt alle adgangskoder, adgangskort og sensitive oplysninger. Synkroniser og få adgang til din boks fra flere enheder diff --git a/apps/browser/store/locales/gl/copy.resx b/apps/browser/store/locales/gl/copy.resx index 0fdb224988a..efcbcda29e6 100644 --- a/apps/browser/store/locales/gl/copy.resx +++ b/apps/browser/store/locales/gl/copy.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden – Xestor de contrasinais - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + En casa, no traballo ou mentres estás a viaxar, Bitwarden protexe doadamente todos os teus contrasinais, chaves de paso, e información sensíbel. 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 - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + En casa, no traballo ou mentres estás a viaxar, Bitwarden protexe doadamente todos os teus contrasinais, chaves de paso, e información sensíbel. Sincroniza e accede á túa caixa forte desde múltiples dispositivos diff --git a/apps/browser/store/locales/ru/copy.resx b/apps/browser/store/locales/ru/copy.resx index 212a899f76e..7c9480567e8 100644 --- a/apps/browser/store/locales/ru/copy.resx +++ b/apps/browser/store/locales/ru/copy.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden - Менеджер паролей - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Дома, на работе или в пути - Bitwarden всегда защитит ваши пароли, passkeys и конфиденциальную информацию. 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 - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Дома, на работе или в пути - Bitwarden всегда защитит ваши пароли, passkeys и конфиденциальную информацию. Синхронизация и доступ к хранилищу с нескольких устройств diff --git a/apps/browser/store/locales/uk/copy.resx b/apps/browser/store/locales/uk/copy.resx index 5a7de183636..e74811a7751 100644 --- a/apps/browser/store/locales/uk/copy.resx +++ b/apps/browser/store/locales/uk/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden – менеджер паролів - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Вдома, на роботі чи в дорозі, Bitwarden захищає ваші паролі, ключі доступу та конфіденційну інформацію. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + Визнаний найкращим менеджером паролів за версією 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 -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. +РОЗШИРЮЙТЕ МОЖЛИВОСТІ СВОЇХ КОМАНД ЗА ДОПОМОГОЮ BITWARDEN +Плани для Команд та Підприємства містять професійні бізнес-функції. Деякі приклади включають інтеграцію SSO, самостійний хостинг, інтеграцію каталогів і забезпечення SCIM, глобальні політики, доступ до API, журнали подій і багато іншого. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Використовуйте Bitwarden, щоб захистити своїх співробітників і ділитися конфіденційною інформацією з колегами. -More reasons to choose Bitwarden: +Більше причин вибрати Bitwarden: -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +Шифрування світового класу +Паролі захищені вдосконаленим наскрізним шифруванням (біт AES-256, солоний хештег і PBKDF2 SHA-256), тому ваші дані залишаються надійно захищеними та конфіденційними. -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. +Аудит третьої сторони +Bitwarden регулярно проводить комплексні сторонні аудити безпеки з відомими компаніями, що займаються безпекою. Ці щорічні аудити включають оцінку вихідного коду та тестування на проникнення на всіх IP-адресах, серверах і веб-додатках Bitwarden. -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +Розширений 2FA +Захистіть свій вхід за допомогою стороннього автентифікатора, кодів, надісланих електронною поштою, або облікових даних FIDO2 WebAuthn, наприклад, апаратного ключа безпеки або пароля. 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 Vault та діліться ними з будь-якого браузера, мобільного пристрою, настільної операційної системи тощо. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +Bitwarden захищає більше, ніж просто паролі +Наскрізні рішення для управління зашифрованими обліковими даними від Bitwarden дозволяють організаціям захистити все, включаючи секрети розробників і досвід роботи з ключами. Відвідайте Bitwarden.com, щоб дізнатися більше про Bitwarden Secrets Manager і Bitwarden Passwordless.dev! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Вдома, на роботі чи в дорозі, Bitwarden захищає ваші паролі, ключі доступу та конфіденційну інформацію. Синхронізуйте й отримуйте доступ до свого сховища на різних пристроях diff --git a/apps/cli/package.json b/apps/cli/package.json index b57b818c63b..c427947bd32 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -71,7 +71,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.18", + "tldts": "6.1.20", "zxcvbn": "4.4.2" } } diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index d7ce250a715..31de1981947 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -116,6 +116,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider"; import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/services/collection.service"; @@ -272,7 +273,7 @@ export class Main { this.secureStorageService = new NodeEnvSecureStorageService( this.storageService, this.logService, - () => this.cryptoService, + this.encryptService, ); this.memoryStorageService = new MemoryStorageService(); @@ -403,12 +404,32 @@ export class Main { " (" + this.platformUtilsService.getDeviceString().toUpperCase() + ")"; + + this.biometricStateService = new DefaultBiometricStateService(this.stateProvider); + this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); + + this.organizationService = new OrganizationService(this.stateProvider); + this.policyService = new PolicyService(this.stateProvider, this.organizationService); + + this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService( + this.accountService, + this.pinService, + this.userDecryptionOptionsService, + this.cryptoService, + this.tokenService, + this.policyService, + this.biometricStateService, + this.stateProvider, + this.logService, + VaultTimeoutStringType.Never, // default vault timeout + ); + this.apiService = new NodeApiService( this.tokenService, this.platformUtilsService, this.environmentService, this.appIdService, - this.stateService, + this.vaultTimeoutSettingsService, async (expired: boolean) => await this.logout(), customUserAgent, ); @@ -454,12 +475,8 @@ export class Main { this.providerService = new ProviderService(this.stateProvider); - this.organizationService = new OrganizationService(this.stateProvider); - this.organizationUserService = new OrganizationUserServiceImplementation(this.apiService); - this.policyService = new PolicyService(this.stateProvider, this.organizationService); - this.policyApiService = new PolicyApiService(this.policyService, this.apiService); this.keyConnectorService = new KeyConnectorService( @@ -489,8 +506,6 @@ export class Main { this.stateService, ); - this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); - this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); this.deviceTrustService = new DeviceTrustService( this.keyGenerationService, @@ -543,6 +558,7 @@ export class Main { this.userDecryptionOptionsService, this.globalStateProvider, this.billingAccountProfileStateService, + this.vaultTimeoutSettingsService, this.kdfConfigService, ); @@ -590,19 +606,6 @@ export class Main { const lockedCallback = async (userId?: string) => await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto); - this.biometricStateService = new DefaultBiometricStateService(this.stateProvider); - - this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService( - this.accountService, - this.pinService, - this.userDecryptionOptionsService, - this.cryptoService, - this.tokenService, - this.policyService, - this.stateService, - this.biometricStateService, - ); - this.userVerificationService = new UserVerificationService( this.stateService, this.cryptoService, @@ -661,6 +664,7 @@ export class Main { async (expired: boolean) => await this.logout(), this.billingAccountProfileStateService, this.tokenService, + this.authService, ); this.totpService = new TotpService(this.cryptoFunctionService, this.logService); diff --git a/apps/cli/src/locales/en/messages.json b/apps/cli/src/locales/en/messages.json index 8364e0b3280..3f753781127 100644 --- a/apps/cli/src/locales/en/messages.json +++ b/apps/cli/src/locales/en/messages.json @@ -58,5 +58,107 @@ }, "errorAssigningTargetFolder": { "message": "Error assigning target folder." + }, + "forwarderError": { + "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "description": "Reports an error returned by a forwarding service to the user.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Invalid characters in domain name." + } + } + }, + "forwarderGeneratedBy": { + "message": "Generated by Bitwarden.", + "description": "Displayed with the address on the forwarding service's configuration screen." + }, + "forwarderGeneratedByWithWebsite": { + "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "description": "Displayed with the address on the forwarding service's configuration screen.", + "placeholders": { + "WEBSITE": { + "content": "$1", + "example": "www.example.com" + } + } + }, + "forwaderInvalidToken": { + "message": "Invalid $SERVICENAME$ API token", + "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidTokenWithMessage": { + "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, + "forwarderNoAccountId": { + "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "description": "Displayed when the forwarding service fails to return an account ID.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderNoDomain": { + "message": "Invalid $SERVICENAME$ domain.", + "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderNoUrl": { + "message": "Invalid $SERVICENAME$ url.", + "description": "Displayed when the url of the forwarding service wasn't supplied.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderUnknownError": { + "message": "Unknown $SERVICENAME$ error occurred.", + "description": "Displayed when the forwarding service failed due to an unknown error.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderUnknownForwarder": { + "message": "Unknown forwarder: '$SERVICENAME$'.", + "description": "Displayed when the forwarding service is not supported.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "JustTrust.us" + } + } } } diff --git a/apps/cli/src/platform/services/node-api.service.ts b/apps/cli/src/platform/services/node-api.service.ts index 9099fd27603..4849aef1512 100644 --- a/apps/cli/src/platform/services/node-api.service.ts +++ b/apps/cli/src/platform/services/node-api.service.ts @@ -2,11 +2,11 @@ import * as FormData from "form-data"; import { HttpsProxyAgent } from "https-proxy-agent"; import * as fe from "node-fetch"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { ApiService } from "@bitwarden/common/services/api.service"; (global as any).fetch = fe.default; @@ -21,7 +21,7 @@ export class NodeApiService extends ApiService { platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, appIdService: AppIdService, - stateService: StateService, + vaultTimeoutSettingsService: VaultTimeoutSettingsService, logoutCallback: (expired: boolean) => Promise, customUserAgent: string = null, ) { @@ -30,7 +30,7 @@ export class NodeApiService extends ApiService { platformUtilsService, environmentService, appIdService, - stateService, + vaultTimeoutSettingsService, logoutCallback, customUserAgent, ); diff --git a/apps/cli/src/platform/services/node-env-secure-storage.service.ts b/apps/cli/src/platform/services/node-env-secure-storage.service.ts index 2364553d2a3..2ab18b6c463 100644 --- a/apps/cli/src/platform/services/node-env-secure-storage.service.ts +++ b/apps/cli/src/platform/services/node-env-secure-storage.service.ts @@ -1,6 +1,6 @@ import { throwError } from "rxjs"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -11,7 +11,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { constructor( private storageService: AbstractStorageService, private logService: LogService, - private cryptoService: () => CryptoService, + private encryptService: EncryptService, ) {} get valuesRequireDeserialization(): boolean { @@ -59,7 +59,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { if (sessionKey == null) { throw new Error("No session key available."); } - const encValue = await this.cryptoService().encryptToBytes( + const encValue = await this.encryptService.encryptToBytes( Utils.fromB64ToArray(plainValue), sessionKey, ); @@ -78,7 +78,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } const encBuf = EncArrayBuffer.fromB64(encValue); - const decValue = await this.cryptoService().decryptFromBytes(encBuf, sessionKey); + const decValue = await this.encryptService.decryptToBytes(encBuf, sessionKey); if (decValue == null) { this.logService.info("Failed to decrypt."); return null; diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 80f634d0c5f..ade019b9fba 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -24,6 +24,11 @@ import { KeySuffixOptions, ThemeType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { + VaultTimeout, + VaultTimeoutOption, + VaultTimeoutStringType, +} from "@bitwarden/common/types/vault-timeout.type"; import { DialogService } from "@bitwarden/components"; import { SetPinComponent } from "../../auth/components/set-pin.component"; @@ -41,7 +46,7 @@ export class SettingsComponent implements OnInit { protected readonly VaultTimeoutAction = VaultTimeoutAction; showMinToTray = false; - vaultTimeoutOptions: any[]; + vaultTimeoutOptions: VaultTimeoutOption[]; localeOptions: any[]; themeOptions: any[]; clearClipboardOptions: any[]; @@ -72,14 +77,14 @@ export class SettingsComponent implements OnInit { timeout: { hours: number; minutes: number }; action: "lock" | "logOut"; }>; - previousVaultTimeout: number = null; + previousVaultTimeout: VaultTimeout = null; userHasMasterPassword: boolean; userHasPinSet: boolean; form = this.formBuilder.group({ // Security - vaultTimeout: [null as number | null], + vaultTimeout: [null as VaultTimeout | null], vaultTimeoutAction: [VaultTimeoutAction.Lock], pin: [null as boolean | null], biometric: false, @@ -159,24 +164,26 @@ export class SettingsComponent implements OnInit { this.showDuckDuckGoIntegrationOption = isMac; this.vaultTimeoutOptions = [ - // { name: i18nService.t('immediately'), value: 0 }, { name: this.i18nService.t("oneMinute"), value: 1 }, { name: this.i18nService.t("fiveMinutes"), value: 5 }, { name: this.i18nService.t("fifteenMinutes"), value: 15 }, { name: this.i18nService.t("thirtyMinutes"), value: 30 }, { name: this.i18nService.t("oneHour"), value: 60 }, { name: this.i18nService.t("fourHours"), value: 240 }, - { name: this.i18nService.t("onIdle"), value: -4 }, - { name: this.i18nService.t("onSleep"), value: -3 }, + { name: this.i18nService.t("onIdle"), value: VaultTimeoutStringType.OnIdle }, + { name: this.i18nService.t("onSleep"), value: VaultTimeoutStringType.OnSleep }, ]; if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) { - this.vaultTimeoutOptions.push({ name: this.i18nService.t("onLocked"), value: -2 }); + this.vaultTimeoutOptions.push({ + name: this.i18nService.t("onLocked"), + value: VaultTimeoutStringType.OnLocked, + }); } this.vaultTimeoutOptions = this.vaultTimeoutOptions.concat([ - { name: this.i18nService.t("onRestart"), value: -1 }, - { name: this.i18nService.t("never"), value: null }, + { name: this.i18nService.t("onRestart"), value: VaultTimeoutStringType.OnRestart }, + { name: this.i18nService.t("never"), value: VaultTimeoutStringType.Never }, ]); const localeOptions: any[] = []; @@ -251,10 +258,14 @@ export class SettingsComponent implements OnInit { // Load initial values this.userHasPinSet = await this.pinService.isPinSet(userId); + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + const initialValues = { - vaultTimeout: await this.vaultTimeoutSettingsService.getVaultTimeout(), + vaultTimeout: await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(activeAccount.id), + ), vaultTimeoutAction: await firstValueFrom( - this.vaultTimeoutSettingsService.vaultTimeoutAction$(), + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), ), pin: this.userHasPinSet, biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(), @@ -299,7 +310,9 @@ export class SettingsComponent implements OnInit { this.refreshTimeoutSettings$ .pipe( - switchMap(() => this.vaultTimeoutSettingsService.vaultTimeoutAction$()), + switchMap(() => + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id), + ), takeUntil(this.destroy$), ) .subscribe((action) => { @@ -357,8 +370,8 @@ export class SettingsComponent implements OnInit { }); } - async saveVaultTimeout(newValue: number) { - if (newValue == null) { + async saveVaultTimeout(newValue: VaultTimeout) { + if (newValue === VaultTimeoutStringType.Never) { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "warning" }, content: { key: "neverLockWarning" }, @@ -387,7 +400,10 @@ export class SettingsComponent implements OnInit { this.previousVaultTimeout = this.form.value.vaultTimeout; + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( + activeAccount.id, newValue, this.form.value.vaultTimeoutAction, ); @@ -418,7 +434,10 @@ export class SettingsComponent implements OnInit { return; } + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( + activeAccount.id, this.form.value.vaultTimeout, newValue, ); diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 056fb3f51e6..e77ef8d3f07 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -38,9 +38,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; +import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UserId } from "@bitwarden/common/types/guid"; +import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -64,12 +66,6 @@ const BroadcasterSubscriptionId = "AppComponent"; const IdleTimeout = 60000 * 10; // 10 minutes const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours -const systemTimeoutOptions = { - onLock: -2, - onSuspend: -3, - onIdle: -4, -}; - @Component({ selector: "app-root", styles: [], @@ -401,6 +397,8 @@ export class AppComponent implements OnInit, OnDestroy { this.router.navigate(["/remove-password"]); break; case "switchAccount": { + // Clear sequentialized caches + clearCaches(); if (message.userId != null) { await this.stateService.clearDecryptedData(message.userId); await this.accountService.switchAccount(message.userId); @@ -430,13 +428,13 @@ export class AppComponent implements OnInit, OnDestroy { break; } case "systemSuspended": - await this.checkForSystemTimeout(systemTimeoutOptions.onSuspend); + await this.checkForSystemTimeout(VaultTimeoutStringType.OnSleep); break; case "systemLocked": - await this.checkForSystemTimeout(systemTimeoutOptions.onLock); + await this.checkForSystemTimeout(VaultTimeoutStringType.OnLocked); break; case "systemIdle": - await this.checkForSystemTimeout(systemTimeoutOptions.onIdle); + await this.checkForSystemTimeout(VaultTimeoutStringType.OnIdle); break; case "openLoginApproval": if (message.notificationId != null) { @@ -721,7 +719,7 @@ export class AppComponent implements OnInit, OnDestroy { } } - private async checkForSystemTimeout(timeout: number): Promise { + private async checkForSystemTimeout(timeout: VaultTimeout): Promise { const accounts = await firstValueFrom(this.accountService.accounts$); for (const userId in accounts) { if (userId == null) { @@ -738,9 +736,13 @@ export class AppComponent implements OnInit, OnDestroy { } } - private async getVaultTimeoutOptions(userId: string): Promise<[number, string]> { - const timeout = await this.stateService.getVaultTimeout({ userId: userId }); - const action = await this.stateService.getVaultTimeoutAction({ userId: userId }); + private async getVaultTimeoutOptions(userId: string): Promise<[VaultTimeout, string]> { + const timeout = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(userId), + ); + const action = await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(userId), + ); return [timeout, action]; } diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index a5f62804aae..8d800970535 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -14,6 +14,7 @@ import { SYSTEM_THEME_OBSERVABLE, SafeInjectionToken, STATE_FACTORY, + DEFAULT_VAULT_TIMEOUT, INTRAPROCESS_MESSAGING_SUBJECT, CLIENT_TYPE, } from "@bitwarden/angular/services/injection-tokens"; @@ -56,6 +57,7 @@ import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/s // eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; +import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService } from "@bitwarden/components"; @@ -138,6 +140,10 @@ const safeProviders: SafeProvider[] = [ provide: SUPPORTS_SECURE_STORAGE, useValue: ELECTRON_SUPPORTS_SECURE_STORAGE, }), + safeProvider({ + provide: DEFAULT_VAULT_TIMEOUT, + useValue: VaultTimeoutStringType.OnRestart, + }), safeProvider({ provide: I18nServiceAbstraction, useClass: I18nRendererService, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index ff9cbc97ccb..9348b8dc0fa 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2132,6 +2132,108 @@ "forwardedEmailDesc": { "message": "Generate an email alias with an external forwarding service." }, + "forwarderError": { + "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "description": "Reports an error returned by a forwarding service to the user.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Invalid characters in domain name." + } + } + }, + "forwarderGeneratedBy": { + "message": "Generated by Bitwarden.", + "description": "Displayed with the address on the forwarding service's configuration screen." + }, + "forwarderGeneratedByWithWebsite": { + "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "description": "Displayed with the address on the forwarding service's configuration screen.", + "placeholders": { + "WEBSITE": { + "content": "$1", + "example": "www.example.com" + } + } + }, + "forwaderInvalidToken": { + "message": "Invalid $SERVICENAME$ API token", + "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwaderInvalidTokenWithMessage": { + "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + }, + "errormessage": { + "content": "$2", + "example": "Please verify your email address to continue." + } + } + }, + "forwarderNoAccountId": { + "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "description": "Displayed when the forwarding service fails to return an account ID.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderNoDomain": { + "message": "Invalid $SERVICENAME$ domain.", + "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderNoUrl": { + "message": "Invalid $SERVICENAME$ url.", + "description": "Displayed when the url of the forwarding service wasn't supplied.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderUnknownError": { + "message": "Unknown $SERVICENAME$ error occurred.", + "description": "Displayed when the forwarding service failed due to an unknown error.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "SimpleLogin" + } + } + }, + "forwarderUnknownForwarder": { + "message": "Unknown forwarder: '$SERVICENAME$'.", + "description": "Displayed when the forwarding service is not supported.", + "placeholders": { + "servicename": { + "content": "$1", + "example": "JustTrust.us" + } + } + }, "hostname": { "message": "Hostname", "description": "Part of a URL." diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index f7df93bdd72..d509a91d957 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -479,7 +479,7 @@ "message": "El tamaño máximo de archivo es de 500MB." }, "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "message": "Se requiere migración de la clave de cifrado. Por favor, inicia sesión a través de la caja fuerte web para actualizar su clave de cifrado." }, "editedFolder": { "message": "Carpeta editada" @@ -561,10 +561,10 @@ "message": "¡Tu nueva cuenta ha sido creada! Ahora puedes acceder." }, "youSuccessfullyLoggedIn": { - "message": "You successfully logged in" + "message": "Has iniciado sesión correctamente" }, "youMayCloseThisWindow": { - "message": "You may close this window" + "message": "Puedes cerrar esta ventana" }, "masterPassSent": { "message": "Te hemos enviado un correo electrónico con la pista de tu contraseña maestra." @@ -801,10 +801,10 @@ "message": "Cambiar contraseña maestra" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "¿Continuar a la aplicación web?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Puedes cambiar tu contraseña maestra en la aplicación web de Bitwarden." }, "fingerprintPhrase": { "message": "Frase de huella digital", @@ -1090,7 +1090,7 @@ "message": "1GB de espacio en disco cifrado." }, "premiumSignUpTwoStepOptions": { - "message": "Proprietary two-step login options such as YubiKey and Duo." + "message": "Opciones de inicio de sesión con autenticación de dos pasos propietarios como YubiKey y Duo." }, "premiumSignUpReports": { "message": "Higiene de contraseña, salud de la cuenta e informes de violaciones de datos para mantener tu caja fuerte segura." @@ -1402,7 +1402,7 @@ "message": "Código PIN inválido." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "Demasiados intentos de entrada de PIN no válidos. Cerrando sesión." }, "unlockWithWindowsHello": { "message": "Desbloquear con Windows Hello" @@ -1553,11 +1553,11 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "Tu organización requiere que establezcas una contraseña maestra.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "verificationRequired": { - "message": "Verification required", + "message": "Verificación requerida", "description": "Default title for the user verification dialog." }, "currentMasterPass": { @@ -1633,10 +1633,10 @@ "message": "La integración con el navegador no está soportada" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Error al habilitar la integración del navegador" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Se ha producido un error mientras se habilitaba la integración del navegador." }, "browserIntegrationMasOnlyDesc": { "message": "Por desgracia la integración del navegador sólo está soportada por ahora en la versión de la Mac App Store." @@ -1654,7 +1654,7 @@ "message": "Requiere una capa adicional de seguridad mediante el solicitar la frase de validación de huella dactilar al establecer un enlace entre el escritorio y el navegador. Cuando se activa, requiere intervención del usuario y verificación cada vez que se establece una conexión." }, "enableHardwareAcceleration": { - "message": "Use hardware acceleration" + "message": "Utilizar aceleración de hardware" }, "enableHardwareAccelerationDesc": { "message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required." @@ -1898,16 +1898,16 @@ "message": "Su contraseña maestra no cumple con una o más de las políticas de su organización. Para acceder a la caja fuerte, debe actualizar su contraseña maestra ahora. Proceder le desconectará de su sesión actual, requiriendo que vuelva a iniciar sesión. Las sesiones activas en otros dispositivos pueden seguir estando activas durante hasta una hora." }, "tryAgain": { - "message": "Try again" + "message": "Intentar de nuevo" }, "verificationRequiredForActionSetPinToContinue": { "message": "Verification required for this action. Set a PIN to continue." }, "setPin": { - "message": "Set PIN" + "message": "Establecer PIN" }, "verifyWithBiometrics": { - "message": "Verify with biometrics" + "message": "Verificar biométricamente" }, "awaitingConfirmation": { "message": "Awaiting confirmation" diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 04b7e4cf298..f947d7dae7c 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -2698,7 +2698,7 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Успех" }, "troubleshooting": { "message": "Решавање проблема" diff --git a/apps/desktop/src/models/account.ts b/apps/desktop/src/models/account.ts index 0291fdeb28c..b3d31284139 100644 --- a/apps/desktop/src/models/account.ts +++ b/apps/desktop/src/models/account.ts @@ -4,7 +4,6 @@ import { } from "@bitwarden/common/platform/models/domain/account"; export class AccountSettings extends BaseAccountSettings { - vaultTimeout = -1; // On Restart dismissedBiometricRequirePasswordOnStartCallout?: boolean; } diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index 52cba4f2aea..dff549ba6e7 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -273,12 +273,13 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { // If the current user is not already in the group and cannot add themselves, remove them from the list if (restrictGroupAccess) { - const organizationUserId = this.members.find((m) => m.userId === activeAccount.id).id; + // organizationUserId may be null if accessing via a provider + const organizationUserId = this.members.find((m) => m.userId === activeAccount.id)?.id; const isAlreadyInGroup = this.groupForm.value.members.some( (m) => m.id === organizationUserId, ); - if (!isAlreadyInGroup) { + if (organizationUserId != null && !isAlreadyInGroup) { this.members = this.members.filter((m) => m.id !== organizationUserId); } } diff --git a/apps/web/src/app/auth/anon-layout-wrapper.component.ts b/apps/web/src/app/auth/anon-layout-wrapper.component.ts index e39a8e11a99..c89b05a3c65 100644 --- a/apps/web/src/app/auth/anon-layout-wrapper.component.ts +++ b/apps/web/src/app/auth/anon-layout-wrapper.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component } from "@angular/core"; import { ActivatedRoute, RouterModule } from "@angular/router"; import { AnonLayoutComponent } from "@bitwarden/auth/angular"; @@ -10,7 +10,7 @@ import { Icon } from "@bitwarden/components"; templateUrl: "anon-layout-wrapper.component.html", imports: [AnonLayoutComponent, RouterModule], }) -export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { +export class AnonLayoutWrapperComponent { protected pageTitle: string; protected pageSubtitle: string; protected pageIcon: Icon; @@ -23,12 +23,4 @@ export class AnonLayoutWrapperComponent implements OnInit, OnDestroy { this.pageSubtitle = this.i18nService.t(this.route.snapshot.firstChild.data["pageSubtitle"]); this.pageIcon = this.route.snapshot.firstChild.data["pageIcon"]; // don't translate } - - ngOnInit() { - document.body.classList.add("layout_frontend"); - } - - ngOnDestroy() { - document.body.classList.remove("layout_frontend"); - } } diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 198332db0c4..a0db7b5a200 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -5,8 +5,8 @@ import { concatMap, firstValueFrom, lastValueFrom, Observable, Subject, takeUnti import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; -import { OrganizationApiKeyType, ProviderType } from "@bitwarden/common/admin-console/enums"; +import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; +import { OrganizationApiKeyType, ProviderStatusType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { PlanType } from "@bitwarden/common/billing/enums"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; @@ -70,7 +70,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy private route: ActivatedRoute, private dialogService: DialogService, private configService: ConfigService, - private providerService: ProviderApiServiceAbstraction, + private providerService: ProviderService, ) {} async ngOnInit() { @@ -108,15 +108,18 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy this.loading = true; this.locale = await firstValueFrom(this.i18nService.locale$); this.userOrg = await this.organizationService.get(this.organizationId); - if (this.userOrg.hasProvider) { - const provider = await this.providerService.getProvider(this.userOrg.providerId); - const enableConsolidatedBilling = await firstValueFrom(this.enableConsolidatedBilling$); - this.isProviderManaged = provider.type == ProviderType.Msp && enableConsolidatedBilling; - } if (this.userOrg.canViewSubscription) { + const enableConsolidatedBilling = await firstValueFrom(this.enableConsolidatedBilling$); + const provider = await this.providerService.get(this.userOrg.providerId); + this.isProviderManaged = + enableConsolidatedBilling && + this.userOrg.hasProvider && + provider.providerStatus == ProviderStatusType.Billable; + this.sub = await this.organizationApiService.getSubscription(this.organizationId); this.lineItems = this.sub?.subscription?.items; + if (this.lineItems && this.lineItems.length) { this.lineItems = this.lineItems .map((item) => { diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index c60280014c3..a7578d0ae22 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -13,6 +13,7 @@ import { OBSERVABLE_DISK_LOCAL_STORAGE, WINDOW, SafeInjectionToken, + DEFAULT_VAULT_TIMEOUT, CLIENT_TYPE, } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; @@ -41,6 +42,7 @@ import { DefaultThemeStateService, ThemeStateService, } from "@bitwarden/common/platform/theming/theme-state.service"; +import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { PolicyListService } from "../admin-console/core/policy-list.service"; import { HtmlStorageService } from "../core/html-storage.service"; @@ -69,6 +71,12 @@ const safeProviders: SafeProvider[] = [ safeProvider(RouterService), safeProvider(EventService), safeProvider(PolicyListService), + safeProvider({ + provide: DEFAULT_VAULT_TIMEOUT, + deps: [PlatformUtilsServiceAbstraction], + useFactory: (platformUtilsService: PlatformUtilsServiceAbstraction): VaultTimeout => + platformUtilsService.isDev() ? VaultTimeoutStringType.Never : 15, + }), safeProvider({ provide: APP_INITIALIZER as SafeInjectionToken<() => void>, useFactory: (initService: InitService) => initService.init(), diff --git a/apps/web/src/app/core/state/account.ts b/apps/web/src/app/core/state/account.ts index 0cb16505e30..b6beafe31ff 100644 --- a/apps/web/src/app/core/state/account.ts +++ b/apps/web/src/app/core/state/account.ts @@ -1,20 +1,8 @@ -import { - Account as BaseAccount, - AccountSettings as BaseAccountSettings, -} from "@bitwarden/common/platform/models/domain/account"; - -export class AccountSettings extends BaseAccountSettings { - vaultTimeout: number = process.env.NODE_ENV === "development" ? null : 15; -} +import { Account as BaseAccount } from "@bitwarden/common/platform/models/domain/account"; +// TODO: platform to clean up accounts in later PR export class Account extends BaseAccount { - settings?: AccountSettings = new AccountSettings(); - constructor(init: Partial) { super(init); - Object.assign(this.settings, { - ...new AccountSettings(), - ...this.settings, - }); } } diff --git a/apps/web/src/app/settings/preferences.component.ts b/apps/web/src/app/settings/preferences.component.ts index 7f9eabb6b3b..a6443b453ef 100644 --- a/apps/web/src/app/settings/preferences.component.ts +++ b/apps/web/src/app/settings/preferences.component.ts @@ -5,6 +5,7 @@ import { concatMap, filter, firstValueFrom, map, Observable, Subject, takeUntil, import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -12,6 +13,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ThemeType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { + VaultTimeout, + VaultTimeoutOption, + VaultTimeoutStringType, +} from "@bitwarden/common/types/vault-timeout.type"; import { DialogService } from "@bitwarden/components"; @Component({ @@ -28,7 +34,7 @@ export class PreferencesComponent implements OnInit { timeout: { hours: number; minutes: number }; action: VaultTimeoutAction; }>; - vaultTimeoutOptions: { name: string; value: number }[]; + vaultTimeoutOptions: VaultTimeoutOption[]; localeOptions: any[]; themeOptions: any[]; @@ -36,7 +42,7 @@ export class PreferencesComponent implements OnInit { private destroy$ = new Subject(); form = this.formBuilder.group({ - vaultTimeout: [null as number | null], + vaultTimeout: [null as VaultTimeout | null], vaultTimeoutAction: [VaultTimeoutAction.Lock], enableFavicons: true, theme: [ThemeType.Light], @@ -52,6 +58,7 @@ export class PreferencesComponent implements OnInit { private themeStateService: ThemeStateService, private domainSettingsService: DomainSettingsService, private dialogService: DialogService, + private accountService: AccountService, ) { this.vaultTimeoutOptions = [ { name: i18nService.t("oneMinute"), value: 1 }, @@ -60,10 +67,13 @@ export class PreferencesComponent implements OnInit { { name: i18nService.t("thirtyMinutes"), value: 30 }, { name: i18nService.t("oneHour"), value: 60 }, { name: i18nService.t("fourHours"), value: 240 }, - { name: i18nService.t("onRefresh"), value: -1 }, + { name: i18nService.t("onRefresh"), value: VaultTimeoutStringType.OnRestart }, ]; if (this.platformUtilsService.isDev()) { - this.vaultTimeoutOptions.push({ name: i18nService.t("never"), value: null }); + this.vaultTimeoutOptions.push({ + name: i18nService.t("never"), + value: VaultTimeoutStringType.Never, + }); } const localeOptions: any[] = []; @@ -130,10 +140,15 @@ export class PreferencesComponent implements OnInit { takeUntil(this.destroy$), ) .subscribe(); + + const activeAcct = await firstValueFrom(this.accountService.activeAccount$); + const initialFormValues = { - vaultTimeout: await this.vaultTimeoutSettingsService.getVaultTimeout(), + vaultTimeout: await firstValueFrom( + this.vaultTimeoutSettingsService.getVaultTimeoutByUserId$(activeAcct.id), + ), vaultTimeoutAction: await firstValueFrom( - this.vaultTimeoutSettingsService.vaultTimeoutAction$(), + this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAcct.id), ), enableFavicons: await firstValueFrom(this.domainSettingsService.showFavicons$), theme: await firstValueFrom(this.themeStateService.selectedTheme$), @@ -154,7 +169,10 @@ export class PreferencesComponent implements OnInit { } const values = this.form.value; + const activeAcct = await firstValueFrom(this.accountService.activeAccount$); + await this.vaultTimeoutSettingsService.setVaultTimeoutOptions( + activeAcct.id, values.vaultTimeout, values.vaultTimeoutAction, ); diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts index 4e95bb4bcc3..f386665186f 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts @@ -3,7 +3,6 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angula import { AbstractControl, FormBuilder, Validators } from "@angular/forms"; import { combineLatest, - from, map, Observable, of, @@ -23,7 +22,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { CollectionResponse } from "@bitwarden/common/vault/models/response/collection.response"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { BitValidators, DialogService } from "@bitwarden/components"; @@ -56,7 +54,10 @@ export interface CollectionDialogParams { initialTab?: CollectionDialogTabType; parentCollectionId?: string; showOrgSelector?: boolean; - collectionIds?: string[]; + /** + * Flag to limit the nested collections to only those the user has explicit CanManage access too. + */ + limitNestedCollections?: boolean; readonly?: boolean; } @@ -85,7 +86,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { protected tabIndex: CollectionDialogTabType; protected loading = true; protected organization?: Organization; - protected collection?: CollectionView; + protected collection?: CollectionAdminView; protected nestOptions: CollectionView[] = []; protected accessItems: AccessItemView[] = []; protected deletedParentName: string | undefined; @@ -107,7 +108,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private organizationService: OrganizationService, private groupService: GroupService, private collectionAdminService: CollectionAdminService, - private collectionService: CollectionService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private organizationUserService: OrganizationUserService, @@ -124,7 +124,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { this.showOrgSelector = true; this.formGroup.controls.selectedOrg.valueChanges .pipe(takeUntil(this.destroy$)) - .subscribe((id) => this.loadOrg(id, this.params.collectionIds)); + .subscribe((id) => this.loadOrg(id)); this.organizations$ = this.organizationService.organizations$.pipe( first(), map((orgs) => @@ -138,11 +138,11 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { } else { // Opened from the org vault this.formGroup.patchValue({ selectedOrg: this.params.organizationId }); - await this.loadOrg(this.params.organizationId, this.params.collectionIds); + await this.loadOrg(this.params.organizationId); } } - async loadOrg(orgId: string, collectionIds: string[]) { + async loadOrg(orgId: string) { const organization$ = this.organizationService .get$(orgId) .pipe(shareReplay({ refCount: true, bufferSize: 1 })); @@ -158,28 +158,14 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { combineLatest({ organization: organization$, collections: this.collectionAdminService.getAll(orgId), - collectionDetails: this.params.collectionId - ? from(this.collectionAdminService.get(orgId, this.params.collectionId)) - : of(null), groups: groups$, // Collection(s) needed to map readonlypermission for (potential) access selector disabled state users: this.organizationUserService.getAllUsers(orgId, { includeCollections: true }), - collection: this.params.collectionId - ? this.collectionService.get(this.params.collectionId) - : of(null), flexibleCollectionsV1: this.flexibleCollectionsV1Enabled$, }) .pipe(takeUntil(this.formGroup.controls.selectedOrg.valueChanges), takeUntil(this.destroy$)) .subscribe( - ({ - organization, - collections, - collectionDetails, - groups, - users, - collection, - flexibleCollectionsV1, - }) => { + ({ organization, collections: allCollections, groups, users, flexibleCollectionsV1 }) => { this.organization = organization; this.accessItems = [].concat( groups.map((group) => mapGroupToAccessItemView(group, this.collectionId)), @@ -189,35 +175,48 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { // Force change detection to update the access selector's items this.changeDetectorRef.detectChanges(); - if (collectionIds) { - collections = collections.filter((c) => collectionIds.includes(c.id)); - } + this.nestOptions = this.params.limitNestedCollections + ? allCollections.filter((c) => c.manage) + : allCollections; if (this.params.collectionId) { - this.collection = collections.find((c) => c.id === this.collectionId); - this.nestOptions = collections.filter((c) => c.id !== this.collectionId); + this.collection = allCollections.find((c) => c.id === this.collectionId); + // Ensure we don't allow nesting the current collection within itself + this.nestOptions = this.nestOptions.filter((c) => c.id !== this.collectionId); if (!this.collection) { throw new Error("Could not find collection to edit."); } - const { name, parent } = parseName(this.collection); - if (parent !== undefined && !this.nestOptions.find((c) => c.name === parent)) { - this.deletedParentName = parent; + // Parse the name to find its parent name + const { name, parent: parentName } = parseName(this.collection); + + // Determine if the user can see/select the parent collection + if (parentName !== undefined) { + if ( + this.organization.canViewAllCollections && + !allCollections.find((c) => c.name === parentName) + ) { + // The user can view all collections, but the parent was not found -> assume it has been deleted + this.deletedParentName = parentName; + } else if (!this.nestOptions.find((c) => c.name === parentName)) { + // We cannot find the current parent collection in our list of options, so add a placeholder + this.nestOptions.unshift({ name: parentName } as CollectionView); + } } - const accessSelections = mapToAccessSelections(collectionDetails); + const accessSelections = mapToAccessSelections(this.collection); this.formGroup.patchValue({ name, externalId: this.collection.externalId, - parent, + parent: parentName, access: accessSelections, }); - this.collection.manage = collection?.manage ?? false; // Get manage flag from sync data collection - this.showDeleteButton = !this.dialogReadonly && this.collection.canDelete(organization); + this.showDeleteButton = + !this.dialogReadonly && + this.collection.canDelete(organization, flexibleCollectionsV1); } else { - this.nestOptions = collections; - const parent = collections.find((c) => c.id === this.params.parentCollectionId); + const parent = this.nestOptions.find((c) => c.id === this.params.parentCollectionId); const currentOrgUserId = users.data.find( (u) => u.userId === this.organization?.userId, )?.id; diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html index 7bad783f197..48db50e2029 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.html @@ -3,7 +3,7 @@ type="checkbox" bitCheckbox appStopProp - *ngIf="canDeleteCollection" + *ngIf="showCheckbox" [disabled]="disabled" [checked]="checked" (change)="$event ? this.checkedToggled.next() : null" @@ -83,9 +83,7 @@ {{ "access" | i18n }} - + + `, }) export class CollectionAccessRestrictedComponent { protected icon = icon; + protected collectionDialogTabType = CollectionDialogTabType; @Input() canEditCollection = false; + @Input() canViewCollectionInfo = false; - @Output() viewCollectionClicked = new EventEmitter(); - - get buttonText() { - return this.canEditCollection ? "editCollection" : "viewCollection"; - } + @Output() viewCollectionClicked = new EventEmitter<{ + readonly: boolean; + tab: CollectionDialogTabType; + }>(); } diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html index 2e20e696fe6..426f1812c99 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html @@ -28,7 +28,11 @@ - + + + + + diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts new file mode 100644 index 00000000000..81e01a66cbd --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-name.component.ts @@ -0,0 +1,77 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; + +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; +import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, ToastService } from "@bitwarden/components"; + +type ManageClientOrganizationNameParams = { + providerId: string; + organization: { + id: string; + name: string; + seats: number; + }; +}; + +export enum ManageClientOrganizationNameResultType { + Closed = "closed", + Submitted = "submitted", +} + +export const openManageClientOrganizationNameDialog = ( + dialogService: DialogService, + dialogConfig: DialogConfig, +) => + dialogService.open( + ManageClientOrganizationNameComponent, + dialogConfig, + ); + +@Component({ + selector: "app-manage-client-organization-name", + templateUrl: "manage-client-organization-name.component.html", +}) +export class ManageClientOrganizationNameComponent { + protected ResultType = ManageClientOrganizationNameResultType; + protected formGroup = this.formBuilder.group({ + name: [this.dialogParams.organization.name, Validators.required], + }); + + constructor( + @Inject(DIALOG_DATA) protected dialogParams: ManageClientOrganizationNameParams, + private billingApiService: BillingApiServiceAbstraction, + private dialogRef: DialogRef, + private formBuilder: FormBuilder, + private i18nService: I18nService, + private toastService: ToastService, + ) {} + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + const request = new UpdateClientOrganizationRequest(); + request.assignedSeats = this.dialogParams.organization.seats; + request.name = this.formGroup.value.name; + + await this.billingApiService.updateClientOrganization( + this.dialogParams.providerId, + this.dialogParams.organization.id, + request, + ); + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("updatedOrganizationName"), + }); + + this.dialogRef.close(this.ResultType.Submitted); + }; +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts index 2182ac43abc..3b054767778 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts @@ -71,6 +71,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit { const request = new UpdateClientOrganizationRequest(); request.assignedSeats = assignedSeats; + request.name = this.clientName; await this.billingApiService.updateClientOrganization( this.providerId, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html index ec5df609c4f..d2f8ab7a855 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html @@ -78,8 +78,12 @@ appA11yTitle="{{ 'options' | i18n }}" > +