diff --git a/apps/browser/package.json b/apps/browser/package.json index 278a3b6c52..1f54bd64ac 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 6c83d771e9..42c1614022 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 18fc6acca8..6ac5c64ddd 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 44dc85d2b9..fb99a0a95d 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 cb12459a44..609131a66a 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 8156e3c6f1..c0ccd92d7a 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 a7334319b5..53c5c4ed44 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 d9cd14102e..2e282e30ee 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 058378ff17..794e2d995d 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 5ec7b9f483..d43cb2b25d 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 4c90522e1e..57f87d7a8e 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 35100a77d2..58e0f17053 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 de0cfb3f6c..60659eeb65 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 9360524da1..828cca3c85 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 a2ba76b3a6..bbaf77b714 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 a95f11ffa1..9e154437ff 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 4ffbb147be..d460a54ac8 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 0f5b164717..40b127f059 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 4bc1373201..0d4790b271 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 8b09cb224f..6620f33c1e 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 95c77e0c09..7af0c95a1d 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 b536fa9c19..6e52afd7db 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 541541ab54..d53961be29 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 d23f0377bc..0c2ce6114a 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 0a9d8a8106..d8b9b1ec9e 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 042ef96ea2..8b08a8a5c2 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 09388672ff..d00e32b139 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 5647f5d97d..65342fa51b 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 37961ba9a3..d0c317a177 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 9420bca6ef..94e0e47915 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 d1429a40e0..0bfe8a84b3 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 416018cc92..1ee37ad5de 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 fec6ab713c..b45fdef39e 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 f5e3b8d334..275723e0cf 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 8b34a76833..116c87a311 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 6ffbf522d3..c160ae5788 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 d8e42a1c50..c7ec65403d 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 aa4fea96e3..0d7049b855 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 463309789d..f44649d1c3 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 fec6ab713c..b45fdef39e 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 ea7b939f63..8d54140b0e 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 fec6ab713c..b45fdef39e 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 aa01ef2c67..0319dea3a4 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 fec6ab713c..b45fdef39e 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 fec6ab713c..b45fdef39e 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 83e19315e8..b34ce5bf66 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 207c890130..c9ade42eb5 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 26828b348a..3cb55214d6 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 8bcf1a8430..8f02fd076d 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 23d69f44ba..8f0d211056 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 f225e854aa..f4f13a4f55 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 7bcffc04ac..db11a30855 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 170ee146f7..b098546de2 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 3827a9dedd..621bd5953f 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 0939ac0280..96776e7060 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 fec6ab713c..b45fdef39e 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 009685b14b..6318d27189 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 866125dbec..e91397a6e2 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 919066188a..a92a68571e 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 f7a0d50bd5..77ce3ea507 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 706bf7a851..bedd993383 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 9300289795..4519e87981 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 c414300431..83ebcaa11e 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 e56a2d5c38..fbb9075156 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 3fe83f2414..3f152d61b9 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 d27410a5d0..be12d24944 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 e5a3b8f8f5..2650c2db4e 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 51959ba03c..32cfbe416d 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 c9b39c16cc..5c739235dc 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 8ed893e733..a14ef1330c 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 7b273459ad..eef033b364 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 7287e9b285..c3722f2a48 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 6c5ea63eba..5f98d9764c 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 57d7844fde..519f1bda6b 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 57cd500441..bfae93f3d8 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 3c3900144b..2c5f8f2419 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 259d6f154a..2bdc2fd0d4 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 cbc5a34b37..0000000000 --- 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 61768cb970..0000000000 --- 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 823c071a4c..0000000000 --- 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 8b8d82b914..0000000000 --- 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 ee224540c1..0000000000 --- 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 6abe363876..0000000000 --- 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 25fac44450..7e94e84ef5 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 de2fc72747..0b9c8f6fe6 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 49f248c7b8..d77a60d3c7 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 534d8a99c5..5da51618ac 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 5e51e05d77..0666f804f2 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 55bf2468d6..d4ad7209b7 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 0c2a52ed10..c9f970a30c 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 fe3aafe9fb..ad9f526f6c 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 b803b97f92..d42c10a5d8 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 f05c138eab..ea4049ac64 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 5b04f7c1dd..5898dbd04d 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 6ac7af4ce3..618e692aad 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 2a3d507df2..ddbc7ac23f 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 bc08f6a107..060ca51b21 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 775a3edd81..928e54d8ed 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 0fdb224988..efcbcda29e 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 212a899f76..7c9480567e 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 5a7de18363..e74811a775 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 b57b818c63..c427947bd3 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 d7ce250a71..31de198194 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 8364e0b328..3f75378112 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 9099fd2760..4849aef151 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 2364553d2a..2ab18b6c46 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 80f634d0c5..ade019b9fb 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 056fb3f51e..e77ef8d3f0 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 a5f62804aa..8d80097053 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 ff9cbc97cc..9348b8dc0f 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 f7df93bdd7..d509a91d95 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 04b7e4cf29..f947d7dae7 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 0291fdeb28..b3d3128413 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 52cba4f2ae..dff549ba6e 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 e39a8e11a9..c89b05a3c6 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 198332db0c..a0db7b5a20 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 c60280014c..a7578d0ae2 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 0cb16505e3..b6beafe31f 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 7f9eabb6b3..a6443b453e 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 4e95bb4bcc..f386665186 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 7bad783f19..48db50e202 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 2e20e696fe..426f1812c9 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 0000000000..81e01a66cb --- /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 2182ac43ab..3b05476777 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 ec5df609c4..d2f8ab7a85 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 }}" > +