diff --git a/.github/workflows/brew-bump-cli.yml b/.github/workflows/brew-bump-cli.yml index 5bee549032b..477c9ace582 100644 --- a/.github/workflows/brew-bump-cli.yml +++ b/.github/workflows/brew-bump-cli.yml @@ -38,4 +38,4 @@ jobs: formula: bitwarden-cli tag: ${{ github.ref }} revision: ${{ github.sha }} - force: false + force: true diff --git a/.github/workflows/brew-bump-desktop.yml b/.github/workflows/brew-bump-desktop.yml index f8bf833dcfa..0a5c3947161 100644 --- a/.github/workflows/brew-bump-desktop.yml +++ b/.github/workflows/brew-bump-desktop.yml @@ -38,5 +38,5 @@ jobs: cask: bitwarden tag: ${{ github.ref }} revision: ${{ github.sha }} - force: false + force: true dryrun: true diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index da076303508..4d19c7e7cce 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -35,7 +35,7 @@ defaults: jobs: cloc: name: CLOC - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout repo uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 @@ -51,7 +51,7 @@ jobs: setup: name: Setup - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 outputs: package_version: ${{ steps.retrieve-version.outputs.package_version }} steps: @@ -69,7 +69,7 @@ jobs: name: Build CLI ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04, macos-11] + os: [ubuntu-22.04, macos-11] runs-on: ${{ matrix.os }} needs: - setup @@ -368,7 +368,7 @@ jobs: check-failures: name: Check for failures if: always() - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: - cloc - setup diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index eaaeafd62c8..9ff812bf305 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -182,6 +182,8 @@ jobs: - name: Publish Snap & logout if: ${{ github.event.inputs.release_type != 'Dry Run' }} + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} run: | snapcraft push bw_${{ env._PKG_VERSION }}_amd64.snap --release stable snapcraft logout diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index c564c1c7254..22b76f46408 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -47,7 +47,7 @@ defaults: jobs: setup: name: Setup - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 outputs: release-version: ${{ steps.version.outputs.version }} release-channel: ${{ steps.release-channel.outputs.channel }} @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-desktop" ]]; then echo "===================================" @@ -69,7 +69,7 @@ jobs: id: version uses: bitwarden/gh-actions/release-version-check@67ab95d7a466bcefdedf3f93cbc10bcff436edfe with: - release-type: ${{ github.event.inputs.release_type }} + release-type: ${{ inputs.release_type }} project-type: ts file: apps/desktop/src/package.json monorepo: true @@ -93,7 +93,7 @@ jobs: esac - name: Create GitHub deployment - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5 id: deployment with: @@ -122,7 +122,7 @@ jobs: cf-prod-account" - name: Download all artifacts - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@67ab95d7a466bcefdedf3f93cbc10bcff436edfe with: workflow: build-desktop.yml @@ -131,7 +131,7 @@ jobs: path: apps/desktop/artifacts - name: Dry Run - Download all artifacts - if: ${{ github.event.inputs.release_type == 'Dry Run' }} + if: ${{ inputs.release_type == 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@67ab95d7a466bcefdedf3f93cbc10bcff436edfe with: workflow: build-desktop.yml @@ -146,17 +146,17 @@ jobs: run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive - name: Set staged rollout percentage - if: ${{ github.event.inputs.electron_publish }} + if: ${{ inputs.electron_publish == 'true' }} env: RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} - ROLLOUT_PCT: ${{ github.event.inputs.rollout_percentage }} + ROLLOUT_PCT: ${{ inputs.rollout_percentage }} run: | echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}.yml echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-linux.yml echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml - name: Publish artifacts to S3 - if: ${{ github.event.inputs.release_type != 'Dry Run' && github.event.inputs.electron_publish }} + if: ${{ inputs.release_type != 'Dry Run' && inputs.electron_publish == 'true' }} env: AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-electron-access-id }} AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-electron-access-key }} @@ -170,7 +170,7 @@ jobs: --quiet - name: Publish artifacts to R2 - if: ${{ github.event.inputs.release_type != 'Dry Run' && github.event.inputs.electron_publish }} + if: ${{ inputs.release_type != 'Dry Run' && inputs.electron_publish == 'true' }} env: AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.r2-electron-access-id }} AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.r2-electron-access-key }} @@ -185,14 +185,14 @@ jobs: --endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com - name: Get checksum files - uses: bitwarden/gh-actions/get-checksum@67ab95d7a466bcefdedf3f93cbc10bcff436edfe + uses: bitwarden/gh-actions/get-checksum@82cfceb235b308c2eb63923824e61d8350d280db with: packages_dir: "apps/desktop/artifacts" file_path: "apps/desktop/artifacts/sha256-checksums.txt" - name: Create Release uses: ncipollo/release-action@a2e71bdd4e7dab70ca26a852f29600c98b33153e # v1.12.0 - if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' && inputs.github_release }} + if: ${{ steps.release-channel.outputs.channel == 'latest' && inputs.release_type != 'Dry Run' && inputs.github_release == 'true' }} env: PKG_VERSION: ${{ steps.version.outputs.version }} RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} @@ -230,7 +230,7 @@ jobs: draft: true - name: Update deployment status to Success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} + if: ${{ inputs.release_type != 'Dry Run' && success() }} uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 with: token: '${{ secrets.GITHUB_TOKEN }}' @@ -238,7 +238,7 @@ jobs: deployment-id: ${{ steps.deployment.outputs.deployment_id }} - name: Update deployment status to Failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} + if: ${{ inputs.release_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 with: token: '${{ secrets.GITHUB_TOKEN }}' @@ -247,9 +247,9 @@ jobs: snap: name: Deploy Snap - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: setup - if: inputs.snap_publish + if: ${{ inputs.snap_publish == 'true' }} env: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} steps: @@ -278,7 +278,7 @@ jobs: working-directory: apps/desktop - name: Download Snap artifact - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@67ab95d7a466bcefdedf3f93cbc10bcff436edfe with: workflow: build-desktop.yml @@ -288,7 +288,7 @@ jobs: path: apps/desktop/dist - name: Dry Run - Download Snap artifact - if: ${{ github.event.inputs.release_type == 'Dry Run' }} + if: ${{ inputs.release_type == 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@67ab95d7a466bcefdedf3f93cbc10bcff436edfe with: workflow: build-desktop.yml @@ -298,7 +298,7 @@ jobs: path: apps/desktop/dist - name: Deploy to Snap Store - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} run: | @@ -310,7 +310,7 @@ jobs: name: Deploy Choco runs-on: windows-2019 needs: setup - if: inputs.choco_publish + if: ${{ inputs.choco_publish == 'true' }} env: _PKG_VERSION: ${{ needs.setup.outputs.release-version }} steps: @@ -346,7 +346,7 @@ jobs: working-directory: apps/desktop - name: Download choco artifact - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@67ab95d7a466bcefdedf3f93cbc10bcff436edfe with: workflow: build-desktop.yml @@ -356,7 +356,7 @@ jobs: path: apps/desktop/dist - name: Dry Run - Download choco artifact - if: ${{ github.event.inputs.release_type == 'Dry Run' }} + if: ${{ inputs.release_type == 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@67ab95d7a466bcefdedf3f93cbc10bcff436edfe with: workflow: build-desktop.yml @@ -366,7 +366,7 @@ jobs: path: apps/desktop/dist - name: Push to Chocolatey - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} shell: pwsh run: choco push --source=https://push.chocolatey.org/ working-directory: apps/desktop/dist diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index 2c13ec05b38..857099db511 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -42,8 +42,6 @@ jobs: name: Bump version to ${{ needs.setup.outputs.version_number }} needs: setup uses: ./.github/workflows/version-bump.yml - secrets: - AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} with: version_number: ${{ needs.setup.outputs.version_number }} client: "Desktop" diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 6ea1198784a..420ef456ec0 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -26,9 +26,6 @@ on: client: required: true type: string - secrets: - AZURE_PROD_KV_CREDENTIALS: - required: true defaults: run: diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 2b6c041a06d..cf6bf851dd0 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 جيغابايت وحدة تخزين مشفرة لمرفقات الملفات." }, - "ppremiumSignUpTwoStep": { - "message": "خيارات تسجيل الدخول الإضافية من خطوتين مثل YubiKey و FIDO U2F و Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "نظافة كلمة المرور، صحة الحساب، وتقارير خرق البيانات للحفاظ على سلامة خزنتك." diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 347fc449fa4..283b03a17a9 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi" }, - "ppremiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F və Duo kimi iki mərhələli giriş seçimləri" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Anbarınızın təhlükəsiyini təmin etmək üçün parol gigiyenası, hesab sağlamlığı və verilənlərin pozulması hesabatları." diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 1df3d80b2e0..6aada06265d 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 ГБ зашыфраванага сховішча для далучаных файлаў." }, - "ppremiumSignUpTwoStep": { - "message": "Дадатковыя варыянты двухэтапнага ўваходу, такія як YubiKey, FIDO U2F і Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Гігіена пароляў, здароўе ўліковага запісу і справаздачы аб уцечках даных для забеспячэння бяспекі вашага сховішча." diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index ee3049ffe22..cf9ec1b8ed9 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB пространство за файлове, които се шифрират." }, - "ppremiumSignUpTwoStep": { - "message": "Двустепенно удостоверяване чрез YubiKey, FIDO U2F и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Проверки в списъците с публикувани пароли, проверка на регистрациите и доклади за пробивите в сигурността, което спомага трезорът ви да е допълнително защитен." diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index a7b49de5ab3..7d7151b58f6 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "ফাইল সংযুক্তির জন্য ১ জিবি এনক্রিপ্টেড স্থান।" }, - "ppremiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F, ও Duo এর মতো অতিরিক্ত দ্বি-পদক্ষেপ লগইন বিকল্পগুলি।" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "আপনার ভল্টটি সুরক্ষিত রাখতে পাসওয়ার্ড স্বাস্থ্যকরন, অ্যাকাউন্ট স্বাস্থ্য এবং ডেটা লঙ্ঘনের প্রতিবেদন।" diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index cf2b0fd8a52..657ce243606 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index f00775b993e..fbe9e33c65d 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB d'emmagatzematge xifrat per als fitxers adjunts." }, - "ppremiumSignUpTwoStep": { - "message": "Opcions addicionals d'inici de sessió en dues passes com ara YubiKey, FIDO U2F i Duo." + "premiumSignUpTwoStepOptions": { + "message": "Opcions propietàries de doble factor com ara YubiKey i Duo." }, "ppremiumSignUpReports": { "message": "Requisits d'higiene de la contrasenya, salut del compte i informe d'infraccions de dades per mantenir la seguretat de la vostra caixa forta." diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 4d642f35640..a7bca0d78eb 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB šifrovaného úložiště pro přílohy." }, - "ppremiumSignUpTwoStep": { - "message": "Další možnosti dvoufázového přihlášení, jako je například YubiKey, FIDO U2F a Duo." + "premiumSignUpTwoStepOptions": { + "message": "Volby proprietálních dvoufázových přihlášení jako je YubiKey a Duo." }, "ppremiumSignUpReports": { "message": "Reporty o hygieně Vašich hesel, zdraví účtu a narušeních bezpečnosti." diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 6b278cbd93b..043a0fffead 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 7d258e9a5d1..d2ddc3381b5 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB krypteret lager til vedhæftede filer." }, - "ppremiumSignUpTwoStep": { - "message": "Yderligere to-trins login muligheder såsom YubiKey, FIDO U2F og Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietære totrins-login muligheder, såsom YubiKey og Duo." }, "ppremiumSignUpReports": { "message": "Adgangskodehygiejne, kontosundhed og rapporter om datalæk til at holde din boks sikker." diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 8dfe56577e8..500329ab9ca 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB verschlüsselter Speicherplatz für Dateianhänge." }, - "ppremiumSignUpTwoStep": { - "message": "Zusätzliche Zweifaktor-Anmeldung über YubiKey, FIDO U2F, und Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietäre Optionen für die Zwei-Faktor Authentifizierung wie YubiKey und Duo." }, "ppremiumSignUpReports": { "message": "Berichte über Kennworthygiene, Kontostatus und Datenschutzverletzungen, um deinen Tresor sicher zu halten." diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 5295936e303..e168fd93576 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." }, - "ppremiumSignUpTwoStep": { - "message": "Πρόσθετες επιλογές σύνδεσης δύο βημάτων, όπως το YubiKey, το FIDO U2F και το Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Ασφάλεια κωδικών, υγεία λογαριασμού και αναφορές παραβίασης δεδομένων για να διατηρήσετε ασφαλές το vault σας." diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 56640a8af8e..6aea5876eac 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index b10b53657a3..1632b31ee03 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 8d46a4cbe26..b9f2a3784c3 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 71764078cf4..e43102b483e 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB de espacio cifrado en disco para adjuntos." }, - "ppremiumSignUpTwoStep": { - "message": "Métodos de autenticación en dos pasos adicionales como YubiKey, FIDO U2F y Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Higiene de contraseña, salud de la cuenta e informes de violaciones de datos para mantener su caja fuerte segura." diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 24c15541572..5fc83e82676 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB ulatuses krüpteeritud salvestusruum." }, - "ppremiumSignUpTwoStep": { - "message": "Lisavõimalused kaheastmeliseks kinnitamiseks, näiteks YubiKey, FIDO U2F ja Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Parooli hügieen, konto seisukord ja andmelekete raportid aitavad hoidlat turvalisena hoida." diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 4e5a6857c4c..ac54d0c4ea6 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "Eranskinentzako 1GB-eko zifratutako biltegia." }, - "ppremiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F eta Duo bezalako bi urratseko saio hasierarako aukera gehigarriak." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Pasahitzaren higienea, kontuaren egoera eta datu-bortxaketen txostenak, kutxa gotorra seguru mantentzeko." diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index b7aadc46e30..e1057038933 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "۱ گیگابایت فضای ذخیره سازی رمزگذاری شده برای پیوست های پرونده." }, - "ppremiumSignUpTwoStep": { - "message": "گزینه‌های ورود دو مرحله‌ای اضافی مانند YubiKey, FIDO U2F و Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 4ab409bfcb2..69492afd7f4 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 Gt salattua tallennustilaa tiedostoliitteille." }, - "ppremiumSignUpTwoStep": { - "message": "Muita kaksivaiheisen kirjautumisen todennusmenetelmiä kuten YubiKey, FIDO U2F ja Duo Security." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Salasanahygienian, tilin terveyden ja tietovuotojen raportointitoiminnot pitävät holvisi turvassa." @@ -2310,7 +2310,7 @@ "message": "pakollinen" }, "search": { - "message": "Etsi" + "message": "Hae" }, "inputMinLength": { "message": "Syötteen tulee sisältää ainakin $COUNT$ merkkiä.", diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 802d686c5af..be101c80b41 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage para sa mga file attachment." }, - "ppremiumSignUpTwoStep": { - "message": "Dagdag na dalawang hakbang na login option gaya ng YubiKey, FIDO U2F, at Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Pasahod higiyena, kalusugan ng account, at mga ulat sa data breach upang panatilihing ligtas ang iyong vault." diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 67c126dd9fc..b554c619a5f 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 Go de stockage chiffré pour les fichiers joints." }, - "ppremiumSignUpTwoStep": { - "message": "Options additionnelles d'identification à deux étapes telles que YubiKey, FIDO U2F et Duo." + "premiumSignUpTwoStepOptions": { + "message": "Options de connexion propriétaires à deux facteurs telles que YubiKey et Duo." }, "ppremiumSignUpReports": { "message": "Hygiène du mot de passe, santé du compte et rapports sur les brèches de données pour assurer la sécurité de votre coffre." diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 56640a8af8e..6aea5876eac 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index ebdc30e269d..752935ce81a 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון עבור קבצים מצורפים." }, - "ppremiumSignUpTwoStep": { - "message": "אפשרויות כניסה דו שלבית מתקדמות כמו YubiKey, FIDO U2F, וגם Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "היגיינת סיסמאות, מצב בריאות החשבון, ודיווחים מעודכנים על פרצות חדשות בכדי לשמור על הכספת שלך בטוחה." diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 7d08de998db..d5c465e68bf 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB of encrypted file storage." }, - "ppremiumSignUpTwoStep": { - "message": "अतिरिक्त दो-चरण लॉगिन विकल्प जैसे YubiKey, FIDO U2F, और डुओ।" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "अपनी वॉल्ट को सुरक्षित रखने के लिए पासवर्ड स्वच्छता, खाता स्वास्थ्य और डेटा उल्लंघन रिपोर्ट।" diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 18f0d151312..db0fefbbffb 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB šifriranog prostora za pohranu podataka." }, - "ppremiumSignUpTwoStep": { - "message": "Dodatne mogućnosti za prijavu dvostrukom autentifikacijom kao što su YubiKey, FIDO U2F i Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Higijenu lozinki, zdravlje računa i izvještaje o krađi podatak radi zaštite svojeg trezora." diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index e32509dbbc3..8bc89651d53 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB titkosított tárhely a fájlmellékleteknek." }, - "ppremiumSignUpTwoStep": { - "message": "További két lépcsős bejelentkezés lehetőségek, mint például YubiKey, FIDO U2F és Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Jelszó higiénia, fiók biztonság és adatszivárgási jelentések a széf biztonsága érdekében." diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index b53f92ba44c..d8f6698f4ce 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB penyimpanan berkas yang dienkripsi." }, - "ppremiumSignUpTwoStep": { - "message": "Pilihan info masuk dua langkah tambahan seperti YubiKey, FIDO U2F, dan Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Kebersihan kata sandi, kesehatan akun, dan laporan kebocoran data untuk tetap menjaga keamanan brankas Anda." diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 75154c2453f..e17ea019409 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB di spazio di archiviazione criptato per gli allegati." }, - "ppremiumSignUpTwoStep": { - "message": "Più opzioni di verifica in due passaggi come YubiKey, FIDO U2F, e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Sicurezza delle password, integrità dell'account, e rapporti su violazioni di dati per mantenere sicura la tua cassaforte." diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 93af04dd99f..e979237988c 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1GB の暗号化されたファイルストレージ" }, - "ppremiumSignUpTwoStep": { - "message": "YubiKey、FIDO U2F、Duoなどの追加の2段階認証ログインオプション" + "premiumSignUpTwoStepOptions": { + "message": "YubiKey、Duo などのプロプライエタリな2段階認証オプション。" }, "ppremiumSignUpReports": { "message": "保管庫を安全に保つための、パスワードやアカウントの健全性、データ侵害に関するレポート" diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index c8c379ec377..a619c47eaf4 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 56640a8af8e..6aea5876eac 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index daaf3011f6f..fd01869139d 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "ಫೈಲ್ ಲಗತ್ತುಗಳಿಗಾಗಿ 1 ಜಿಬಿ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹ." }, - "ppremiumSignUpTwoStep": { - "message": "ಹೆಚ್ಚುವರಿ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಆಯ್ಕೆಗಳಾದ ಯೂಬಿಕೆ, ಎಫ್‌ಐಡಿಒ ಯು 2 ಎಫ್, ಮತ್ತು ಡ್ಯುವೋ." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "ನಿಮ್ಮ ವಾಲ್ಟ್ ಅನ್ನು ಸುರಕ್ಷಿತವಾಗಿರಿಸಲು ಪಾಸ್ವರ್ಡ್ ನೈರ್ಮಲ್ಯ, ಖಾತೆ ಆರೋಗ್ಯ ಮತ್ತು ಡೇಟಾ ಉಲ್ಲಂಘನೆ ವರದಿಗಳು." diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 4d302bc5834..f982332a503 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1GB의 암호화된 파일 저장소." }, - "ppremiumSignUpTwoStep": { - "message": "YubiKey나 FIDO U2F, Duo 등의 추가적인 2단계 인증 옵션." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "보관함을 안전하게 유지하기 위한 암호 위생, 계정 상태, 데이터 유출 보고서" diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 72748ba4a37..a3ba40c1fd9 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB užšifruotos vietos diske bylų prisegimams." }, - "ppremiumSignUpTwoStep": { - "message": "Papildomos dviejų žingsių prisijungimo opcijos, tokios kaip YubiKey, FIDO U2F ir Duo." + "premiumSignUpTwoStepOptions": { + "message": "Patentuotos dviejų žingsnių prisijungimo parinktys, tokios kaip YubiKey ir Duo." }, "ppremiumSignUpReports": { "message": "Slaptažodžio higiena, prieigos sveikata ir duomenų nutekinimo ataskaitos, kad tavo saugyklas būtų saugus." diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index ee46e51a6b2..cbf2c469124 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB šifrētas krātuves datņu pielikumiem." }, - "ppremiumSignUpTwoStep": { - "message": "Tādas papildu divpakāpju pieteikšanās iespējas kā YubiKey, FIDO U2F un Duo." + "premiumSignUpTwoStepOptions": { + "message": "Tādas slēgtā pirmavota divpakāpju pieteikšanās iespējas kā YubiKey un Duo." }, "ppremiumSignUpReports": { "message": "Paroļu higiēnas, konta veselības un datu noplūžu pārskati, lai uzturētu glabātavu drošu." diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index c7b3bd91b0d..416b35f78ec 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "ഫയൽ അറ്റാച്ചുമെന്റുകൾക്കായി 1 ജിബി എൻക്രിപ്റ്റുചെയ്‌ത സംഭരണം." }, - "ppremiumSignUpTwoStep": { - "message": "രണ്ട്-ഘട്ട പ്രവേശന ഓപ്ഷനുകളായ Yubikey, FIDO U2F, Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "നിങ്ങളുടെ വാൾട് സൂക്ഷിക്കുന്നതിന്. പാസ്‌വേഡ് ശുചിത്വം, അക്കൗണ്ട് ആരോഗ്യം, ഡാറ്റ ലംഘന റിപ്പോർട്ടുകൾ." diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 5943fb9724e..e12aa7f8a65 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 56640a8af8e..6aea5876eac 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 8b3889b799d..e806ea0a781 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB med kryptert fillagring for filvedlegg." }, - "ppremiumSignUpTwoStep": { - "message": "Ytterligere 2-trinnsinnloggingsmuligheter, slik som YubiKey, FIDO U2F, og Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Passordhygiene, kontohelse, og databruddsrapporter som holder hvelvet ditt trygt." diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 56640a8af8e..6aea5876eac 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index ed6f5394af3..f34016f70f0 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB versleutelde opslag voor bijlagen." }, - "ppremiumSignUpTwoStep": { - "message": "Extra opties voor tweestapsaanmelding zoals YubiKey, FIDO U2F en Duo." + "premiumSignUpTwoStepOptions": { + "message": "Eigen opties voor tweestapsaanmelding zoals YubiKey en Duo." }, "ppremiumSignUpReports": { "message": "Wachtwoordhygiëne, gezondheid van je account en datalekken om je kluis veilig te houden." diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 56640a8af8e..6aea5876eac 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 56640a8af8e..6aea5876eac 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 37e5b701472..75f416b14e4 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB miejsca na zaszyfrowane załączniki." }, - "ppremiumSignUpTwoStep": { - "message": "Dodatkowe opcje logowania dwustopniowego, takie jak klucze YubiKey, FIDO U2F oraz Duo." + "premiumSignUpTwoStepOptions": { + "message": "Własnościowe opcje logowania dwuetapowego, takie jak YubiKey i Duo." }, "ppremiumSignUpReports": { "message": "Raporty bezpieczeństwa haseł, stanu konta i raporty wycieków danych, aby Twoje dane były bezpieczne." diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 0dd3ed1eee6..f8db10e49f8 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB de armazenamento de arquivos encriptados." }, - "ppremiumSignUpTwoStep": { - "message": "Opções de autenticação de duas etapas adicionais como YubiKey, FIDO U2F, e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Higiene de senha, saúde da conta, e relatórios sobre violação de dados para manter o seu cofre seguro." diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 68a69fcf9c5..074ddb150e0 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB de armazenamento encriptado para anexos de ficheiros." }, - "ppremiumSignUpTwoStep": { - "message": "Opções adicionais de verificação de dois passos, como YubiKey, FIDO U2F e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Higiene de palavras-passe, saúde da conta e relatórios de violação de dados para manter o seu cofre seguro." diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 6f577a8da57..20c44ddcca2 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -196,13 +196,13 @@ "message": "Ajutor și feedback" }, "helpCenter": { - "message": "Bitwarden Help center" + "message": "Centrul de Ajutor Bitwarden" }, "communityForums": { - "message": "Explore Bitwarden community forums" + "message": "Explorează forumurile comunității Bitwarden" }, "contactSupport": { - "message": "Contact Bitwarden support" + "message": "Contactați asistența Bitwarden" }, "sync": { "message": "Sincronizare" @@ -442,7 +442,7 @@ "message": "Este necesară rescrierea parolei principale." }, "masterPasswordMinlength": { - "message": "Master password must be at least $VALUE$ characters long.", + "message": "Parola principală trebuie să aibă cel puțin $VALUE$ caractere.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -634,10 +634,10 @@ "message": "Actualizare" }, "notificationUnlockDesc": { - "message": "Unlock your Bitwarden vault to complete the auto-fill request." + "message": "Deblochează seiful Bitwarden pentru a finaliza solicitarea de completare automată." }, "notificationUnlock": { - "message": "Unlock" + "message": "Deblocare" }, "enableContextMenuItem": { "message": "Afișați opțiunile meniului contextual" @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB spațiu de stocare criptat pentru atașamente de fișiere." }, - "ppremiumSignUpTwoStep": { - "message": "Opțiuni adiționale de conectare în două etape, cum ar fi YubiKey, FIDO U2F și Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Rapoarte privind igiena parolelor, sănătatea contului și breșele de date pentru a vă păstra seiful în siguranță." @@ -985,7 +985,7 @@ "message": "Dacă se detectează un formular de autentificare, completați-l automat la încărcarea paginii web." }, "experimentalFeature": { - "message": "Compromised or untrusted websites can exploit auto-fill on page load." + "message": "Site-urile web compromise sau nesigure pot exploata funcția de autocompletare la încărcarea paginii." }, "learnMoreAboutAutofill": { "message": "Learn more about auto-fill" @@ -1468,7 +1468,7 @@ "message": "Articolul s-a completat automat " }, "insecurePageWarning": { - "message": "Warning: This is an unsecured HTTP page, and any information you submit can potentially be seen and changed by others. This Login was originally saved on a secure (HTTPS) page." + "message": "Avertisment: Aceasta este o pagină HTTP nesecurizată și orice informație pe care o trimiteți poate fi văzută și modificată de alte persoane. Această Parolă a fost salvată inițial pe o pagină securizată (HTTPS)." }, "insecurePageWarningFillPrompt": { "message": "Do you still wish to fill this login?" diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index d9614a63c2c..738193fb48a 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 ГБ зашифрованного хранилища для вложенных файлов." }, - "ppremiumSignUpTwoStep": { - "message": "Дополнительные варианты двухэтапной аутентификации, такие как YubiKey, FIDO U2F и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Проприетарные варианты двухэтапной аутентификации, такие как YubiKey или Duo." }, "ppremiumSignUpReports": { "message": "Гигиена паролей, здоровье аккаунта и отчеты об утечках данных для обеспечения безопасности вашего хранилища." diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 0d9a9648b7e..cf3ac370c6d 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "ගොනු ඇමුණුම් සඳහා 1 GB සංකේතාත්මක ගබඩා." }, - "ppremiumSignUpTwoStep": { - "message": "එවැනි YuBiKey, FIDO U2F, සහ Duo ලෙස අතිරේක පියවර දෙකක් පිවිසුම් විකල්ප." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "ඔබගේ සුරක්ෂිතාගාරය ආරක්ෂිතව තබා ගැනීම සඳහා මුරපදය සනීපාරක්ෂාව, ගිණුම් සෞඛ්යය සහ දත්ත උල්ලං ach නය වාර්තා කරයි." diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index c06ad279ec6..1b90bc94053 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB šifrovaného úložiska." }, - "ppremiumSignUpTwoStep": { - "message": "Ďalšie možnosti dvojstupňového prihlásenia ako YubiKey, FIDO U2F a Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Správy o sile hesla, zabezpečení účtov a únikoch dát ktoré vám pomôžu udržať vaše kontá v bezpečí." diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index dba7c971bd7..cae2464dcb1 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB šifriranega prostora za shrambo podatkov." }, - "ppremiumSignUpTwoStep": { - "message": "Dodatne možnosti za prijavo v dveh korakih, n.pr. YubiKey, FIDO U2F in Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Higiena gesel, zdravje računa in poročila o kraji podatkov, ki vam pomagajo ohraniti varnost vašega trezorja." diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 155c5f6acc5..d4e5ee4f2a4 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -339,7 +339,7 @@ "message": "Остало" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "Подесите метод откључавања да бисте променили радњу временског ограничења сефа." }, "rateExtension": { "message": "Оцени овај додатак" @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1ГБ шифровано складиште за прилоге." }, - "ppremiumSignUpTwoStep": { - "message": "Додатне опције пријаве у два корака као што су YubiKey, FIDO U2F, и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Извештаји о хигијени лозинки, здравственом стању налога и кршењу података да бисте заштитили сеф." @@ -1606,10 +1606,10 @@ "message": "Биометрија прегледача није подржана на овом уређају." }, "biometricsFailedTitle": { - "message": "Biometrics failed" + "message": "Биометрија није успела" }, "biometricsFailedDesc": { - "message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support." + "message": "Биометрија се не може завршити, размислите о коришћењу главне лозинке или одјавите се. Ако се ово настави, контактирајте подршку Bitwarden-а." }, "nativeMessaginPermissionErrorTitle": { "message": "Дозвола није дата" @@ -2153,7 +2153,7 @@ "message": "Обавештење је послато на ваш уређај." }, "loginInitiated": { - "message": "Login initiated" + "message": "Пријава је покренута" }, "exposedMasterPassword": { "message": "Изложена главна лозинка" @@ -2240,25 +2240,25 @@ "message": "Отвара се у новом прозору" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Потребно је одобрење уређаја. Изаберите опцију одобрења испод:" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "Запамти овај уређај" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Искључите ако се користи јавни уређај" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Одобри са мојим другим уређајем" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "Затражити одобрење администратора" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "Одобрити са главном лозинком" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "Потребан је SSO идентификатор организације." }, "eu": { "message": "EU", @@ -2280,40 +2280,40 @@ "message": "Приказ" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Налог је успешно креиран!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "Захтевано је одобрење администратора" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "Ваш захтев је послат вашем администратору." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Бићете обавештени када буде одобрено." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Имате проблема са пријављивањем?" }, "loginApproved": { - "message": "Login approved" + "message": "Пријава је одобрена" }, "userEmailMissing": { - "message": "User email missing" + "message": "Недостаје имејл корисника" }, "deviceTrusted": { - "message": "Device trusted" + "message": "Уређај поуздан" }, "inputRequired": { - "message": "Input is required." + "message": "Унос је потребан." }, "required": { - "message": "required" + "message": "обавезно" }, "search": { - "message": "Search" + "message": "Тражи" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "Унос трба имати најмање $COUNT$ слова.", "placeholders": { "count": { "content": "$1", @@ -2322,7 +2322,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Унос не сме бити већи од $COUNT$ карактера.", "placeholders": { "count": { "content": "$1", @@ -2331,7 +2331,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "Следећи знакови нису дозвољени: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -2340,7 +2340,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "Вредност мора бити најмање $MIN$.", "placeholders": { "min": { "content": "$1", @@ -2349,7 +2349,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "Вредност не сме бити већа од $MAX$.", "placeholders": { "max": { "content": "$1", @@ -2358,17 +2358,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "1 или више имејлова су неважећи" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "Унос не сме да садржи само размак.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "Унос није имејл." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ поље(а) изнад захтевај(у) вашу пажњу.", "placeholders": { "count": { "content": "$1", @@ -2377,22 +2377,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Одабрати --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Тип за филтрирање --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Преузимање опција..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Нема предмета" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Обриши све" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ још $QUANTITY$", "placeholders": { "quantity": { "content": "$1", @@ -2401,10 +2401,10 @@ } }, "submenu": { - "message": "Submenu" + "message": "Под-мени" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "Промени проширење", "description": "Toggling an expand/collapse state." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index f3275fce001..7ab058680c4 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB lagring av krypterade filer." }, - "ppremiumSignUpTwoStep": { - "message": "Ytterligare alternativ för tvåstegsverifiering såsom YubiKey, FIDO U2F och Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Lösenordshygien, kontohälsa och dataintrångsrapporter för att hålla ditt valv säkert." diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 56640a8af8e..6aea5876eac 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "ppremiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 1cc5e9bc50c..7fcf7835119 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB of encrypted file storage." }, - "ppremiumSignUpTwoStep": { - "message": "ตัวเลือกการเข้าสู่ระบบแบบสองขั้นตอนเพิ่มเติม เช่น YubiKey, FIDO U2F และ Duo" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "สุขอนามัยของรหัสผ่าน ความสมบูรณ์ของบัญชี และรายงานการละเมิดข้อมูลเพื่อให้ตู้นิรภัยของคุณปลอดภัย" diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 8bcdf1f6580..4134a27d265 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "Dosya ekleri için 1 GB şifrelenmiş depolama." }, - "ppremiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F ve Duo gibi iki aşamalı giriş seçenekleri." + "premiumSignUpTwoStepOptions": { + "message": "YubiKey ve Duo gibi marka bazlı iki aşamalı giriş seçenekleri." }, "ppremiumSignUpReports": { "message": "Kasanızı güvende tutmak için parola hijyeni, hesap sağlığı ve veri ihlali raporları." diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index dfa5ac4c7d3..2d1a0b66291 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." }, - "ppremiumSignUpTwoStep": { - "message": "Додаткові можливості двоетапної перевірки, наприклад, YubiKey, FIDO U2F та Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Гігієна паролів, здоров'я облікового запису, а також звіти про вразливості даних, щоб зберігати ваше сховище в безпеці." @@ -2304,16 +2304,16 @@ "message": "Довірений пристрій" }, "inputRequired": { - "message": "Input is required." + "message": "Необхідно ввести дані." }, "required": { - "message": "required" + "message": "обов'язково" }, "search": { - "message": "Search" + "message": "Пошук" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "Введені дані мають бути довжиною принаймні $COUNT$ символів.", "placeholders": { "count": { "content": "$1", @@ -2322,7 +2322,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Вхідне значення не повинно перевищувати $COUNT$ символів.", "placeholders": { "count": { "content": "$1", @@ -2331,7 +2331,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "Вказані символи заборонені: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -2340,7 +2340,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "Значення має бути принаймні $MIN$.", "placeholders": { "min": { "content": "$1", @@ -2349,7 +2349,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "Значення не може перевищувати $MAX$.", "placeholders": { "max": { "content": "$1", @@ -2358,17 +2358,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "1 або більше адрес е-пошти недійсні" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "Введене значення не повинно містити лише пробіл.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "Введені дані не є адресою е-пошти." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ поле (поля) вище потребують вашої уваги.", "placeholders": { "count": { "content": "$1", @@ -2377,22 +2377,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Оберіть--" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Введіть для фільтрування --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Параметри отримання..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Нічого не знайдено" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Очистити все" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ ще $QUANTITY$", "placeholders": { "quantity": { "content": "$1", @@ -2401,10 +2401,10 @@ } }, "submenu": { - "message": "Submenu" + "message": "Підменю" }, "toggleCollapse": { - "message": "Toggle collapse", + "message": "Згорнути/розгорнути", "description": "Toggling an expand/collapse state." } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 7faabed46e0..5d71340db52 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1GB bộ nhớ lưu trữ tập tin được mã hóa." }, - "ppremiumSignUpTwoStep": { - "message": "Tuỳ chọn đăng nhập 2 bước bổ sung như YubiKey, FIDO U2F, và Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "Thanh lọc mật khẩu, kiểm tra an toàn tài khoản và các báo cáo rò rĩ dữ liệu là để giữ cho kho của bạn an toàn." diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index eb02a54f453..1fc419d0d16 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "1 GB 文件附件加密存储。" }, - "ppremiumSignUpTwoStep": { - "message": "额外的两步登录选项,如 YubiKey、FIDO U2F 和 Duo。" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "密码健康、账户体检以及数据泄露报告,保障您的密码库安全。" diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index a0a2c9e3eb3..68eb917021e 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -795,8 +795,8 @@ "ppremiumSignUpStorage": { "message": "用於檔案附件的 1 GB 加密儲存空間。" }, - "ppremiumSignUpTwoStep": { - "message": "YubiKey、FIDO U2F 和 Duo 等額外的兩步驟登入選項。" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "ppremiumSignUpReports": { "message": "密碼健康度檢查、提供帳戶體檢以及資料外洩報告,以保障您的密碼庫安全。" diff --git a/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts b/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts index dbe391ce4ab..d7cac8d44b2 100644 --- a/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts +++ b/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts @@ -1,7 +1,6 @@ import { mock, MockProxy } from "jest-mock-extended"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; @@ -14,7 +13,6 @@ describe("CipherContextMenuHandler", () => { let mainContextMenuHandler: MockProxy; let authService: MockProxy; let cipherService: MockProxy; - let userVerificationService: MockProxy; let sut: CipherContextMenuHandler; @@ -22,17 +20,10 @@ describe("CipherContextMenuHandler", () => { mainContextMenuHandler = mock(); authService = mock(); cipherService = mock(); - userVerificationService = mock(); - userVerificationService.hasMasterPassword.mockResolvedValue(true); jest.spyOn(MainContextMenuHandler, "removeAll").mockResolvedValue(); - sut = new CipherContextMenuHandler( - mainContextMenuHandler, - authService, - cipherService, - userVerificationService - ); + sut = new CipherContextMenuHandler(mainContextMenuHandler, authService, cipherService); }); afterEach(() => jest.resetAllMocks()); @@ -78,7 +69,7 @@ describe("CipherContextMenuHandler", () => { expect(mainContextMenuHandler.noLogins).toHaveBeenCalledTimes(1); }); - it("only adds valid ciphers", async () => { + it("only adds login ciphers including ciphers that require reprompt", async () => { authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); mainContextMenuHandler.init.mockResolvedValue(true); @@ -90,47 +81,6 @@ describe("CipherContextMenuHandler", () => { name: "Test Cipher", login: { username: "Test Username" }, }; - - cipherService.getAllDecryptedForUrl.mockResolvedValue([ - null, // invalid cipher - undefined, // invalid cipher - { type: CipherType.Card }, // invalid cipher - { type: CipherType.Login, reprompt: CipherRepromptType.Password }, // invalid cipher - realCipher, // valid cipher - ] as any[]); - - await sut.update("https://test.com"); - - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); - - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com"); - - expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(2); - - expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledWith( - "Test Cipher (Test Username)", - "5", - "https://test.com", - realCipher - ); - }); - - it("adds ciphers with master password reprompt if the user does not have a master password", async () => { - authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Unlocked); - - // User does not have a master password, or has one but hasn't logged in with it (key connector user or TDE user) - userVerificationService.hasMasterPasswordAndMasterKeyHash.mockResolvedValue(false); - - mainContextMenuHandler.init.mockResolvedValue(true); - - const realCipher = { - id: "5", - type: CipherType.Login, - reprompt: CipherRepromptType.None, - name: "Test Cipher", - login: { username: "Test Username" }, - }; - const repromptCipher = { id: "6", type: CipherType.Login, @@ -143,8 +93,8 @@ describe("CipherContextMenuHandler", () => { null, // invalid cipher undefined, // invalid cipher { type: CipherType.Card }, // invalid cipher - repromptCipher, // valid cipher realCipher, // valid cipher + repromptCipher, ] as any[]); await sut.update("https://test.com"); @@ -153,7 +103,6 @@ describe("CipherContextMenuHandler", () => { expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com"); - // Should call this twice, once for each valid cipher expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(2); expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledWith( diff --git a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts index 1d1be8f8386..6140db260f5 100644 --- a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts @@ -1,5 +1,4 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -12,7 +11,6 @@ import { authServiceFactory, AuthServiceInitOptions, } from "../../auth/background/service-factories/auth-service.factory"; -import { userVerificationServiceFactory } from "../../auth/background/service-factories/user-verification-service.factory"; import { Account } from "../../models/account"; import { CachedServices } from "../../platform/background/service-factories/factory-options"; import { BrowserApi } from "../../platform/browser/browser-api"; @@ -39,8 +37,7 @@ export class CipherContextMenuHandler { constructor( private mainContextMenuHandler: MainContextMenuHandler, private authService: AuthService, - private cipherService: CipherService, - private userVerificationService: UserVerificationService + private cipherService: CipherService ) {} static async create(cachedServices: CachedServices) { @@ -69,9 +66,6 @@ export class CipherContextMenuHandler { clipboardWriteCallback: NOT_IMPLEMENTED, win: self, }, - stateMigrationServiceOptions: { - stateFactory: stateFactory, - }, stateServiceOptions: { stateFactory: stateFactory, }, @@ -79,8 +73,7 @@ export class CipherContextMenuHandler { return new CipherContextMenuHandler( await MainContextMenuHandler.mv3Create(cachedServices), await authServiceFactory(cachedServices, serviceOptions), - await cipherServiceFactory(cachedServices, serviceOptions), - await userVerificationServiceFactory(cachedServices, serviceOptions) + await cipherServiceFactory(cachedServices, serviceOptions) ); } @@ -180,11 +173,7 @@ export class CipherContextMenuHandler { } private async updateForCipher(url: string, cipher: CipherView) { - if ( - cipher == null || - cipher.type !== CipherType.Login || - (await this.userVerificationService.hasMasterPasswordAndMasterKeyHash()) - ) { + if (cipher == null || cipher.type !== CipherType.Login) { return; } diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts index a9dbcbaacc5..021d15df89e 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts @@ -3,6 +3,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; @@ -63,6 +64,7 @@ describe("ContextMenuClickedHandler", () => { let cipherService: MockProxy; let totpService: MockProxy; let eventCollectionService: MockProxy; + let userVerificationService: MockProxy; let sut: ContextMenuClickedHandler; @@ -82,7 +84,8 @@ describe("ContextMenuClickedHandler", () => { authService, cipherService, totpService, - eventCollectionService + eventCollectionService, + userVerificationService ); }); diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts index 38e605abe70..a6bff50a195 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts @@ -1,6 +1,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { EventType } from "@bitwarden/common/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; @@ -14,6 +15,7 @@ import { AuthServiceInitOptions, } from "../../auth/background/service-factories/auth-service.factory"; import { totpServiceFactory } from "../../auth/background/service-factories/totp-service.factory"; +import { userVerificationServiceFactory } from "../../auth/background/service-factories/user-verification-service.factory"; import LockedVaultPendingNotificationsItem from "../../background/models/lockedVaultPendingNotificationsItem"; import { eventCollectionServiceFactory } from "../../background/service-factories/event-collection-service.factory"; import { Account } from "../../models/account"; @@ -56,7 +58,8 @@ export class ContextMenuClickedHandler { private authService: AuthService, private cipherService: CipherService, private totpService: TotpService, - private eventCollectionService: EventCollectionService + private eventCollectionService: EventCollectionService, + private userVerificationService: UserVerificationService ) {} static async mv3Create(cachedServices: CachedServices) { @@ -85,9 +88,6 @@ export class ContextMenuClickedHandler { clipboardWriteCallback: NOT_IMPLEMENTED, win: self, }, - stateMigrationServiceOptions: { - stateFactory: stateFactory, - }, stateServiceOptions: { stateFactory: stateFactory, }, @@ -109,7 +109,8 @@ export class ContextMenuClickedHandler { await authServiceFactory(cachedServices, serviceOptions), await cipherServiceFactory(cachedServices, serviceOptions), await totpServiceFactory(cachedServices, serviceOptions), - await eventCollectionServiceFactory(cachedServices, serviceOptions) + await eventCollectionServiceFactory(cachedServices, serviceOptions), + await userVerificationServiceFactory(cachedServices, serviceOptions) ); } @@ -204,7 +205,7 @@ export class ContextMenuClickedHandler { return; } - if (cipher.reprompt !== CipherRepromptType.None) { + if (await this.isPasswordRepromptRequired(cipher)) { await BrowserApi.tabSendMessageData(tab, "passwordReprompt", { cipherId: cipher.id, action: AUTOFILL_ID, @@ -218,7 +219,7 @@ export class ContextMenuClickedHandler { this.copyToClipboard({ text: cipher.login.username, tab: tab }); break; case COPY_PASSWORD_ID: - if (cipher.reprompt !== CipherRepromptType.None) { + if (await this.isPasswordRepromptRequired(cipher)) { await BrowserApi.tabSendMessageData(tab, "passwordReprompt", { cipherId: cipher.id, action: COPY_PASSWORD_ID, @@ -230,7 +231,7 @@ export class ContextMenuClickedHandler { break; case COPY_VERIFICATIONCODE_ID: - if (cipher.reprompt !== CipherRepromptType.None) { + if (await this.isPasswordRepromptRequired(cipher)) { await BrowserApi.tabSendMessageData(tab, "passwordReprompt", { cipherId: cipher.id, action: COPY_VERIFICATIONCODE_ID, @@ -246,6 +247,13 @@ export class ContextMenuClickedHandler { } } + private async isPasswordRepromptRequired(cipher: CipherView): Promise { + return ( + cipher.reprompt === CipherRepromptType.Password && + (await this.userVerificationService.hasMasterPasswordAndMasterKeyHash()) + ); + } + private async getIdentifier(tab: chrome.tabs.Tab, info: chrome.contextMenus.OnClickData) { return new Promise((resolve, reject) => { BrowserApi.sendTabsMessage( diff --git a/apps/browser/src/autofill/browser/main-context-menu-handler.ts b/apps/browser/src/autofill/browser/main-context-menu-handler.ts index 9b16aa266db..b9af3dd191f 100644 --- a/apps/browser/src/autofill/browser/main-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/main-context-menu-handler.ts @@ -79,9 +79,6 @@ export class MainContextMenuHandler { logServiceOptions: { isDev: false, }, - stateMigrationServiceOptions: { - stateFactory: stateFactory, - }, stateServiceOptions: { stateFactory: stateFactory, }, diff --git a/apps/browser/src/autofill/content/autofill.js b/apps/browser/src/autofill/content/autofill.js index f6db33af97d..1833c09e159 100644 --- a/apps/browser/src/autofill/content/autofill.js +++ b/apps/browser/src/autofill/content/autofill.js @@ -768,8 +768,16 @@ // Detect if within an iframe, and the iframe is sandboxed function isSandboxed() { - // self.origin is 'null' if inside a frame with sandboxed csp or iframe tag - return self.origin == null || self.origin === 'null'; + // self.origin is 'null' if inside a frame with sandboxed csp or iframe tag + if (String(self.origin).toLowerCase() === "null") { + return true; + } + + if (window.frameElement?.hasAttribute("sandbox")) { + return true; + } + + return location.hostname === ""; } function doFill(fillScript) { diff --git a/apps/browser/src/autofill/content/autofillv2.ts b/apps/browser/src/autofill/content/autofillv2.ts index 8bf16ff879c..65813b3afe6 100644 --- a/apps/browser/src/autofill/content/autofillv2.ts +++ b/apps/browser/src/autofill/content/autofillv2.ts @@ -849,7 +849,15 @@ function fill(document: Document, fillScript: AutofillScript) { // Detect if within an iframe, and the iframe is sandboxed function isSandboxed() { // self.origin is 'null' if inside a frame with sandboxed csp or iframe tag - return self.origin == null || self.origin === "null"; + if (String(self.origin).toLowerCase() === "null") { + return true; + } + + if (window.frameElement?.hasAttribute("sandbox")) { + return true; + } + + return location.hostname === ""; } function doFill(fillScript: AutofillScript) { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 617acc2bf78..31e81c198f6 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -59,7 +59,6 @@ import { EncryptServiceImplementation } from "@bitwarden/common/platform/service import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation"; import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; -import { StateMigrationService } from "@bitwarden/common/platform/services/state-migration.service"; import { SystemService } from "@bitwarden/common/platform/services/system.service"; import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service"; @@ -177,7 +176,6 @@ export default class MainBackground { searchService: SearchServiceAbstraction; notificationsService: NotificationsServiceAbstraction; stateService: StateServiceAbstraction; - stateMigrationService: StateMigrationService; systemService: SystemServiceAbstraction; eventCollectionService: EventCollectionServiceAbstraction; eventUploadService: EventUploadServiceAbstraction; @@ -262,17 +260,11 @@ export default class MainBackground { new KeyGenerationService(this.cryptoFunctionService) ) : new MemoryStorageService(); - this.stateMigrationService = new StateMigrationService( - this.storageService, - this.secureStorageService, - new StateFactory(GlobalState, Account) - ); this.stateService = new BrowserStateService( this.storageService, this.secureStorageService, this.memoryStorageService, this.logService, - this.stateMigrationService, new StateFactory(GlobalState, Account) ); this.platformUtilsService = new BrowserPlatformUtilsService( @@ -635,7 +627,8 @@ export default class MainBackground { this.authService, this.cipherService, this.totpService, - this.eventCollectionService + this.eventCollectionService, + this.userVerificationService ); this.contextMenusBackground = new ContextMenusBackground(contextMenuClickedHandler); @@ -670,8 +663,7 @@ export default class MainBackground { this.cipherContextMenuHandler = new CipherContextMenuHandler( this.mainContextMenuHandler, this.authService, - this.cipherService, - this.userVerificationService + this.cipherService ); } } diff --git a/apps/browser/src/platform/background/service-factories/state-migration-service.factory.ts b/apps/browser/src/platform/background/service-factories/state-migration-service.factory.ts deleted file mode 100644 index 8d4ee969583..00000000000 --- a/apps/browser/src/platform/background/service-factories/state-migration-service.factory.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; -import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; -import { StateMigrationService } from "@bitwarden/common/platform/services/state-migration.service"; - -import { Account } from "../../../models/account"; - -import { CachedServices, factory, FactoryOptions } from "./factory-options"; -import { - diskStorageServiceFactory, - DiskStorageServiceInitOptions, - secureStorageServiceFactory, - SecureStorageServiceInitOptions, -} from "./storage-service.factory"; - -type StateMigrationServiceFactoryOptions = FactoryOptions & { - stateMigrationServiceOptions: { - stateFactory: StateFactory; - }; -}; - -export type StateMigrationServiceInitOptions = StateMigrationServiceFactoryOptions & - DiskStorageServiceInitOptions & - SecureStorageServiceInitOptions; - -export function stateMigrationServiceFactory( - cache: { stateMigrationService?: StateMigrationService } & CachedServices, - opts: StateMigrationServiceInitOptions -): Promise { - return factory( - cache, - "stateMigrationService", - opts, - async () => - new StateMigrationService( - await diskStorageServiceFactory(cache, opts), - await secureStorageServiceFactory(cache, opts), - opts.stateMigrationServiceOptions.stateFactory - ) - ); -} diff --git a/apps/browser/src/platform/background/service-factories/state-service.factory.ts b/apps/browser/src/platform/background/service-factories/state-service.factory.ts index f926d428890..7d3aaf9b6f3 100644 --- a/apps/browser/src/platform/background/service-factories/state-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/state-service.factory.ts @@ -6,10 +6,6 @@ import { BrowserStateService } from "../../services/browser-state.service"; import { CachedServices, factory, FactoryOptions } from "./factory-options"; import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; -import { - stateMigrationServiceFactory, - StateMigrationServiceInitOptions, -} from "./state-migration-service.factory"; import { diskStorageServiceFactory, secureStorageServiceFactory, @@ -30,8 +26,7 @@ export type StateServiceInitOptions = StateServiceFactoryOptions & DiskStorageServiceInitOptions & SecureStorageServiceInitOptions & MemoryStorageServiceInitOptions & - LogServiceInitOptions & - StateMigrationServiceInitOptions; + LogServiceInitOptions; export async function stateServiceFactory( cache: { stateService?: BrowserStateService } & CachedServices, @@ -47,7 +42,6 @@ export async function stateServiceFactory( await secureStorageServiceFactory(cache, opts), await memoryStorageServiceFactory(cache, opts), await logServiceFactory(cache, opts), - await stateMigrationServiceFactory(cache, opts), opts.stateServiceOptions.stateFactory, opts.stateServiceOptions.useAccountCache ) diff --git a/apps/browser/src/platform/listeners/on-command-listener.ts b/apps/browser/src/platform/listeners/on-command-listener.ts index 65af31e173c..0e2cf03828d 100644 --- a/apps/browser/src/platform/listeners/on-command-listener.ts +++ b/apps/browser/src/platform/listeners/on-command-listener.ts @@ -47,9 +47,6 @@ const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise => { stateServiceOptions: { stateFactory: new StateFactory(GlobalState, Account), }, - stateMigrationServiceOptions: { - stateFactory: new StateFactory(GlobalState, Account), - }, apiServiceOptions: { logoutCallback: () => Promise.resolve(), }, @@ -94,9 +91,6 @@ const doGeneratePasswordToClipboard = async (tab: chrome.tabs.Tab): Promise Promise.resolve(), win: self, }, - stateMigrationServiceOptions: { - stateFactory: stateFactory, - }, stateServiceOptions: { stateFactory: stateFactory, }, diff --git a/apps/browser/src/platform/listeners/on-install-listener.ts b/apps/browser/src/platform/listeners/on-install-listener.ts index 480e811fd26..0394941e283 100644 --- a/apps/browser/src/platform/listeners/on-install-listener.ts +++ b/apps/browser/src/platform/listeners/on-install-listener.ts @@ -23,9 +23,6 @@ export async function onInstallListener(details: chrome.runtime.InstalledDetails stateServiceOptions: { stateFactory: new StateFactory(GlobalState, Account), }, - stateMigrationServiceOptions: { - stateFactory: new StateFactory(GlobalState, Account), - }, }; const environmentService = await environmentServiceFactory(cache, opts); diff --git a/apps/browser/src/platform/listeners/update-badge.ts b/apps/browser/src/platform/listeners/update-badge.ts index 89b620ad6fe..1b692eb9b97 100644 --- a/apps/browser/src/platform/listeners/update-badge.ts +++ b/apps/browser/src/platform/listeners/update-badge.ts @@ -272,9 +272,6 @@ export class UpdateBadge { stateServiceOptions: { stateFactory: new StateFactory(GlobalState, Account), }, - stateMigrationServiceOptions: { - stateFactory: new StateFactory(GlobalState, Account), - }, apiServiceOptions: { logoutCallback: () => Promise.reject("not implemented"), }, diff --git a/apps/browser/src/platform/popup/browser-popout-window.service.ts b/apps/browser/src/platform/popup/browser-popout-window.service.ts index 95be15cc20d..ee03e3a2ec4 100644 --- a/apps/browser/src/platform/popup/browser-popout-window.service.ts +++ b/apps/browser/src/platform/popup/browser-popout-window.service.ts @@ -12,7 +12,6 @@ class BrowserPopoutWindowService implements BrowserPopupWindowServiceInterface { }; async openUnlockPrompt(senderWindowId: number) { - await this.closeUnlockPrompt(); await this.openSingleActionPopout( senderWindowId, "popup/index.html?uilocation=popout", @@ -36,8 +35,6 @@ class BrowserPopoutWindowService implements BrowserPopupWindowServiceInterface { action: string; } ) { - await this.closePasswordRepromptPrompt(); - const promptWindowPath = "popup/index.html#/view-cipher" + "?uilocation=popout" + @@ -73,18 +70,16 @@ class BrowserPopoutWindowService implements BrowserPopupWindowServiceInterface { const popupWindow = await BrowserApi.createWindow(windowOptions); - if (!singleActionPopoutKey) { - return; - } + await this.closeSingleActionPopout(singleActionPopoutKey); this.singleActionPopoutTabIds[singleActionPopoutKey] = popupWindow?.tabs[0].id; } private async closeSingleActionPopout(popoutKey: string) { const tabId = this.singleActionPopoutTabIds[popoutKey]; - if (!tabId) { - return; + + if (tabId) { + await BrowserApi.removeTab(tabId); } - await BrowserApi.removeTab(tabId); this.singleActionPopoutTabIds[popoutKey] = null; } } diff --git a/apps/browser/src/platform/services/browser-state.service.spec.ts b/apps/browser/src/platform/services/browser-state.service.spec.ts index d6bb83f7fb5..0712416172c 100644 --- a/apps/browser/src/platform/services/browser-state.service.spec.ts +++ b/apps/browser/src/platform/services/browser-state.service.spec.ts @@ -8,7 +8,6 @@ import { import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { State } from "@bitwarden/common/platform/models/domain/state"; -import { StateMigrationService } from "@bitwarden/common/platform/services/state-migration.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; @@ -26,7 +25,6 @@ describe("Browser State Service", () => { let secureStorageService: MockProxy; let diskStorageService: MockProxy; let logService: MockProxy; - let stateMigrationService: MockProxy; let stateFactory: MockProxy>; let useAccountCache: boolean; @@ -39,7 +37,6 @@ describe("Browser State Service", () => { secureStorageService = mock(); diskStorageService = mock(); logService = mock(); - stateMigrationService = mock(); stateFactory = mock(); // turn off account cache for tests useAccountCache = false; @@ -64,7 +61,6 @@ describe("Browser State Service", () => { secureStorageService, memoryStorageService, logService, - stateMigrationService, stateFactory, useAccountCache ); diff --git a/apps/browser/src/platform/services/browser-state.service.ts b/apps/browser/src/platform/services/browser-state.service.ts index 34fa1a1d0f3..5e356e7fbe8 100644 --- a/apps/browser/src/platform/services/browser-state.service.ts +++ b/apps/browser/src/platform/services/browser-state.service.ts @@ -1,7 +1,6 @@ import { BehaviorSubject } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { StateMigrationService } from "@bitwarden/common/platform/abstractions/state-migration.service"; import { AbstractStorageService, AbstractMemoryStorageService, @@ -41,7 +40,6 @@ export class BrowserStateService secureStorageService: AbstractStorageService, memoryStorageService: AbstractMemoryStorageService, logService: LogService, - stateMigrationService: StateMigrationService, stateFactory: StateFactory, useAccountCache = true ) { @@ -50,7 +48,6 @@ export class BrowserStateService secureStorageService, memoryStorageService, logService, - stateMigrationService, stateFactory, useAccountCache ); diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index 05843a9b351..d8891cf620b 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -24,10 +24,10 @@ $gray-light: #777; $text-muted: $gray-light; $brand-primary: #175ddc; -$brand-danger: #dd4b39; -$brand-success: #00a65a; +$brand-danger: #c83522; +$brand-success: #017e45; $brand-info: #555555; -$brand-warning: #bf7e16; +$brand-warning: #8b6609; $brand-primary-accent: #1252a3; $background-color: #f0f0f0; @@ -43,6 +43,10 @@ $button-color: lighten($text-color, 40%); $button-color-primary: darken($brand-primary, 8%); $button-color-danger: darken($brand-danger, 10%); +$code-color: #c01176; +$code-color-dark: #f08dc7; +$code-color-nord: #dbb1d5; + $solarizedDarkBase03: #002b36; $solarizedDarkBase02: #073642; $solarizedDarkBase01: #586e75; @@ -122,7 +126,7 @@ $themes: ( // light has no hover so use same color webkitCalendarPickerHoverFilter: invert(46%) sepia(69%) saturate(6397%) hue-rotate(211deg) brightness(85%) contrast(103%), - codeColor: #e83e8c, + codeColor: $code-color, ), dark: ( textColor: #ffffff, @@ -184,7 +188,7 @@ $themes: ( hue-rotate(184deg) brightness(87%) contrast(93%), webkitCalendarPickerHoverFilter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(93deg) brightness(103%) contrast(103%), - codeColor: #e83e8c, + codeColor: $code-color-dark, ), nord: ( textColor: $nord5, @@ -237,7 +241,7 @@ $themes: ( passwordCountText: $nord5, calloutBorderColor: $nord0, calloutBackgroundColor: $nord2, - toastTextColor: #ffffff, + toastTextColor: #000000, svgSuffix: "-dark.svg", transparentColor: rgba(0, 0, 0, 0), dateInputColorScheme: dark, @@ -246,7 +250,7 @@ $themes: ( // has no hover so use same color webkitCalendarPickerHoverFilter: brightness(0) saturate(100%) invert(94%) sepia(5%) saturate(454%) hue-rotate(185deg) brightness(93%) contrast(96%), - codeColor: #e83e8c, + codeColor: $code-color-nord, ), solarizedDark: ( textColor: $solarizedDarkBase2, @@ -299,7 +303,7 @@ $themes: ( passwordCountText: $solarizedDarkBase2, calloutBorderColor: $solarizedDarkBase03, calloutBackgroundColor: $solarizedDarkBase01, - toastTextColor: #ffffff, + toastTextColor: #000000, svgSuffix: "-solarized.svg", transparentColor: rgba(0, 0, 0, 0), dateInputColorScheme: dark, @@ -307,7 +311,7 @@ $themes: ( hue-rotate(138deg) brightness(92%) contrast(90%), webkitCalendarPickerHoverFilter: brightness(0) saturate(100%) invert(94%) sepia(10%) saturate(462%) hue-rotate(345deg) brightness(103%) contrast(87%), - codeColor: #e83e8c, + codeColor: $code-color-dark, ), ); diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 191e2c78060..261f6abe37d 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -47,7 +47,6 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateMigrationService } from "@bitwarden/common/platform/abstractions/state-migration.service"; import { StateService as BaseStateServiceAbstraction, StateService, @@ -442,36 +441,23 @@ function getBgService(service: keyof MainBackground) { provide: MEMORY_STORAGE, useFactory: getBgService("memoryStorageService"), }, - { - provide: StateMigrationService, - useFactory: getBgService("stateMigrationService"), - deps: [], - }, { provide: StateServiceAbstraction, useFactory: ( storageService: AbstractStorageService, secureStorageService: AbstractStorageService, memoryStorageService: AbstractMemoryStorageService, - logService: LogServiceAbstraction, - stateMigrationService: StateMigrationService + logService: LogServiceAbstraction ) => { return new BrowserStateService( storageService, secureStorageService, memoryStorageService, logService, - stateMigrationService, new StateFactory(GlobalState, Account) ); }, - deps: [ - AbstractStorageService, - SECURE_STORAGE, - MEMORY_STORAGE, - LogServiceAbstraction, - StateMigrationService, - ], + deps: [AbstractStorageService, SECURE_STORAGE, MEMORY_STORAGE, LogServiceAbstraction], }, { provide: UsernameGenerationServiceAbstraction, diff --git a/apps/browser/src/popup/settings/premium.component.html b/apps/browser/src/popup/settings/premium.component.html index a9a569ec176..2727ee405b9 100644 --- a/apps/browser/src/popup/settings/premium.component.html +++ b/apps/browser/src/popup/settings/premium.component.html @@ -22,7 +22,7 @@
  • - {{ "ppremiumSignUpTwoStep" | i18n }} + {{ "premiumSignUpTwoStepOptions" | i18n }}
  • diff --git a/apps/browser/src/vault/popup/components/vault/view.component.ts b/apps/browser/src/vault/popup/components/vault/view.component.ts index a70a11475ca..8f45547737a 100644 --- a/apps/browser/src/vault/popup/components/vault/view.component.ts +++ b/apps/browser/src/vault/popup/components/vault/view.component.ts @@ -170,8 +170,8 @@ export class ViewComponent extends BaseViewComponent { switch (this.loadAction) { case AUTOFILL_ID: - this.fillCipher(); - return; + await this.fillCipher(); + break; case COPY_USERNAME_ID: await this.copy(this.cipher.login.username, "username", "Username"); break; @@ -186,7 +186,7 @@ export class ViewComponent extends BaseViewComponent { } if (this.inPopout && this.loadAction) { - this.close(); + setTimeout(() => this.close(), 1000); } } @@ -238,10 +238,6 @@ export class ViewComponent extends BaseViewComponent { const didAutofill = await this.doAutofill(); if (didAutofill) { this.platformUtilsService.showToast("success", null, this.i18nService.t("autoFillSuccess")); - - if (this.inPopout) { - this.close(); - } } } diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 1bcaa1a2acf..42ba158ee72 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -37,7 +37,6 @@ import { EnvironmentService } from "@bitwarden/common/platform/services/environm import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service"; -import { StateMigrationService } from "@bitwarden/common/platform/services/state-migration.service"; import { StateService } from "@bitwarden/common/platform/services/state.service"; import { AuditService } from "@bitwarden/common/services/audit.service"; import { OrganizationUserServiceImplementation } from "@bitwarden/common/services/organization-user/organization-user.service.implementation"; @@ -136,7 +135,6 @@ export class Main { keyConnectorService: KeyConnectorService; userVerificationService: UserVerificationService; stateService: StateService; - stateMigrationService: StateMigrationService; organizationService: OrganizationService; providerService: ProviderService; twoFactorService: TwoFactorService; @@ -188,18 +186,11 @@ export class Main { this.memoryStorageService = new MemoryStorageService(); - this.stateMigrationService = new StateMigrationService( - this.storageService, - this.secureStorageService, - new StateFactory(GlobalState, Account) - ); - this.stateService = new StateService( this.storageService, this.secureStorageService, this.memoryStorageService, this.logService, - this.stateMigrationService, new StateFactory(GlobalState, Account) ); diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 9eca236a3a0..8bca024b410 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -298,9 +298,12 @@ export class Program { .option("-p, --passphrase", "Generate a passphrase.") .option("--length ", "Length of the password.") .option("--words ", "Number of words.") + .option("--minNumber ", "Minimum number of numeric characters.") + .option("--minSpecial ", "Minimum number of special characters.") .option("--separator ", "Word separator.") .option("-c, --capitalize", "Title case passphrase.") .option("--includeNumber", "Passphrase includes number.") + .option("--ambiguous", "Avoid ambiguous characters.") .on("--help", () => { writeLn("\n Notes:"); writeLn(""); diff --git a/apps/cli/src/tools/generate.command.ts b/apps/cli/src/tools/generate.command.ts index bd9ad88a04f..30436e7db71 100644 --- a/apps/cli/src/tools/generate.command.ts +++ b/apps/cli/src/tools/generate.command.ts @@ -1,5 +1,6 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; +import { PasswordGeneratorOptions } from "@bitwarden/common/tools/generator/password/password-generator-options"; import { Response } from "../models/response"; import { StringResponse } from "../models/response/string.response"; @@ -13,7 +14,7 @@ export class GenerateCommand { async run(cmdOptions: Record): Promise { const normalizedOptions = new Options(cmdOptions); - const options = { + const options: PasswordGeneratorOptions = { uppercase: normalizedOptions.uppercase, lowercase: normalizedOptions.lowercase, number: normalizedOptions.number, @@ -24,6 +25,9 @@ export class GenerateCommand { numWords: normalizedOptions.words, capitalize: normalizedOptions.capitalize, includeNumber: normalizedOptions.includeNumber, + minNumber: normalizedOptions.minNumber, + minSpecial: normalizedOptions.minSpecial, + ambiguous: normalizedOptions.ambiguous, }; const enforcedOptions = (await this.stateService.getIsAuthenticated()) @@ -47,6 +51,9 @@ class Options { words: number; capitalize: boolean; includeNumber: boolean; + minNumber: number; + minSpecial: number; + ambiguous: boolean; constructor(passedOptions: Record) { this.uppercase = CliUtils.convertBooleanOption(passedOptions?.uppercase); @@ -55,10 +62,13 @@ class Options { this.special = CliUtils.convertBooleanOption(passedOptions?.special); this.capitalize = CliUtils.convertBooleanOption(passedOptions?.capitalize); this.includeNumber = CliUtils.convertBooleanOption(passedOptions?.includeNumber); - this.length = passedOptions?.length != null ? parseInt(passedOptions?.length, null) : 14; + this.ambiguous = CliUtils.convertBooleanOption(passedOptions?.ambiguous); + this.length = CliUtils.convertNumberOption(passedOptions?.length, 14); this.type = passedOptions?.passphrase ? "passphrase" : "password"; - this.separator = passedOptions?.separator == null ? "-" : passedOptions.separator + ""; - this.words = passedOptions?.words != null ? parseInt(passedOptions.words, null) : 3; + this.separator = CliUtils.convertStringOption(passedOptions?.separator, "-"); + this.words = CliUtils.convertNumberOption(passedOptions?.words, 3); + this.minNumber = CliUtils.convertNumberOption(passedOptions?.minNumber, 1); + this.minSpecial = CliUtils.convertNumberOption(passedOptions?.minSpecial, 1); if (!this.uppercase && !this.lowercase && !this.special && !this.number) { this.lowercase = true; diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index f8780dbec63..5d77f6d3730 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -253,4 +253,20 @@ export class CliUtils { static convertBooleanOption(optionValue: any) { return optionValue || optionValue === "" ? true : false; } + + static convertNumberOption(optionValue: any, defaultValue: number) { + try { + if (optionValue != null) { + const numVal = parseInt(optionValue); + return !Number.isNaN(numVal) ? numVal : defaultValue; + } + return defaultValue; + } catch { + return defaultValue; + } + } + + static convertStringOption(optionValue: any, defaultValue: string) { + return optionValue != null ? String(optionValue) : defaultValue; + } } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 83a3aedde21..82c77156c12 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2023.8.2", + "version": "2023.8.3", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index ded0366dc16..42208077c33 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -28,7 +28,6 @@ import { } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/platform/abstractions/state-migration.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service"; @@ -134,7 +133,6 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK"); SECURE_STORAGE, MEMORY_STORAGE, LogService, - StateMigrationServiceAbstraction, STATE_FACTORY, STATE_SERVICE_USE_CACHE, ], diff --git a/apps/desktop/src/auth/login/login.component.ts b/apps/desktop/src/auth/login/login.component.ts index 45b330f6dae..d1c6c88d14b 100644 --- a/apps/desktop/src/auth/login/login.component.ts +++ b/apps/desktop/src/auth/login/login.component.ts @@ -182,6 +182,6 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy { private focusInput() { const email = this.loggedEmail; - document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus(); + document.getElementById(email == null || email === "" ? "email" : "masterPassword")?.focus(); } } diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 7e1ee03ea58..fa880624311 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GG geënkripteerde berging vir lêeraanhegsels." }, - "premiumSignUpTwoStep": { - "message": "Bykomende tweestapaantekenopsies soos YubiKey, FIDO U2F en Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Wagwoordhigiëne, rekeningwelstand en databreukverslae om u kluis veilig te hou." diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index a10a5d6ee7b..914d2026267 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 جيغابايت وحدة تخزين مشفرة لمرفقات الملفات." }, - "premiumSignUpTwoStep": { - "message": "خيارات تسجيل الدخول الإضافية من خطوتين مثل YubiKey و FIDO U2F و Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "نظافة كلمة المرور، صحة الحساب، وتقارير خرق البيانات للحفاظ على سلامة خزنتك." diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 44fd72128ca..3256e1198b2 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi." }, - "premiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F və Duo kimi iki mərhələli giriş seçimləri." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Anbarınızın təhlükəsiyini təmin etmək üçün parol gigiyenası, hesab sağlamlığı və verilənlərin pozulması hesabatları." diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 680a7599638..fc45db18b3f 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 ГБ зашыфраванага сховішча для далучаных файлаў." }, - "premiumSignUpTwoStep": { - "message": "Дадатковыя варыянты двухэтапнага ўваходу, такія як YubiKey, FIDO U2F і Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Гігіена пароляў, здароўе ўліковага запісу і справаздачы аб уцечках даных для забеспячэння бяспекі вашага сховішча." diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index ab6f93166ca..2283ccbde6e 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 ГБ пространство за файлове, които се шифроват." }, - "premiumSignUpTwoStep": { - "message": "Двустепенно удостоверяване чрез YubiKey, FIDO U2F и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Проверки в списъците с публикувани пароли, проверка на регистрациите и доклади за пробивите в сигурността, което спомага трезорът ви да е допълнително защитен." diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 5c155e80210..e59dd9de8b9 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "ফাইল সংযুক্তির জন্য ১ জিবি এনক্রিপ্টেড স্থান।" }, - "premiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F, ও Duo এর মতো অতিরিক্ত দ্বি-পদক্ষেপ লগইন বিকল্পগুলি।" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "আপনার ভল্টটি সুরক্ষিত রাখতে পাসওয়ার্ড স্বাস্থ্যকরন, অ্যাকাউন্ট স্বাস্থ্য এবং ডেটা লঙ্ঘনের প্রতিবেদন।" diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 3571229b42c..c88f7f392f1 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB šifriranog prostora za pohranu podataka." }, - "premiumSignUpTwoStep": { - "message": "Dodatne mogućnosti za prijavu u dva koraka kao što su YubiKey, FIDO U2F i Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Higijenu lozinki, zdravlje računa i izvještaje o krađi podataka radi zaštite svojeg trezora." diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 0e11e49b8f4..9850e4b82a9 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB d'emmagatzematge xifrat per als fitxers adjunts." }, - "premiumSignUpTwoStep": { - "message": "Opcions addicionals d'inici de sessió en dues passes com ara YubiKey, FIDO U2F i Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Requisits d'higiene de la contrasenya, salut del compte i informe d'infraccions de dades per mantenir la seguretat de la vostra caixa forta." diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index a931ae1b14b..4ce336733f6 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB šifrovaného uložiště pro přílohy." }, - "premiumSignUpTwoStep": { - "message": "Další možnosti dvoufázového přihlášení, jako je například YubiKey, FIDO U2F a Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Reporty o hygieně Vašich hesel, zdraví účtu a narušeních bezpečnosti." diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index d2405f785d9..38e81a83bfd 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index e398565d90c..157e1313ff5 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB krypteret lagerplads til filvedhæftninger." }, - "premiumSignUpTwoStep": { - "message": "Yderligere totrins-loginmuligheder, såsom YubiKey, FIDO U2F og Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietære totrins-login muligheder, såsom YubiKey og Duo." }, "premiumSignUpReports": { "message": "Adgangskodehygiejne, kontosundhed og rapporter om datalæk til at holde din boks sikker." diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index b238af9b9d7..d16eb636284 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB verschlüsselter Speicherplatz für Dateianhänge." }, - "premiumSignUpTwoStep": { - "message": "Zusätzliche Zwei-Faktor-Anmeldung über YubiKey, FIDO U2F, und Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietäre Optionen für die Zwei-Faktor Authentifizierung wie YubiKey und Duo." }, "premiumSignUpReports": { "message": "Berichte über Kennworthygiene, Kontostatus und Datenschutzverletzungen, um Ihren Tresor sicher zu halten." diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 10f77e91baa..fd800e8d713 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." }, - "premiumSignUpTwoStep": { - "message": "Πρόσθετες επιλογές σύνδεσης δύο βημάτων, όπως το YubiKey, το FIDO U2F και το Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Ασφάλεια κωδικών, υγιής λογαριασμός και αναφορές παραβίασης δεδομένων για να διατηρήσετε ασφαλή τη λίστα σας." diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index dd2d38e38b4..dbcb70d5e0c 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 7af01bc7cc4..6bc9772eb88 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 811d706fbdd..363648da567 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 1d93cc95b66..66195d90739 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index e662e3455c5..b40c409d1b0 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1GB de espacio en disco cifrado." }, - "premiumSignUpTwoStep": { - "message": "Métodos de autenticación en dos pasos adicionales como YubiKey, FIDO U2F y Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Higiene de contraseña, salud de la cuenta e informes de violaciones de datos para mantener tu caja fuerte segura." diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 64e3bc19e06..fb74084186c 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB ulatuses krüpteeritud salvestusruum." }, - "premiumSignUpTwoStep": { - "message": "Lisavõimalused kaheastmeliseks kinnitamiseks, näiteks YubiKey, FIDO U2F ja Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Parooli hügieen, konto seisukord ja andmelekete raportid aitavad hoidlat turvalisena hoida." diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index c590b004d6f..4c3931490df 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "Eranskinentzako 1GB-eko zifratutako biltegia." }, - "premiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F eta Duo bezalako bi urratseko saio hasierarako aukera gehigarriak." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Pasahitzaren higienea, kontuaren egoera eta datu-bortxaketen txostenak, kutxa gotorra seguru mantentzeko." diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 5661678b37a..9e5f6f8468c 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "۱ گیگابایت فضای ذخیره‌سازی رمزنگاری شده برای پرونده‌های پیوست." }, - "premiumSignUpTwoStep": { - "message": "گزینه‌های ورود دو مرحله‌ای اضافی مانند YubiKey, FIDO U2F و Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index c3e4de27aab..9025306a708 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 Gt salattua tallennustilaa tiedostoliitteille." }, - "premiumSignUpTwoStep": { - "message": "Muita kaksivaiheisen kirjautumisen todennusmenetelmiä kuten YubiKey, FIDO U2F ja Duo Security." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Salasanahygienian, tilin terveyden ja tietovuotojen raportointitoiminnot pitävät holvisi turvassa." diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 996d1c28277..87b9bde0c46 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB naka encrypt na imbakan para sa mga attachment ng file." }, - "premiumSignUpTwoStep": { - "message": "Karagdagang dalawang hakbang na mga pagpipilian sa pag login tulad ng YubiKey, FIDO U2F, at Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Kalinisan ng password, kalusugan ng account, at mga ulat ng paglabag sa data upang mapanatiling ligtas ang iyong vault." diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 6049b364fd0..3f249000bd5 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 Go de stockage chiffré pour les fichiers joints." }, - "premiumSignUpTwoStep": { - "message": "Options additionnelles d'identification à deux étapes telles que YubiKey, FIDO U2F et Duo." + "premiumSignUpTwoStepOptions": { + "message": "Options de connexion propriétaires à deux facteurs telles que YubiKey et Duo." }, "premiumSignUpReports": { "message": "Hygiène du mot de passe, santé du compte et rapports sur les brèches de données pour assurer la sécurité de votre coffre." diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index d2405f785d9..38e81a83bfd 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index b06507da0c2..ad625991fa1 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון מוצפן עבור קבצים מצורפים." }, - "premiumSignUpTwoStep": { - "message": "אפשרויות כניסה דו שלבית מתקדמות כמו YubiKey, FIDO U2F, וDuo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "היגיינת סיסמאות, מצב בריאות החשבון, ודיווחים מעודכנים על פרצות חדשות בכדי לשמור על הכספת שלך בטוחה." diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 024bcfb2dfe..156a431a2d2 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 6f1e9e08272..42966a51575 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB šifriranog prostora za pohranu podataka." }, - "premiumSignUpTwoStep": { - "message": "Dodatne mogućnosti za prijavu dvostrukom autentifikacijom kao što su YubiKey, FIDO U2F i Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Higijenu lozinki, zdravlje računa i izvještaje o krađi podatak radi zaštite svojeg trezora." diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 283d30e0ee0..796da6fdece 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB titkosított fájlmelléklet tárhely." }, - "premiumSignUpTwoStep": { - "message": "További olyan kétlépcsős bejelentkezési opciók mint a YubiKey, FIDO U2F és Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Jelszó higiénia, felhasználói fiók biztonsága, és adatszivárgási jelentések a széf biztonsága érdekében." diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 491ede25dc9..b976d1b8fd9 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB penyimpanan berkas yang dienkripsi." }, - "premiumSignUpTwoStep": { - "message": "Pilihan info masuk dua langkah tambahan seperti YubiKey, FIDO U2F, dan Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Kebersihan kata sandi, kesehatan akun, dan laporan pelanggaran data untuk menjaga brankas Anda tetap aman." diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index fc13e4220f7..f294888cc38 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB di spazio di archiviazione criptato per gli allegati." }, - "premiumSignUpTwoStep": { - "message": "Più opzioni di verifica in due passaggi come YubiKey, FIDO U2F, e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Sicurezza delle password, integrità dell'account e rapporti sulle violazioni dei dati per mantenere la tua cassaforte sicura." diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 393f203fb84..f8c11aca7e5 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1GB の暗号化されたファイルストレージ。" }, - "premiumSignUpTwoStep": { - "message": "YubiKey、FIDO U2F、Duoなどの追加の2段階認証ログインオプション" + "premiumSignUpTwoStepOptions": { + "message": "YubiKey、Duo などのプロプライエタリな2段階認証オプション。" }, "premiumSignUpReports": { "message": "保管庫を安全に保つための、パスワードやアカウントの健全性、データ侵害に関するレポート。" @@ -1681,7 +1681,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "myVault": { - "message": "保管庫" + "message": "自分の保管庫" }, "text": { "message": "テキスト" @@ -2049,7 +2049,7 @@ "message": "組織の検索" }, "searchMyVault": { - "message": "保管庫を検索" + "message": "自分の保管庫内を検索" }, "forwardedEmail": { "message": "転送されたメールエイリアス" diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index d2405f785d9..38e81a83bfd 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index d2405f785d9..38e81a83bfd 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 4be76af8b3e..bfc29e570a1 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "ಫೈಲ್ ಲಗತ್ತುಗಳಿಗಾಗಿ 1 ಜಿಬಿ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹ." }, - "premiumSignUpTwoStep": { - "message": "ಹೆಚ್ಚುವರಿ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಆಯ್ಕೆಗಳಾದ ಯೂಬಿಕೆ, ಎಫ್‌ಐಡಿಒ ಯು 2 ಎಫ್, ಮತ್ತು ಡ್ಯುವೋ." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "ನಿಮ್ಮ ವಾಲ್ಟ್ ಅನ್ನು ಸುರಕ್ಷಿತವಾಗಿರಿಸಲು ಪಾಸ್ವರ್ಡ್ ನೈರ್ಮಲ್ಯ, ಖಾತೆ ಆರೋಗ್ಯ ಮತ್ತು ಡೇಟಾ ಉಲ್ಲಂಘನೆ ವರದಿಗಳು." diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 8948b61ee85..7bff705c8ec 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1GB의 암호화된 파일 저장소." }, - "premiumSignUpTwoStep": { - "message": "YubiKey나 FIDO U2F, Duo 등의 추가적인 2단계 인증 옵션." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "보관함을 안전하게 유지하기 위한 암호 위생, 계정 상태, 데이터 유출 보고서" diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json new file mode 100644 index 00000000000..38e81a83bfd --- /dev/null +++ b/apps/desktop/src/locales/lt/messages.json @@ -0,0 +1,2419 @@ +{ + "bitwarden": { + "message": "Bitwarden" + }, + "filters": { + "message": "Filters" + }, + "allItems": { + "message": "All items" + }, + "favorites": { + "message": "Favorites" + }, + "types": { + "message": "Types" + }, + "typeLogin": { + "message": "Login" + }, + "typeCard": { + "message": "Card" + }, + "typeIdentity": { + "message": "Identity" + }, + "typeSecureNote": { + "message": "Secure note" + }, + "folders": { + "message": "Folders" + }, + "collections": { + "message": "Collections" + }, + "searchVault": { + "message": "Search vault" + }, + "addItem": { + "message": "Add item" + }, + "shared": { + "message": "Shared" + }, + "share": { + "message": "Share" + }, + "moveToOrganization": { + "message": "Move to organization" + }, + "movedItemToOrg": { + "message": "$ITEMNAME$ moved to $ORGNAME$", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + }, + "orgname": { + "content": "$2", + "example": "Company Name" + } + } + }, + "moveToOrgDesc": { + "message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved." + }, + "attachments": { + "message": "Attachments" + }, + "viewItem": { + "message": "View item" + }, + "name": { + "message": "Name" + }, + "uri": { + "message": "URI" + }, + "uriPosition": { + "message": "URI $POSITION$", + "description": "A listing of URIs. Ex: URI 1, URI 2, URI 3, etc.", + "placeholders": { + "position": { + "content": "$1", + "example": "2" + } + } + }, + "newUri": { + "message": "New URI" + }, + "username": { + "message": "Username" + }, + "password": { + "message": "Password" + }, + "passphrase": { + "message": "Passphrase" + }, + "editItem": { + "message": "Edit item" + }, + "emailAddress": { + "message": "Email address" + }, + "verificationCodeTotp": { + "message": "Verification code (TOTP)" + }, + "website": { + "message": "Website" + }, + "notes": { + "message": "Notes" + }, + "customFields": { + "message": "Custom fields" + }, + "launch": { + "message": "Launch" + }, + "copyValue": { + "message": "Copy value", + "description": "Copy value to clipboard" + }, + "minimizeOnCopyToClipboard": { + "message": "Minimize when copying to clipboard" + }, + "minimizeOnCopyToClipboardDesc": { + "message": "Minimize application when copying an item's data to the clipboard." + }, + "toggleVisibility": { + "message": "Toggle visibility" + }, + "toggleCollapse": { + "message": "Toggle collapse", + "description": "Toggling an expand/collapse state." + }, + "cardholderName": { + "message": "Cardholder name" + }, + "number": { + "message": "Number" + }, + "brand": { + "message": "Brand" + }, + "expiration": { + "message": "Expiration" + }, + "securityCode": { + "message": "Security code" + }, + "identityName": { + "message": "Identity name" + }, + "company": { + "message": "Company" + }, + "ssn": { + "message": "Social Security number" + }, + "passportNumber": { + "message": "Passport number" + }, + "licenseNumber": { + "message": "License number" + }, + "email": { + "message": "Email" + }, + "phone": { + "message": "Phone" + }, + "address": { + "message": "Address" + }, + "premiumRequired": { + "message": "Premium required" + }, + "premiumRequiredDesc": { + "message": "A Premium membership is required to use this feature." + }, + "errorOccurred": { + "message": "An error has occurred." + }, + "error": { + "message": "Error" + }, + "january": { + "message": "January" + }, + "february": { + "message": "February" + }, + "march": { + "message": "March" + }, + "april": { + "message": "April" + }, + "may": { + "message": "May" + }, + "june": { + "message": "June" + }, + "july": { + "message": "July" + }, + "august": { + "message": "August" + }, + "september": { + "message": "September" + }, + "october": { + "message": "October" + }, + "november": { + "message": "November" + }, + "december": { + "message": "December" + }, + "ex": { + "message": "ex.", + "description": "Short abbreviation for 'example'." + }, + "title": { + "message": "Title" + }, + "mr": { + "message": "Mr" + }, + "mrs": { + "message": "Mrs" + }, + "ms": { + "message": "Ms" + }, + "mx": { + "message": "Mx" + }, + "dr": { + "message": "Dr" + }, + "expirationMonth": { + "message": "Expiration month" + }, + "expirationYear": { + "message": "Expiration year" + }, + "select": { + "message": "Select" + }, + "other": { + "message": "Other" + }, + "generatePassword": { + "message": "Generate password" + }, + "type": { + "message": "Type" + }, + "firstName": { + "message": "First name" + }, + "middleName": { + "message": "Middle name" + }, + "lastName": { + "message": "Last name" + }, + "fullName": { + "message": "Full name" + }, + "address1": { + "message": "Address 1" + }, + "address2": { + "message": "Address 2" + }, + "address3": { + "message": "Address 3" + }, + "cityTown": { + "message": "City / Town" + }, + "stateProvince": { + "message": "State / Province" + }, + "zipPostalCode": { + "message": "Zip / Postal code" + }, + "country": { + "message": "Country" + }, + "save": { + "message": "Save" + }, + "cancel": { + "message": "Cancel" + }, + "delete": { + "message": "Delete" + }, + "favorite": { + "message": "Favorite" + }, + "edit": { + "message": "Edit" + }, + "authenticatorKeyTotp": { + "message": "Authenticator key (TOTP)" + }, + "folder": { + "message": "Folder" + }, + "newCustomField": { + "message": "New custom field" + }, + "value": { + "message": "Value" + }, + "dragToSort": { + "message": "Drag to sort" + }, + "cfTypeText": { + "message": "Text" + }, + "cfTypeHidden": { + "message": "Hidden" + }, + "cfTypeBoolean": { + "message": "Boolean" + }, + "cfTypeLinked": { + "message": "Linked", + "description": "This describes a field that is 'linked' (related) to another field." + }, + "linkedValue": { + "message": "Linked value", + "description": "This describes a value that is 'linked' (related) to another value." + }, + "remove": { + "message": "Remove" + }, + "nameRequired": { + "message": "Name is required." + }, + "addedItem": { + "message": "Item added" + }, + "editedItem": { + "message": "Item saved" + }, + "deleteItem": { + "message": "Delete item" + }, + "deleteFolder": { + "message": "Delete folder" + }, + "deleteAttachment": { + "message": "Delete attachment" + }, + "deleteItemConfirmation": { + "message": "Do you really want to send to the trash?" + }, + "deletedItem": { + "message": "Item sent to trash" + }, + "overwritePasswordConfirmation": { + "message": "Are you sure you want to overwrite the current password?" + }, + "overwriteUsername": { + "message": "Overwrite username" + }, + "overwriteUsernameConfirmation": { + "message": "Are you sure you want to overwrite the current username?" + }, + "noneFolder": { + "message": "No folder", + "description": "This is the folder for uncategorized items" + }, + "addFolder": { + "message": "Add folder" + }, + "editFolder": { + "message": "Edit folder" + }, + "regeneratePassword": { + "message": "Regenerate password" + }, + "copyPassword": { + "message": "Copy password" + }, + "copyUri": { + "message": "Copy URI" + }, + "copyVerificationCodeTotp": { + "message": "Copy verification code (TOTP)" + }, + "length": { + "message": "Length" + }, + "uppercase": { + "message": "Uppercase (A-Z)" + }, + "lowercase": { + "message": "Lowercase (a-z)" + }, + "numbers": { + "message": "Numbers (0-9)" + }, + "specialCharacters": { + "message": "Special characters (!@#$%^&*)" + }, + "numWords": { + "message": "Number of words" + }, + "wordSeparator": { + "message": "Word separator" + }, + "capitalize": { + "message": "Capitalize", + "description": "Make the first letter of a word uppercase." + }, + "includeNumber": { + "message": "Include number" + }, + "close": { + "message": "Close" + }, + "minNumbers": { + "message": "Minimum numbers" + }, + "minSpecial": { + "message": "Minimum special", + "description": "Minimum Special Characters" + }, + "ambiguous": { + "message": "Avoid ambiguous characters" + }, + "searchCollection": { + "message": "Search collection" + }, + "searchFolder": { + "message": "Search folder" + }, + "searchFavorites": { + "message": "Search favorites" + }, + "searchType": { + "message": "Search type", + "description": "Search item type" + }, + "newAttachment": { + "message": "Add new attachment" + }, + "deletedAttachment": { + "message": "Attachment deleted" + }, + "deleteAttachmentConfirmation": { + "message": "Are you sure you want to delete this attachment?" + }, + "attachmentSaved": { + "message": "Attachment saved" + }, + "file": { + "message": "File" + }, + "selectFile": { + "message": "Select a file" + }, + "maxFileSize": { + "message": "Maximum file size is 500 MB." + }, + "updateKey": { + "message": "You cannot use this feature until you update your encryption key." + }, + "editedFolder": { + "message": "Folder saved" + }, + "addedFolder": { + "message": "Folder added" + }, + "deleteFolderConfirmation": { + "message": "Are you sure you want to delete this folder?" + }, + "deletedFolder": { + "message": "Folder deleted" + }, + "loginOrCreateNewAccount": { + "message": "Log in or create a new account to access your secure vault." + }, + "createAccount": { + "message": "Create account" + }, + "logIn": { + "message": "Log in" + }, + "submit": { + "message": "Submit" + }, + "masterPass": { + "message": "Master password" + }, + "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." + }, + "masterPassHintDesc": { + "message": "A master password hint can help you remember your password if you forget it." + }, + "reTypeMasterPass": { + "message": "Re-type master password" + }, + "masterPassHint": { + "message": "Master password hint (optional)" + }, + "settings": { + "message": "Settings" + }, + "passwordHint": { + "message": "Password hint" + }, + "enterEmailToGetHint": { + "message": "Enter your account email address to receive your master password hint." + }, + "getMasterPasswordHint": { + "message": "Get master password hint" + }, + "emailRequired": { + "message": "Email address is required." + }, + "invalidEmail": { + "message": "Invalid email address." + }, + "masterPasswordRequired": { + "message": "Master password is required." + }, + "confirmMasterPasswordRequired": { + "message": "Master password retype is required." + }, + "masterPasswordMinlength": { + "message": "Master password must be at least $VALUE$ characters long.", + "description": "The Master Password must be at least a specific number of characters long.", + "placeholders": { + "value": { + "content": "$1", + "example": "8" + } + } + }, + "masterPassDoesntMatch": { + "message": "Master password confirmation does not match." + }, + "newAccountCreated": { + "message": "Your new account has been created! You may now log in." + }, + "masterPassSent": { + "message": "We've sent you an email with your master password hint." + }, + "unexpectedError": { + "message": "An unexpected error has occurred." + }, + "itemInformation": { + "message": "Item information" + }, + "noItemsInList": { + "message": "There are no items to list." + }, + "sendVerificationCode": { + "message": "Send a verification code to your email" + }, + "sendCode": { + "message": "Send code" + }, + "codeSent": { + "message": "Code sent" + }, + "verificationCode": { + "message": "Verification code" + }, + "confirmIdentity": { + "message": "Confirm your identity to continue." + }, + "verificationCodeRequired": { + "message": "Verification code is required." + }, + "invalidVerificationCode": { + "message": "Invalid verification code" + }, + "continue": { + "message": "Continue" + }, + "enterVerificationCodeApp": { + "message": "Enter the 6 digit verification code from your authenticator app." + }, + "enterVerificationCodeEmail": { + "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "verificationCodeEmailSent": { + "message": "Verification email sent to $EMAIL$.", + "placeholders": { + "email": { + "content": "$1", + "example": "example@gmail.com" + } + } + }, + "rememberMe": { + "message": "Remember me" + }, + "sendVerificationCodeEmailAgain": { + "message": "Send verification code email again" + }, + "useAnotherTwoStepMethod": { + "message": "Use another two-step login method" + }, + "insertYubiKey": { + "message": "Insert your YubiKey into your computer's USB port, then touch its button." + }, + "insertU2f": { + "message": "Insert your security key into your computer's USB port. If it has a button, touch it." + }, + "recoveryCodeDesc": { + "message": "Lost access to all of your two-factor providers? Use your recovery code to turn off all two-factor providers on your account." + }, + "recoveryCodeTitle": { + "message": "Recovery code" + }, + "authenticatorAppTitle": { + "message": "Authenticator app" + }, + "authenticatorAppDesc": { + "message": "Use an authenticator app (such as Authy or Google Authenticator) to generate time-based verification codes.", + "description": "'Authy' and 'Google Authenticator' are product names and should not be translated." + }, + "yubiKeyTitle": { + "message": "YubiKey OTP security key" + }, + "yubiKeyDesc": { + "message": "Use a YubiKey to access your account. Works with YubiKey 4, 4 Nano, 4C, and NEO devices." + }, + "duoDesc": { + "message": "Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "duoOrganizationDesc": { + "message": "Verify with Duo Security for your organization using the Duo Mobile app, SMS, phone call, or U2F security key.", + "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." + }, + "webAuthnTitle": { + "message": "FIDO2 WebAuthn" + }, + "webAuthnDesc": { + "message": "Use any WebAuthn compatible security key to access your account." + }, + "emailTitle": { + "message": "Email" + }, + "emailDesc": { + "message": "Verification codes will be emailed to you." + }, + "loginUnavailable": { + "message": "Login unavailable" + }, + "noTwoStepProviders": { + "message": "This account has two-step login set up, however, none of the configured two-step providers are supported by this device." + }, + "noTwoStepProviders2": { + "message": "Please add additional providers that are better supported across devices (such as an authenticator app)." + }, + "twoStepOptions": { + "message": "Two-step login options" + }, + "selfHostedEnvironment": { + "message": "Self-hosted environment" + }, + "selfHostedEnvironmentFooter": { + "message": "Specify the base URL of your on-premises hosted Bitwarden installation." + }, + "customEnvironment": { + "message": "Custom environment" + }, + "customEnvironmentFooter": { + "message": "For advanced users. You can specify the base URL of each service independently." + }, + "baseUrl": { + "message": "Server URL" + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, + "ok": { + "message": "Ok" + }, + "yes": { + "message": "Yes" + }, + "no": { + "message": "No" + }, + "overwritePassword": { + "message": "Overwrite password" + }, + "learnMore": { + "message": "Learn more" + }, + "featureUnavailable": { + "message": "Feature unavailable" + }, + "loggedOut": { + "message": "Logged out" + }, + "loginExpired": { + "message": "Your login session has expired." + }, + "logOutConfirmation": { + "message": "Are you sure you want to log out?" + }, + "logOut": { + "message": "Log out" + }, + "addNewLogin": { + "message": "New login" + }, + "addNewItem": { + "message": "New item" + }, + "addNewFolder": { + "message": "New folder" + }, + "view": { + "message": "View" + }, + "account": { + "message": "Account" + }, + "loading": { + "message": "Loading..." + }, + "lockVault": { + "message": "Lock vault" + }, + "passwordGenerator": { + "message": "Password generator" + }, + "contactUs": { + "message": "Contact us" + }, + "helpAndFeedback": { + "message": "Help and feedback" + }, + "getHelp": { + "message": "Get help" + }, + "fileBugReport": { + "message": "File a bug report" + }, + "blog": { + "message": "Blog" + }, + "followUs": { + "message": "Follow us" + }, + "syncVault": { + "message": "Sync vault" + }, + "changeMasterPass": { + "message": "Change master password" + }, + "changeMasterPasswordConfirmation": { + "message": "You can change your master password on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "fingerprintPhrase": { + "message": "Fingerprint phrase", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "yourAccountsFingerprint": { + "message": "Your account's fingerprint phrase", + "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." + }, + "goToWebVault": { + "message": "Go to web vault" + }, + "getMobileApp": { + "message": "Get mobile app" + }, + "getBrowserExtension": { + "message": "Get browser extension" + }, + "syncingComplete": { + "message": "Syncing complete" + }, + "syncingFailed": { + "message": "Syncing failed" + }, + "yourVaultIsLocked": { + "message": "Your vault is locked. Verify your identity to continue." + }, + "unlock": { + "message": "Unlock" + }, + "loggedInAsOn": { + "message": "Logged in as $EMAIL$ on $HOSTNAME$.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "hostname": { + "content": "$2", + "example": "bitwarden.com" + } + } + }, + "invalidMasterPassword": { + "message": "Invalid master password" + }, + "twoStepLoginConfirmation": { + "message": "Two-step login makes your account more secure by requiring you to verify your login with another device such as a security key, authenticator app, SMS, phone call, or email. Two-step login can be set up on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "twoStepLogin": { + "message": "Two-step login" + }, + "vaultTimeout": { + "message": "Vault timeout" + }, + "vaultTimeoutDesc": { + "message": "Choose when your vault will take the vault timeout action." + }, + "immediately": { + "message": "Immediately" + }, + "tenSeconds": { + "message": "10 seconds" + }, + "twentySeconds": { + "message": "20 seconds" + }, + "thirtySeconds": { + "message": "30 seconds" + }, + "oneMinute": { + "message": "1 minute" + }, + "twoMinutes": { + "message": "2 minutes" + }, + "fiveMinutes": { + "message": "5 minutes" + }, + "fifteenMinutes": { + "message": "15 minutes" + }, + "thirtyMinutes": { + "message": "30 minutes" + }, + "oneHour": { + "message": "1 hour" + }, + "fourHours": { + "message": "4 hours" + }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, + "onLocked": { + "message": "On system lock" + }, + "onRestart": { + "message": "On restart" + }, + "never": { + "message": "Never" + }, + "security": { + "message": "Security" + }, + "clearClipboard": { + "message": "Clear clipboard", + "description": "Clipboard is the operating system thing where you copy/paste data to on your device." + }, + "clearClipboardDesc": { + "message": "Automatically clear copied values from your clipboard.", + "description": "Clipboard is the operating system thing where you copy/paste data to on your device." + }, + "enableFavicon": { + "message": "Show website icons" + }, + "faviconDesc": { + "message": "Show a recognizable image next to each login." + }, + "enableMinToTray": { + "message": "Minimize to tray icon" + }, + "enableMinToTrayDesc": { + "message": "When minimizing the window, show an icon in the system tray instead." + }, + "enableMinToMenuBar": { + "message": "Minimize to menu bar" + }, + "enableMinToMenuBarDesc": { + "message": "When minimizing the window, show an icon in the menu bar instead." + }, + "enableCloseToTray": { + "message": "Close to tray icon" + }, + "enableCloseToTrayDesc": { + "message": "When closing the window, show an icon in the system tray instead." + }, + "enableCloseToMenuBar": { + "message": "Close to menu bar" + }, + "enableCloseToMenuBarDesc": { + "message": "When closing the window, show an icon in the menu bar instead." + }, + "enableTray": { + "message": "Show tray icon" + }, + "enableTrayDesc": { + "message": "Always show an icon in the system tray." + }, + "startToTray": { + "message": "Start to tray icon" + }, + "startToTrayDesc": { + "message": "When the application is first started, only show an icon in the system tray." + }, + "startToMenuBar": { + "message": "Start to menu bar" + }, + "startToMenuBarDesc": { + "message": "When the application is first started, only show an icon in the menu bar." + }, + "openAtLogin": { + "message": "Start automatically on login" + }, + "openAtLoginDesc": { + "message": "Start the Bitwarden desktop application automatically on login." + }, + "alwaysShowDock": { + "message": "Always show in the Dock" + }, + "alwaysShowDockDesc": { + "message": "Show the Bitwarden icon in the Dock even when minimized to the menu bar." + }, + "confirmTrayTitle": { + "message": "Confirm hiding tray" + }, + "confirmTrayDesc": { + "message": "Turning off this setting will also turn off all other tray related settings." + }, + "language": { + "message": "Language" + }, + "languageDesc": { + "message": "Change the language used by the application. Restart is required." + }, + "theme": { + "message": "Theme" + }, + "themeDesc": { + "message": "Change the application's color theme." + }, + "dark": { + "message": "Dark", + "description": "Dark color" + }, + "light": { + "message": "Light", + "description": "Light color" + }, + "copy": { + "message": "Copy", + "description": "Copy to clipboard" + }, + "checkForUpdates": { + "message": "Check for updates…" + }, + "version": { + "message": "Version $VERSION_NUM$", + "placeholders": { + "version_num": { + "content": "$1", + "example": "1.2.3" + } + } + }, + "restartToUpdate": { + "message": "Restart to update" + }, + "restartToUpdateDesc": { + "message": "Version $VERSION_NUM$ is ready to install. You must restart the application to complete the installation. Do you want to restart and update now?", + "placeholders": { + "version_num": { + "content": "$1", + "example": "1.2.3" + } + } + }, + "updateAvailable": { + "message": "Update available" + }, + "updateAvailableDesc": { + "message": "An update was found. Do you want to download it now?" + }, + "restart": { + "message": "Restart" + }, + "later": { + "message": "Later" + }, + "noUpdatesAvailable": { + "message": "No updates are currently available. You are using the latest version." + }, + "updateError": { + "message": "Update error" + }, + "unknown": { + "message": "Unknown" + }, + "copyUsername": { + "message": "Copy username" + }, + "copyNumber": { + "message": "Copy number", + "description": "Copy credit card number" + }, + "copySecurityCode": { + "message": "Copy security code", + "description": "Copy credit card security code (CVV)" + }, + "premiumMembership": { + "message": "Premium membership" + }, + "premiumManage": { + "message": "Manage membership" + }, + "premiumManageAlert": { + "message": "You can manage your membership on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "premiumRefresh": { + "message": "Refresh membership" + }, + "premiumNotCurrentMember": { + "message": "You are not currently a Premium member." + }, + "premiumSignUpAndGet": { + "message": "Sign up for a Premium membership and get:" + }, + "premiumSignUpStorage": { + "message": "1 GB encrypted storage for file attachments." + }, + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." + }, + "premiumSignUpReports": { + "message": "Password hygiene, account health, and data breach reports to keep your vault safe." + }, + "premiumSignUpTotp": { + "message": "TOTP verification code (2FA) generator for logins in your vault." + }, + "premiumSignUpSupport": { + "message": "Priority customer support." + }, + "premiumSignUpFuture": { + "message": "All future premium features. More coming soon!" + }, + "premiumPurchase": { + "message": "Purchase Premium" + }, + "premiumPurchaseAlert": { + "message": "You can purchase premium membership on the bitwarden.com web vault. Do you want to visit the website now?" + }, + "premiumCurrentMember": { + "message": "You are a premium member!" + }, + "premiumCurrentMemberThanks": { + "message": "Thank you for supporting Bitwarden." + }, + "premiumPrice": { + "message": "All for just $PRICE$ /year!", + "placeholders": { + "price": { + "content": "$1", + "example": "$10" + } + } + }, + "refreshComplete": { + "message": "Refresh complete" + }, + "passwordHistory": { + "message": "Password history" + }, + "clear": { + "message": "Clear", + "description": "To clear something out. example: To clear browser history." + }, + "noPasswordsInList": { + "message": "There are no passwords to list." + }, + "undo": { + "message": "Undo" + }, + "redo": { + "message": "Redo" + }, + "cut": { + "message": "Cut", + "description": "Cut to clipboard" + }, + "paste": { + "message": "Paste", + "description": "Paste from clipboard" + }, + "selectAll": { + "message": "Select all" + }, + "zoomIn": { + "message": "Zoom in" + }, + "zoomOut": { + "message": "Zoom out" + }, + "resetZoom": { + "message": "Reset zoom" + }, + "toggleFullScreen": { + "message": "Toggle full screen" + }, + "reload": { + "message": "Reload" + }, + "toggleDevTools": { + "message": "Toggle developer tools" + }, + "minimize": { + "message": "Minimize", + "description": "Minimize window" + }, + "zoom": { + "message": "Zoom" + }, + "bringAllToFront": { + "message": "Bring all to front", + "description": "Bring all windows to front (foreground)" + }, + "aboutBitwarden": { + "message": "About Bitwarden" + }, + "services": { + "message": "Services" + }, + "hideBitwarden": { + "message": "Hide Bitwarden" + }, + "hideOthers": { + "message": "Hide others" + }, + "showAll": { + "message": "Show all" + }, + "quitBitwarden": { + "message": "Quit Bitwarden" + }, + "valueCopied": { + "message": "$VALUE$ copied", + "description": "Value has been copied to the clipboard.", + "placeholders": { + "value": { + "content": "$1", + "example": "Password" + } + } + }, + "help": { + "message": "Help" + }, + "window": { + "message": "Window" + }, + "checkPassword": { + "message": "Check if password has been exposed." + }, + "passwordExposed": { + "message": "This password has been exposed $VALUE$ time(s) in data breaches. You should change it.", + "placeholders": { + "value": { + "content": "$1", + "example": "2" + } + } + }, + "passwordSafe": { + "message": "This password was not found in any known data breaches. It should be safe to use." + }, + "baseDomain": { + "message": "Base domain", + "description": "Domain name. Ex. website.com" + }, + "domainName": { + "message": "Domain name", + "description": "Domain name. Ex. website.com" + }, + "host": { + "message": "Host", + "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." + }, + "exact": { + "message": "Exact" + }, + "startsWith": { + "message": "Starts with" + }, + "regEx": { + "message": "Regular expression", + "description": "A programming term, also known as 'RegEx'." + }, + "matchDetection": { + "message": "Match detection", + "description": "URI match detection for auto-fill." + }, + "defaultMatchDetection": { + "message": "Default match detection", + "description": "Default URI match detection for auto-fill." + }, + "toggleOptions": { + "message": "Toggle options" + }, + "organization": { + "message": "Organization", + "description": "An entity of multiple related people (ex. a team or business organization)." + }, + "default": { + "message": "Default" + }, + "exit": { + "message": "Exit" + }, + "showHide": { + "message": "Show / Hide", + "description": "Text for a button that toggles the visibility of the window. Shows the window when it is hidden or hides the window if it is currently open." + }, + "hideToTray": { + "message": "Hide to tray" + }, + "alwaysOnTop": { + "message": "Always on top", + "description": "Application window should always stay on top of other windows" + }, + "dateUpdated": { + "message": "Updated", + "description": "ex. Date this item was updated" + }, + "dateCreated": { + "message": "Created", + "description": "ex. Date this item was created" + }, + "datePasswordUpdated": { + "message": "Password updated", + "description": "ex. Date this password was updated" + }, + "exportVault": { + "message": "Export vault" + }, + "fileFormat": { + "message": "File format" + }, + "hCaptchaUrl": { + "message": "hCaptcha Url", + "description": "hCaptcha is the name of a website, should not be translated" + }, + "loadAccessibilityCookie": { + "message": "Load accessibility cookie" + }, + "registerAccessibilityUser": { + "message": "Register as an accessibility user at", + "description": "ex. Register as an accessibility user at hcaptcha.com" + }, + "copyPasteLink": { + "message": "Copy and paste the link sent to your email below" + }, + "enterhCaptchaUrl": { + "message": "Enter URL to load accessibility cookie for hCaptcha", + "description": "hCaptcha is the name of a website, should not be translated" + }, + "hCaptchaUrlRequired": { + "message": "hCaptcha Url is required", + "description": "hCaptcha is the name of a website, should not be translated" + }, + "invalidUrl": { + "message": "Invalid Url" + }, + "done": { + "message": "Done" + }, + "accessibilityCookieSaved": { + "message": "Accessibility cookie saved!" + }, + "noAccessibilityCookieSaved": { + "message": "No accessibility cookie saved" + }, + "warning": { + "message": "WARNING", + "description": "WARNING (should stay in capitalized letters if the language permits)" + }, + "confirmVaultExport": { + "message": "Confirm vault export" + }, + "exportWarningDesc": { + "message": "This export contains your vault data in an unencrypted format. You should not store or send the exported file over unsecure channels (such as email). Delete it immediately after you are done using it." + }, + "encExportKeyWarningDesc": { + "message": "This export encrypts your data using your account's encryption key. If you ever rotate your account's encryption key you should export again since you will not be able to decrypt this export file." + }, + "encExportAccountWarningDesc": { + "message": "Account encryption keys are unique to each Bitwarden user account, so you can't import an encrypted export into a different account." + }, + "noOrganizationsList": { + "message": "You do not belong to any organizations. Organizations allow you to securely share items with other users." + }, + "noCollectionsInList": { + "message": "There are no collections to list." + }, + "ownership": { + "message": "Ownership" + }, + "whoOwnsThisItem": { + "message": "Who owns this item?" + }, + "strong": { + "message": "Strong", + "description": "ex. A strong password. Scale: Weak -> Good -> Strong" + }, + "good": { + "message": "Good", + "description": "ex. A good password. Scale: Weak -> Good -> Strong" + }, + "weak": { + "message": "Weak", + "description": "ex. A weak password. Scale: Weak -> Good -> Strong" + }, + "weakMasterPassword": { + "message": "Weak master password" + }, + "weakMasterPasswordDesc": { + "message": "The master password you have chosen is weak. You should use a strong master password (or a passphrase) to properly protect your Bitwarden account. Are you sure you want to use this master password?" + }, + "pin": { + "message": "PIN", + "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." + }, + "unlockWithPin": { + "message": "Unlock with PIN" + }, + "setYourPinCode": { + "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." + }, + "pinRequired": { + "message": "PIN code is required." + }, + "invalidPin": { + "message": "Invalid PIN code." + }, + "unlockWithWindowsHello": { + "message": "Unlock with Windows Hello" + }, + "additionalWindowsHelloSettings": { + "message": "Additional Windows Hello settings" + }, + "windowsHelloConsentMessage": { + "message": "Verify for Bitwarden." + }, + "unlockWithTouchId": { + "message": "Unlock with Touch ID" + }, + "additionalTouchIdSettings": { + "message": "Additional Touch ID settings" + }, + "touchIdConsentMessage": { + "message": "unlock your vault" + }, + "autoPromptWindowsHello": { + "message": "Ask for Windows Hello on app start" + }, + "autoPromptTouchId": { + "message": "Ask for Touch ID on app start" + }, + "requirePasswordOnStart": { + "message": "Require password or PIN on app start" + }, + "recommendedForSecurity": { + "message": "Recommended for security." + }, + "lockWithMasterPassOnRestart": { + "message": "Lock with master password on restart" + }, + "deleteAccount": { + "message": "Delete account" + }, + "deleteAccountDesc": { + "message": "Proceed below to delete your account and all vault data." + }, + "deleteAccountWarning": { + "message": "Deleting your account is permanent. It cannot be undone." + }, + "accountDeleted": { + "message": "Account deleted" + }, + "accountDeletedDesc": { + "message": "Your account has been closed and all associated data has been deleted." + }, + "preferences": { + "message": "Preferences" + }, + "enableMenuBar": { + "message": "Show menu bar icon" + }, + "enableMenuBarDesc": { + "message": "Always show an icon in the menu bar." + }, + "hideToMenuBar": { + "message": "Hide to menu bar" + }, + "selectOneCollection": { + "message": "You must select at least one collection." + }, + "premiumUpdated": { + "message": "You've upgraded to Premium." + }, + "restore": { + "message": "Restore" + }, + "premiumManageAlertAppStore": { + "message": "You can manage your subscription from the App Store. Do you want to visit the App Store now?" + }, + "legal": { + "message": "Legal", + "description": "Noun. As in 'legal documents', like our terms of service and privacy policy." + }, + "termsOfService": { + "message": "Terms of Service" + }, + "privacyPolicy": { + "message": "Privacy Policy" + }, + "unsavedChangesConfirmation": { + "message": "Are you sure you want to leave? If you leave now then your current information will not be saved." + }, + "unsavedChangesTitle": { + "message": "Unsaved changes" + }, + "clone": { + "message": "Clone" + }, + "passwordGeneratorPolicyInEffect": { + "message": "One or more organization policies are affecting your generator settings." + }, + "vaultTimeoutAction": { + "message": "Vault timeout action" + }, + "vaultTimeoutActionLockDesc": { + "message": "Master password or other unlock method is required to access your vault again." + }, + "vaultTimeoutActionLogOutDesc": { + "message": "Re-authentication is required to access your vault again." + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "lock": { + "message": "Lock", + "description": "Verb form: to make secure or inaccesible by" + }, + "trash": { + "message": "Trash", + "description": "Noun: a special folder to hold deleted items" + }, + "searchTrash": { + "message": "Search trash" + }, + "permanentlyDeleteItem": { + "message": "Permanently delete item" + }, + "permanentlyDeleteItemConfirmation": { + "message": "Are you sure you want to permanently delete this item?" + }, + "permanentlyDeletedItem": { + "message": "Item permanently deleted" + }, + "restoredItem": { + "message": "Item restored" + }, + "permanentlyDelete": { + "message": "Permanently delete" + }, + "vaultTimeoutLogOutConfirmation": { + "message": "Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?" + }, + "vaultTimeoutLogOutConfirmationTitle": { + "message": "Timeout action confirmation" + }, + "enterpriseSingleSignOn": { + "message": "Enterprise single sign-on" + }, + "setMasterPassword": { + "message": "Set master password" + }, + "ssoCompleteRegistration": { + "message": "In order to complete logging in with SSO, please set a master password to access and protect your vault." + }, + "currentMasterPass": { + "message": "Current master password" + }, + "newMasterPass": { + "message": "New master password" + }, + "confirmNewMasterPass": { + "message": "Confirm new master password" + }, + "masterPasswordPolicyInEffect": { + "message": "One or more organization policies require your master password to meet the following requirements:" + }, + "policyInEffectMinComplexity": { + "message": "Minimum complexity score of $SCORE$", + "placeholders": { + "score": { + "content": "$1", + "example": "4" + } + } + }, + "policyInEffectMinLength": { + "message": "Minimum length of $LENGTH$", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } + }, + "policyInEffectUppercase": { + "message": "Contain one or more uppercase characters" + }, + "policyInEffectLowercase": { + "message": "Contain one or more lowercase characters" + }, + "policyInEffectNumbers": { + "message": "Contain one or more numbers" + }, + "policyInEffectSpecial": { + "message": "Contain one or more of the following special characters $CHARS$", + "placeholders": { + "chars": { + "content": "$1", + "example": "!@#$%^&*" + } + } + }, + "masterPasswordPolicyRequirementsNotMet": { + "message": "Your new master password does not meet the policy requirements." + }, + "acceptPolicies": { + "message": "By checking this box you agree to the following:" + }, + "acceptPoliciesRequired": { + "message": "Terms of Service and Privacy Policy have not been acknowledged." + }, + "enableBrowserIntegration": { + "message": "Allow browser integration" + }, + "enableBrowserIntegrationDesc": { + "message": "Used for biometrics in browser." + }, + "enableDuckDuckGoBrowserIntegration": { + "message": "Allow DuckDuckGo browser integration" + }, + "enableDuckDuckGoBrowserIntegrationDesc": { + "message": "Use your Bitwarden vault when browsing with DuckDuckGo." + }, + "browserIntegrationUnsupportedTitle": { + "message": "Browser integration not supported" + }, + "browserIntegrationMasOnlyDesc": { + "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." + }, + "browserIntegrationWindowsStoreDesc": { + "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." + }, + "browserIntegrationLinuxDesc": { + "message": "Unfortunately browser integration is currently not supported in the linux version." + }, + "enableBrowserIntegrationFingerprint": { + "message": "Require verification for browser integration" + }, + "enableBrowserIntegrationFingerprintDesc": { + "message": "Add an additional layer of security by requiring fingerprint phrase confirmation when establishing a link between your desktop and browser. This requires user action and verification each time a connection is created." + }, + "approve": { + "message": "Approve" + }, + "verifyBrowserTitle": { + "message": "Verify browser connection" + }, + "verifyBrowserDesc": { + "message": "Please ensure the shown fingerprint is identical to the fingerprint showed in the browser extension." + }, + "verifyNativeMessagingConnectionTitle": { + "message": "$APPID$ wants to connect to Bitwarden", + "placeholders": { + "appid": { + "content": "$1", + "example": "My App" + } + } + }, + "verifyNativeMessagingConnectionDesc": { + "message": "Would you like to approve this request?" + }, + "verifyNativeMessagingConnectionWarning": { + "message": "If you did not initiate this request, do not approve it." + }, + "biometricsNotEnabledTitle": { + "message": "Biometrics not set up" + }, + "biometricsNotEnabledDesc": { + "message": "Browser biometrics requires desktop biometrics to be set up in the settings first." + }, + "personalOwnershipSubmitError": { + "message": "Due to an enterprise policy, you are restricted from saving items to your individual vault. Change the ownership option to an organization and choose from available collections." + }, + "hintEqualsPassword": { + "message": "Your password hint cannot be the same as your password." + }, + "personalOwnershipPolicyInEffect": { + "message": "An organization policy is affecting your ownership options." + }, + "allSends": { + "message": "All Sends", + "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTypeFile": { + "message": "File" + }, + "sendTypeText": { + "message": "Text" + }, + "searchSends": { + "message": "Search Sends", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editSend": { + "message": "Edit Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "myVault": { + "message": "My vault" + }, + "text": { + "message": "Text" + }, + "deletionDate": { + "message": "Deletion date" + }, + "deletionDateDesc": { + "message": "The Send will be permanently deleted on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "expirationDate": { + "message": "Expiration date" + }, + "expirationDateDesc": { + "message": "If set, access to this Send will expire on the specified date and time.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "maxAccessCount": { + "message": "Maximum access count", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, + "maxAccessCountDesc": { + "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "currentAccessCount": { + "message": "Current access count" + }, + "disableSend": { + "message": "Deactivate this Send so that no one can access it.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendPasswordDesc": { + "message": "Optionally require a password for users to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendNotesDesc": { + "message": "Private notes about this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendLink": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendLinkLabel": { + "message": "Send link", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "textHiddenByDefault": { + "message": "When accessing the Send, hide the text by default", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createdSend": { + "message": "Send added", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "editedSend": { + "message": "Send saved", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "deletedSend": { + "message": "Send deleted", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "newPassword": { + "message": "New password" + }, + "whatTypeOfSend": { + "message": "What type of Send is this?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "createSend": { + "message": "New Send", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendTextDesc": { + "message": "The text you want to send." + }, + "sendFileDesc": { + "message": "The file you want to send." + }, + "days": { + "message": "$DAYS$ days", + "placeholders": { + "days": { + "content": "$1", + "example": "1" + } + } + }, + "oneDay": { + "message": "1 day" + }, + "custom": { + "message": "Custom" + }, + "deleteSendConfirmation": { + "message": "Are you sure you want to delete this Send?", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "copySendLinkToClipboard": { + "message": "Copy Send link to clipboard", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "copySendLinkOnSave": { + "message": "Copy the link to share this Send to my clipboard upon save." + }, + "sendDisabled": { + "message": "Send removed", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendDisabledWarning": { + "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "copyLink": { + "message": "Copy link" + }, + "disabled": { + "message": "Disabled" + }, + "removePassword": { + "message": "Remove password" + }, + "removedPassword": { + "message": "Password removed" + }, + "removePasswordConfirmation": { + "message": "Are you sure you want to remove the password?" + }, + "maxAccessCountReached": { + "message": "Max access count reached" + }, + "expired": { + "message": "Expired" + }, + "pendingDeletion": { + "message": "Pending deletion" + }, + "webAuthnAuthenticate": { + "message": "Authenticate WebAuthn" + }, + "hideEmail": { + "message": "Hide my email address from recipients." + }, + "sendOptionsPolicyInEffect": { + "message": "One or more organization policies are affecting your Send options." + }, + "emailVerificationRequired": { + "message": "Email verification required" + }, + "emailVerificationRequiredDesc": { + "message": "You must verify your email to use this feature." + }, + "passwordPrompt": { + "message": "Master password re-prompt" + }, + "passwordConfirmation": { + "message": "Master password confirmation" + }, + "passwordConfirmationDesc": { + "message": "This action is protected. To continue, please re-enter your master password to verify your identity." + }, + "updatedMasterPassword": { + "message": "Updated master password" + }, + "updateMasterPassword": { + "message": "Update master password" + }, + "updateMasterPasswordWarning": { + "message": "Your master password was recently changed by an administrator in your organization. In order to access the vault, you must update it now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "updateWeakMasterPasswordWarning": { + "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + }, + "hours": { + "message": "Hours" + }, + "minutes": { + "message": "Minutes" + }, + "vaultTimeoutPolicyInEffect": { + "message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "vaultTimeoutPolicyWithActionInEffect": { + "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + }, + "action": { + "content": "$3", + "example": "Lock" + } + } + }, + "vaultTimeoutActionPolicyInEffect": { + "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "placeholders": { + "action": { + "content": "$1", + "example": "Lock" + } + } + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "resetPasswordPolicyAutoEnroll": { + "message": "Automatic enrollment" + }, + "resetPasswordAutoEnrollInviteWarning": { + "message": "This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password." + }, + "vaultExportDisabled": { + "message": "Vault export removed" + }, + "personalVaultExportPolicyInEffect": { + "message": "One or more organization policies prevents you from exporting your personal vault." + }, + "addAccount": { + "message": "Add account" + }, + "removeMasterPassword": { + "message": "Remove master password" + }, + "removedMasterPassword": { + "message": "Master password removed" + }, + "convertOrganizationEncryptionDesc": { + "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + } + } + }, + "leaveOrganization": { + "message": "Leave organization" + }, + "leaveOrganizationConfirmation": { + "message": "Are you sure you want to leave this organization?" + }, + "leftOrganization": { + "message": "You have left the organization." + }, + "ssoKeyConnectorError": { + "message": "Key connector error: make sure key connector is available and working correctly." + }, + "lockAllVaults": { + "message": "Lock all vaults" + }, + "accountLimitReached": { + "message": "No more than 5 accounts may be logged in at the same time." + }, + "accountPreferences": { + "message": "Preferences" + }, + "appPreferences": { + "message": "App settings (all accounts)" + }, + "accountSwitcherLimitReached": { + "message": "Account limit reached. Log out of an account to add another." + }, + "settingsTitle": { + "message": "App settings for $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "jdoe@example.com" + } + } + }, + "switchAccount": { + "message": "Switch account" + }, + "options": { + "message": "Options" + }, + "sessionTimeout": { + "message": "Your session has timed out. Please go back and try logging in again." + }, + "exportingPersonalVaultTitle": { + "message": "Exporting individual vault" + }, + "exportingPersonalVaultDescription": { + "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included.", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "locked": { + "message": "Locked" + }, + "unlocked": { + "message": "Unlocked" + }, + "generator": { + "message": "Generator" + }, + "whatWouldYouLikeToGenerate": { + "message": "What would you like to generate?" + }, + "passwordType": { + "message": "Password type" + }, + "regenerateUsername": { + "message": "Regenerate username" + }, + "generateUsername": { + "message": "Generate username" + }, + "usernameType": { + "message": "Username type" + }, + "plusAddressedEmail": { + "message": "Plus addressed email", + "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" + }, + "plusAddressedEmailDesc": { + "message": "Use your email provider's sub-addressing capabilities." + }, + "catchallEmail": { + "message": "Catch-all email" + }, + "catchallEmailDesc": { + "message": "Use your domain's configured catch-all inbox." + }, + "random": { + "message": "Random" + }, + "randomWord": { + "message": "Random word" + }, + "websiteName": { + "message": "Website name" + }, + "service": { + "message": "Service" + }, + "allVaults": { + "message": "All vaults" + }, + "searchOrganization": { + "message": "Search organization" + }, + "searchMyVault": { + "message": "Search my vault" + }, + "forwardedEmail": { + "message": "Forwarded email alias" + }, + "forwardedEmailDesc": { + "message": "Generate an email alias with an external forwarding service." + }, + "hostname": { + "message": "Hostname", + "description": "Part of a URL." + }, + "apiAccessToken": { + "message": "API Access Token" + }, + "apiKey": { + "message": "API key" + }, + "premiumSubcriptionRequired": { + "message": "Premium subscription required" + }, + "organizationIsDisabled": { + "message": "Organization suspended" + }, + "disabledOrganizationFilterError": { + "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "vault": { + "message": "Vault" + }, + "loginWithMasterPassword": { + "message": "Log in with master password" + }, + "loggingInAs": { + "message": "Logging in as" + }, + "rememberEmail": { + "message": "Remember email" + }, + "notYou": { + "message": "Not you?" + }, + "newAroundHere": { + "message": "New around here?" + }, + "loggingInTo": { + "message": "Logging in to $DOMAIN$", + "placeholders": { + "domain": { + "content": "$1", + "example": "example.com" + } + } + }, + "logInWithAnotherDevice": { + "message": "Log in with another device" + }, + "loginInitiated": { + "message": "Login initiated" + }, + "notificationSentDevice": { + "message": "A notification has been sent to your device." + }, + "fingerprintMatchInfo": { + "message": "Please make sure your vault is unlocked and Fingerprint phrase matches the other device." + }, + "fingerprintPhraseHeader": { + "message": "Fingerprint phrase" + }, + "needAnotherOption": { + "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + }, + "viewAllLoginOptions": { + "message": "View all login options" + }, + "resendNotification": { + "message": "Resend notification" + }, + "toggleCharacterCount": { + "message": "Toggle character count", + "description": "'Character count' describes a feature that displays a number next to each character of the password." + }, + "areYouTryingtoLogin": { + "message": "Are you trying to log in?" + }, + "logInAttemptBy": { + "message": "Login attempt by $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "deviceType": { + "message": "Device Type" + }, + "ipAddress": { + "message": "IP Address" + }, + "time": { + "message": "Time" + }, + "confirmLogIn": { + "message": "Confirm login" + }, + "denyLogIn": { + "message": "Deny login" + }, + "approveLoginRequests": { + "message": "Approve login requests" + }, + "logInConfirmedForEmailOnDevice": { + "message": "Login confirmed for $EMAIL$ on $DEVICE$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + }, + "device": { + "content": "$2", + "example": "iOS" + } + } + }, + "youDeniedALogInAttemptFromAnotherDevice": { + "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." + }, + "justNow": { + "message": "Just now" + }, + "requestedXMinutesAgo": { + "message": "Requested $MINUTES$ minutes ago", + "placeholders": { + "minutes": { + "content": "$1", + "example": "5" + } + } + }, + "loginRequestHasAlreadyExpired": { + "message": "Login request has already expired." + }, + "thisRequestIsNoLongerValid": { + "message": "This request is no longer valid." + }, + "approveLoginRequestDesc": { + "message": "Use this device to approve login requests made from other devices." + }, + "confirmLoginAtemptForMail": { + "message": "Confirm login attempt for $EMAIL$", + "placeholders": { + "email": { + "content": "$1", + "example": "name@example.com" + } + } + }, + "logInRequested": { + "message": "Log in requested" + }, + "exposedMasterPassword": { + "message": "Exposed Master Password" + }, + "exposedMasterPasswordDesc": { + "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" + }, + "weakAndExposedMasterPassword": { + "message": "Weak and Exposed Master Password" + }, + "weakAndBreachedMasterPasswordDesc": { + "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" + }, + "checkForBreaches": { + "message": "Check known data breaches for this password" + }, + "important": { + "message": "Important:" + }, + "masterPasswordHint": { + "message": "Your master password cannot be recovered if you forget it!" + }, + "characterMinimum": { + "message": "$LENGTH$ character minimum", + "placeholders": { + "length": { + "content": "$1", + "example": "14" + } + } + }, + "windowsBiometricUpdateWarning": { + "message": "Bitwarden recommends updating your biometric settings to require your master password (or PIN) on the first unlock. Would you like to update your settings now?" + }, + "windowsBiometricUpdateWarningTitle": { + "message": "Recommended Settings Update" + }, + "deviceApprovalRequired": { + "message": "Device approval required. Select an approval option below:" + }, + "rememberThisDevice": { + "message": "Remember this device" + }, + "uncheckIfPublicDevice": { + "message": "Uncheck if using a public device" + }, + "approveFromYourOtherDevice": { + "message": "Approve from your other device" + }, + "requestAdminApproval": { + "message": "Request admin approval" + }, + "approveWithMasterPassword": { + "message": "Approve with master password" + }, + "region": { + "message": "Region" + }, + "ssoIdentifierRequired": { + "message": "Organization SSO identifier is required." + }, + "eu": { + "message": "EU", + "description": "European Union" + }, + "loggingInOn": { + "message": "Logging in on" + }, + "usDomain": { + "message": "bitwarden.com" + }, + "euDomain": { + "message": "bitwarden.eu" + }, + "selfHosted": { + "message": "Self-hosted" + }, + "accessDenied": { + "message": "Access denied. You do not have permission to view this page." + }, + "accountSuccessfullyCreated": { + "message": "Account successfully created!" + }, + "adminApprovalRequested": { + "message": "Admin approval requested" + }, + "adminApprovalRequestSentToAdmins": { + "message": "Your request has been sent to your admin." + }, + "youWillBeNotifiedOnceApproved": { + "message": "You will be notified once approved." + }, + "troubleLoggingIn": { + "message": "Trouble logging in?" + }, + "loginApproved": { + "message": "Login approved" + }, + "userEmailMissing": { + "message": "User email missing" + }, + "deviceTrusted": { + "message": "Device trusted" + }, + "inputRequired": { + "message": "Input is required." + }, + "required": { + "message": "required" + }, + "search": { + "message": "Search" + }, + "inputMinLength": { + "message": "Input must be at least $COUNT$ characters long.", + "placeholders": { + "count": { + "content": "$1", + "example": "8" + } + } + }, + "inputMaxLength": { + "message": "Input must not exceed $COUNT$ characters in length.", + "placeholders": { + "count": { + "content": "$1", + "example": "20" + } + } + }, + "inputForbiddenCharacters": { + "message": "The following characters are not allowed: $CHARACTERS$", + "placeholders": { + "characters": { + "content": "$1", + "example": "@, #, $, %" + } + } + }, + "inputMinValue": { + "message": "Input value must be at least $MIN$.", + "placeholders": { + "min": { + "content": "$1", + "example": "8" + } + } + }, + "inputMaxValue": { + "message": "Input value must not exceed $MAX$.", + "placeholders": { + "max": { + "content": "$1", + "example": "100" + } + } + }, + "multipleInputEmails": { + "message": "1 or more emails are invalid" + }, + "inputTrimValidator": { + "message": "Input must not contain only whitespace.", + "description": "Notification to inform the user that a form's input can't contain only whitespace." + }, + "inputEmail": { + "message": "Input is not an email address." + }, + "fieldsNeedAttention": { + "message": "$COUNT$ field(s) above need your attention.", + "placeholders": { + "count": { + "content": "$1", + "example": "4" + } + } + }, + "selectPlaceholder": { + "message": "-- Select --" + }, + "multiSelectPlaceholder": { + "message": "-- Type to filter --" + }, + "multiSelectLoading": { + "message": "Retrieving options..." + }, + "multiSelectNotFound": { + "message": "No items found" + }, + "multiSelectClearAll": { + "message": "Clear all" + }, + "plusNMore": { + "message": "+ $QUANTITY$ more", + "placeholders": { + "quantity": { + "content": "$1", + "example": "5" + } + } + }, + "submenu": { + "message": "Submenu" + } +} diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 79b8ae6ab25..5d75ee2208d 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB šifrētas krātuves datņu pielikumiem." }, - "premiumSignUpTwoStep": { - "message": "Tādas papildu divpakāpju pieteikšanās iespējas kā YubiKey, FIDO U2F un Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Paroļu higiēnas, konta veselības un datu noplūžu pārskati, lai uzturētu glabātavu drošu." diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index e7c60b94ba7..20336258846 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB šifrovanog skladišta za priloge datoteka." }, - "premiumSignUpTwoStep": { - "message": "Dodatne opcije prijave u dva koraka kao što su YubiKey, FIDO U2F i Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Higijena lozinke, zdravlje računa i podaci o krađi podataka kako bi trezor bio siguran." diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index f3f435e22aa..18b448b1d1e 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "ഫയൽ അറ്റാച്ചുമെന്റുകൾക്കായി 1 GB എൻക്രിപ്റ്റുചെയ്‌ത സ്റ്റോറേജ്." }, - "premiumSignUpTwoStep": { - "message": "രണ്ട്-ഘട്ട പ്രവേശന ഓപ്ഷനുകളായ Yubikey, FIDO U2F, Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "നിങ്ങളുടെ വാൾട് സൂക്ഷിക്കുന്നതിന്. പാസ്‌വേഡ് ശുചിത്വം, അക്കൗണ്ട് ആരോഗ്യം, ഡാറ്റ ലംഘന റിപ്പോർട്ടുകൾ." diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index d2405f785d9..38e81a83bfd 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index d0277bff90d..29d7954de21 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 900f615b204..f4f65b9f536 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB med kryptert fillagring." }, - "premiumSignUpTwoStep": { - "message": "Ytterligere 2-trinnsinnloggingsmuligheter, slik som YubiKey, FIDO U2F, og Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Passordhygiene, kontohelse, og databruddsrapporter som holder hvelvet ditt trygt." diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index d2405f785d9..38e81a83bfd 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 088b2ca8281..a124cede5cf 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB versleutelde opslag voor bijlagen." }, - "premiumSignUpTwoStep": { - "message": "Extra opties voor tweestapsaanmelding zoals YubiKey, FIDO U2F en Duo." + "premiumSignUpTwoStepOptions": { + "message": "Eigen opties voor tweestapsaanmelding zoals YubiKey en Duo." }, "premiumSignUpReports": { "message": "Rapportage op wachtwoordhygiëne, gezondheid van je account en gegevensinbreuk om je kluis veilig te houden." diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index a936f9047e9..46dee838abf 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index cec32d87b39..d6cf45a696e 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 63d724e0b78..ce85ae771dd 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB miejsca na zaszyfrowane załączniki." }, - "premiumSignUpTwoStep": { - "message": "Dodatkowe opcje logowania dwustopniowego, takie jak klucze YubiKey, FIDO U2F oraz Duo." + "premiumSignUpTwoStepOptions": { + "message": "Własnościowe opcje logowania dwuetapowego, takie jak YubiKey i Duo." }, "premiumSignUpReports": { "message": "Raporty bezpieczeństwa haseł, stanu konta i raporty wycieków danych, aby Twoje dane były bezpieczne." diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 781605752cc..8e3aac885c3 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB de armazenamento de arquivos encriptados." }, - "premiumSignUpTwoStep": { - "message": "Opções de autenticação em duas etapas adicionais como YubiKey, FIDO U2F, e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Higiene de senha, saúde da conta, e relatórios sobre violação de dados para manter o seu cofre seguro." diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 5a52982f763..52188383594 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB de armazenamento encriptado para anexos de ficheiros." }, - "premiumSignUpTwoStep": { - "message": "Opções adicionais de verificação de dois passos, como YubiKey, FIDO U2F e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Higiene de palavras-passe, saúde da conta e relatórios de violação de dados para manter o seu cofre seguro." diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 5e9acf893ea..ca46af9d5d5 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB spațiu de stocare criptat pentru atașamente de fișiere." }, - "premiumSignUpTwoStep": { - "message": "Opțiuni adiționale de autentificare în două etape, cum ar fi YubiKey, FIDO U2F și Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Rapoarte privind igiena parolelor, sănătatea contului și breșele de date pentru a vă păstra seiful în siguranță." diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 61944e41ecd..7785e84f1cf 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 ГБ зашифрованного хранилища для вложенных файлов." }, - "premiumSignUpTwoStep": { - "message": "Дополнительные варианты двухэтапной аутентификации, такие как YubiKey, FIDO U2F и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Гигиена паролей, здоровье аккаунта и отчеты об утечках данных для обеспечения безопасности вашего хранилища." diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 6ed26ccdc6f..e2e5347aeb9 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index af7a73301d8..41411606590 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB šifrovaného úložiska." }, - "premiumSignUpTwoStep": { - "message": "Ďalšie možnosti dvojstupňového prihlásenia ako YubiKey, FIDO U2F a Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Správy o sile hesla, zabezpečení účtov a únikoch dát ktoré vám pomôžu udržať vaše kontá v bezpečí." diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 3da6f653bb5..089da060600 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 246757c9f54..b5712de8625 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1ГБ шифровано складиште за прилоге." }, - "premiumSignUpTwoStep": { - "message": "Додатне опције пријаве у два корака као што су YubiKey, FIDO U2F, и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Извештаји о хигијени лозинки, здравственом стању налога и кршењу података да бисте заштитили сеф." @@ -1493,7 +1493,7 @@ "message": "Одјављени сеф захтева да поново потврдите идентитет да бисте му поново приступили." }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "Подесите метод откључавања да бисте променили радњу временског ограничења сефа." }, "lock": { "message": "Закључај", @@ -2110,7 +2110,7 @@ "message": "Пријавите се са другим уређајем" }, "loginInitiated": { - "message": "Login initiated" + "message": "Пријава је покренута" }, "notificationSentDevice": { "message": "Обавештење је послато на ваш уређај." @@ -2250,28 +2250,28 @@ "message": "Препоручено ажурирање поставки" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Потребно је одобрење уређаја. Изаберите опцију одобрења испод:" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "Запамти овај уређај" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Искључите ако се користи јавни уређај" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Одобри са мојим другим уређајем" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "Затражити одобрење администратора" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "Одобрити са главном лозинком" }, "region": { - "message": "Region" + "message": "Регион" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "Потребан је SSO идентификатор организације." }, "eu": { "message": "EU", @@ -2293,40 +2293,40 @@ "message": "Одбијен приступ. Немате дозволу да видите ову страницу." }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Налог је успешно креиран!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "Захтевано је одобрење администратора" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "Ваш захтев је послат вашем администратору." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Бићете обавештени када буде одобрено." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Имате проблема са пријављивањем?" }, "loginApproved": { - "message": "Login approved" + "message": "Пријава је одобрена" }, "userEmailMissing": { - "message": "User email missing" + "message": "Недостаје имејл корисника" }, "deviceTrusted": { - "message": "Device trusted" + "message": "Уређај поуздан" }, "inputRequired": { - "message": "Input is required." + "message": "Унос је потребан." }, "required": { - "message": "required" + "message": "обавезно" }, "search": { - "message": "Search" + "message": "Тражи" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "Унос трба имати најмање $COUNT$ слова.", "placeholders": { "count": { "content": "$1", @@ -2335,7 +2335,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Унос не сме бити већи од $COUNT$ карактера.", "placeholders": { "count": { "content": "$1", @@ -2344,7 +2344,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "Следећи знакови нису дозвољени: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -2353,7 +2353,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "Вредност мора бити најмање $MIN$.", "placeholders": { "min": { "content": "$1", @@ -2362,7 +2362,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "Вредност не сме бити већа од $MAX$.", "placeholders": { "max": { "content": "$1", @@ -2371,17 +2371,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "1 или више имејлова су неважећи" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "Унос не сме да садржи само размак.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "Унос није имејл." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ поље(а) изнад захтевај(у) вашу пажњу.", "placeholders": { "count": { "content": "$1", @@ -2390,22 +2390,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Одабрати --" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Тип за филтрирање --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Преузимање опција..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Нема предмета" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Обриши све" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ још $QUANTITY$", "placeholders": { "quantity": { "content": "$1", @@ -2414,6 +2414,6 @@ } }, "submenu": { - "message": "Submenu" + "message": "Под-мени" } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 3101b8a7228..b224bb083c5 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB krypterad lagring." }, - "premiumSignUpTwoStep": { - "message": "Ytterligare alternativ för tvåstegsverifiering såsom YubiKey, FIDO U2F och Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Lösenordshygien, kontohälsa och dataintrångsrapporter för att skydda ditt valv." diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index d2405f785d9..38e81a83bfd 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index b2210cd5363..32f10e27404 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB of encrypted file storage." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Password hygiene, account health, and data breach reports to keep your vault safe." diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 7919a0f90f8..ff5185c7141 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "Dosya ekleri için 1 GB şifrelenmiş depolama." }, - "premiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F ve Duo gibi iki aşamalı giriş seçenekleri." + "premiumSignUpTwoStepOptions": { + "message": "YubiKey ve Duo gibi marka bazlı iki aşamalı giriş seçenekleri." }, "premiumSignUpReports": { "message": "Kasanızı güvende tutmak için parola hijyeni, hesap sağlığı ve veri ihlali raporları." diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index afe6b9a8ffd..09a7ec512bc 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." }, - "premiumSignUpTwoStep": { - "message": "Додаткові можливості двоетапної перевірки, наприклад, YubiKey, FIDO U2F та Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Гігієна паролів, здоров'я облікового запису, а також звіти про вразливості даних, щоб зберігати ваше сховище в безпеці." @@ -2317,16 +2317,16 @@ "message": "Довірений пристрій" }, "inputRequired": { - "message": "Input is required." + "message": "Необхідно ввести дані." }, "required": { - "message": "required" + "message": "обов'язково" }, "search": { - "message": "Search" + "message": "Пошук" }, "inputMinLength": { - "message": "Input must be at least $COUNT$ characters long.", + "message": "Введені дані мають бути довжиною принаймні $COUNT$ символів.", "placeholders": { "count": { "content": "$1", @@ -2335,7 +2335,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Вхідне значення не повинно перевищувати $COUNT$ символів.", "placeholders": { "count": { "content": "$1", @@ -2344,7 +2344,7 @@ } }, "inputForbiddenCharacters": { - "message": "The following characters are not allowed: $CHARACTERS$", + "message": "Вказані символи заборонені: $CHARACTERS$", "placeholders": { "characters": { "content": "$1", @@ -2353,7 +2353,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "Значення має бути принаймні $MIN$.", "placeholders": { "min": { "content": "$1", @@ -2362,7 +2362,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "Значення не може перевищувати $MAX$.", "placeholders": { "max": { "content": "$1", @@ -2371,17 +2371,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "1 або більше адрес е-пошти недійсні" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "Введене значення не повинно містити лише пробіл.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { - "message": "Input is not an email address." + "message": "Введені дані не є адресою е-пошти." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ поле (поля) вище потребують вашої уваги.", "placeholders": { "count": { "content": "$1", @@ -2390,22 +2390,22 @@ } }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Оберіть--" }, "multiSelectPlaceholder": { - "message": "-- Type to filter --" + "message": "-- Введіть для фільтрування --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Параметри отримання..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Нічого не знайдено" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Очистити все" }, "plusNMore": { - "message": "+ $QUANTITY$ more", + "message": "+ ще $QUANTITY$", "placeholders": { "quantity": { "content": "$1", @@ -2414,6 +2414,6 @@ } }, "submenu": { - "message": "Submenu" + "message": "Підменю" } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index 2cee99f327c..dcbedbe2938 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1GB bộ nhớ lưu trữ tập tin được mã hóa." }, - "premiumSignUpTwoStep": { - "message": "Các tùy chọn xác thực hai lớp bổ sung như YubiKey, FIDO U2F và Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "Thanh lọc mật khẩu, kiểm tra an toàn tài khoản và các báo cáo rò rĩ dữ liệu là để giữ cho kho của bạn an toàn." diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 135104c0fc7..ff74b521eed 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "1 GB 文件附件加密存储。" }, - "premiumSignUpTwoStep": { - "message": "额外的两步登录选项,如 YubiKey、FIDO U2F 和 Duo。" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "密码健康、账户体检以及数据泄露报告,保障您的密码库安全。" diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index be38a8605fe..1de910b1b44 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -1077,8 +1077,8 @@ "premiumSignUpStorage": { "message": "用於檔案附件的 1 GB 的加密檔案儲存空間。" }, - "premiumSignUpTwoStep": { - "message": "YubiKey、FIDO U2F 和 Duo 等額外的兩步驟登入選項。" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpReports": { "message": "密碼健康度檢查、提供帳戶體檢以及資料外洩報告,以保障您的密碼庫安全。" @@ -1414,7 +1414,7 @@ "message": "啟動時詢問 Touch ID" }, "requirePasswordOnStart": { - "message": "Require password or PIN on app start" + "message": "需要在啟動應用程式時輸入密碼或 PIN 碼。" }, "recommendedForSecurity": { "message": "Recommended for security." @@ -2110,7 +2110,7 @@ "message": "使用其他裝置登入" }, "loginInitiated": { - "message": "Login initiated" + "message": "登入已發起" }, "notificationSentDevice": { "message": "已傳送通知至您的裝置。" @@ -2181,7 +2181,7 @@ "message": "You denied a login attempt from another device. If this really was you, try to log in with the device again." }, "justNow": { - "message": "Just now" + "message": "剛剛" }, "requestedXMinutesAgo": { "message": "Requested $MINUTES$ minutes ago", @@ -2193,7 +2193,7 @@ } }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "登入要求已逾期。" }, "thisRequestIsNoLongerValid": { "message": "This request is no longer valid." @@ -2202,7 +2202,7 @@ "message": "Use this device to approve login requests made from other devices." }, "confirmLoginAtemptForMail": { - "message": "Confirm login attempt for $EMAIL$", + "message": "確認 $EMAIL$ 的登入嘗試", "placeholders": { "email": { "content": "$1", @@ -2214,13 +2214,13 @@ "message": "已要求登入" }, "exposedMasterPassword": { - "message": "Exposed Master Password" + "message": "已暴露的主密碼" }, "exposedMasterPasswordDesc": { "message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?" }, "weakAndExposedMasterPassword": { - "message": "Weak and Exposed Master Password" + "message": "強度不足且已暴露的主密碼" }, "weakAndBreachedMasterPasswordDesc": { "message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?" @@ -2253,7 +2253,7 @@ "message": "Device approval required. Select an approval option below:" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "記住這個裝置" }, "uncheckIfPublicDevice": { "message": "Uncheck if using a public device" @@ -2268,7 +2268,7 @@ "message": "Approve with master password" }, "region": { - "message": "Region" + "message": "區域" }, "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." @@ -2293,10 +2293,10 @@ "message": "拒絕存取。您沒有檢視此頁面的權限。" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "成功建立帳號!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "需要管理員批准" }, "adminApprovalRequestSentToAdmins": { "message": "Your request has been sent to your admin." @@ -2305,7 +2305,7 @@ "message": "You will be notified once approved." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "登入時遇到困難?" }, "loginApproved": { "message": "Login approved" diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 9f15d0d24d9..5107d31b1c5 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -90,7 +90,6 @@ export class Main { null, this.memoryStorageService, this.logService, - null, new StateFactory(GlobalState, Account), false // Do not use disk caching because this will get out of sync with the renderer service ); diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index f91ee186705..54af2baea1e 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2023.8.2", + "version": "2023.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2023.8.2", + "version": "2023.8.3", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 2853ab64649..b171046bcef 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2023.8.2", + "version": "2023.8.3", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/scss/misc.scss b/apps/desktop/src/scss/misc.scss index a72b7754c13..8ed6a9b54be 100644 --- a/apps/desktop/src/scss/misc.scss +++ b/apps/desktop/src/scss/misc.scss @@ -561,7 +561,6 @@ h2, h3, label, a, -button, p, img, .box-header, diff --git a/apps/desktop/src/scss/variables.scss b/apps/desktop/src/scss/variables.scss index b99881134d5..e4a2f124768 100644 --- a/apps/desktop/src/scss/variables.scss +++ b/apps/desktop/src/scss/variables.scss @@ -20,10 +20,10 @@ $gray-light: #777; $text-muted: $gray-light; $brand-primary: #175ddc; -$brand-danger: #dd4b39; -$brand-success: #00a65a; +$brand-danger: #c83522; +$brand-success: #017e45; $brand-info: #555555; -$brand-warning: #bf7e16; +$brand-warning: #8b6609; $brand-primary-accent: #1252a3; $background-color: white; @@ -41,7 +41,9 @@ $button-color: lighten($text-color, 40%); $button-color-primary: darken($brand-primary, 8%); $button-color-danger: darken($brand-danger, 10%); -$code-color: #e83e8c; +$code-color: #c01176; +$code-color-dark: #f08dc7; +$code-color-nord: #dbb1d5; $themes: ( light: ( @@ -158,7 +160,7 @@ $themes: ( accountSwitcherTextColor: #ffffff, svgSuffix: "-dark.svg", hrColor: #bac0ce, - codeColor: $code-color, + codeColor: $code-color-dark, ), nord: ( textColor: $nord5, @@ -211,12 +213,12 @@ $themes: ( passwordCountText: $nord5, calloutBorderColor: $nord1, calloutBackgroundColor: $nord2, - toastTextColor: #ffffff, + toastTextColor: #000000, accountSwitcherBackgroundColor: $nord0, accountSwitcherTextColor: $nord5, svgSuffix: "-dark.svg", hrColor: $nord4, - codeColor: $code-color, + codeColor: $code-color-nord, ), ); diff --git a/apps/desktop/src/vault/app/accounts/premium.component.html b/apps/desktop/src/vault/app/accounts/premium.component.html index b91a021f9cc..b3a847dce12 100644 --- a/apps/desktop/src/vault/app/accounts/premium.component.html +++ b/apps/desktop/src/vault/app/accounts/premium.component.html @@ -17,7 +17,7 @@
  • - {{ "premiumSignUpTwoStep" | i18n }} + {{ "premiumSignUpTwoStepOptions" | i18n }}
  • diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts index e18c7548e03..183b57a90c6 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts +++ b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts @@ -94,6 +94,13 @@ export class TrialInitiationComponent implements OnInit, OnDestroy { if (this.referenceData.id === "") { this.referenceData.id = null; + } else { + // Matches "_ga_QBRN562QQQ=value1.value2.session" and captures values and session. + const regex = /_ga_QBRN562QQQ=([^.]+)\.([^.]+)\.(\d+)/; + const match = document.cookie.match(regex); + if (match) { + this.referenceData.session = match[3]; + } } } diff --git a/apps/web/src/app/billing/settings/organization-plans.component.html b/apps/web/src/app/billing/settings/organization-plans.component.html index dea4e21c9d3..b9331579033 100644 --- a/apps/web/src/app/billing/settings/organization-plans.component.html +++ b/apps/web/src/app/billing/settings/organization-plans.component.html @@ -329,7 +329,7 @@ > {{ "submit" | i18n }} - diff --git a/apps/web/src/app/billing/settings/payment-method.component.html b/apps/web/src/app/billing/settings/payment-method.component.html index 9fde6419e16..16e9d7bfcd6 100644 --- a/apps/web/src/app/billing/settings/payment-method.component.html +++ b/apps/web/src/app/billing/settings/payment-method.component.html @@ -112,6 +112,7 @@ *ngIf="showAdjustPayment" > +

    {{ "paymentChargedWithUnpaidSubscription" | i18n }}

    {{ "taxInformation" | i18n }}

    {{ "taxInformationDesc" | i18n }}

    diff --git a/apps/web/src/app/billing/settings/payment-method.component.ts b/apps/web/src/app/billing/settings/payment-method.component.ts index a5509262629..5a8e598f7b8 100644 --- a/apps/web/src/app/billing/settings/payment-method.component.ts +++ b/apps/web/src/app/billing/settings/payment-method.component.ts @@ -4,9 +4,10 @@ import { ActivatedRoute, Router } from "@angular/router"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { BillingPaymentResponse } from "@bitwarden/common/billing/models/response/billing-payment.response"; +import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; +import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; import { VerifyBankRequest } from "@bitwarden/common/models/request/verify-bank.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -28,9 +29,11 @@ export class PaymentMethodComponent implements OnInit { showAdjustPayment = false; showAddCredit = false; billing: BillingPaymentResponse; - org: OrganizationResponse; + org: OrganizationSubscriptionResponse; + sub: SubscriptionResponse; paymentMethodType = PaymentMethodType; organizationId: string; + isUnpaid = false; verifyBankPromise: Promise; taxFormPromise: Promise; @@ -83,13 +86,23 @@ export class PaymentMethodComponent implements OnInit { if (this.forOrganization) { const billingPromise = this.organizationApiService.getBilling(this.organizationId); - const orgPromise = this.organizationApiService.get(this.organizationId); + const organizationSubscriptionPromise = this.organizationApiService.getSubscription( + this.organizationId + ); - [this.billing, this.org] = await Promise.all([billingPromise, orgPromise]); + [this.billing, this.org] = await Promise.all([ + billingPromise, + organizationSubscriptionPromise, + ]); } else { - this.billing = await this.apiService.getUserBillingPayment(); + const billingPromise = this.apiService.getUserBillingPayment(); + const subPromise = this.apiService.getUserSubscription(); + + [this.billing, this.sub] = await Promise.all([billingPromise, subPromise]); } + this.isUnpaid = this.subscription?.status === "unpaid" ?? false; + this.loading = false; } @@ -127,6 +140,7 @@ export class PaymentMethodComponent implements OnInit { return; } + this.showAdjustPayment = true; } @@ -214,4 +228,8 @@ export class PaymentMethodComponent implements OnInit { this.paymentSource.type === PaymentMethodType.GoogleInApp) ); } + + get subscription() { + return this.sub?.subscription ?? this.org?.subscription ?? null; + } } diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 03f20ad2955..b2e44d7e3db 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -17,7 +17,6 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/platform/abstractions/state-migration.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; @@ -27,7 +26,6 @@ import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@ import { PolicyListService } from "../admin-console/core/policy-list.service"; import { HtmlStorageService } from "../core/html-storage.service"; import { I18nService } from "../core/i18n.service"; -import { StateMigrationService } from "../core/state-migration.service"; import { CollectionAdminService } from "../vault/core/collection-admin.service"; import { PasswordRepromptService } from "../vault/core/password-reprompt.service"; @@ -84,11 +82,6 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service"; }, { provide: MessagingServiceAbstraction, useClass: BroadcasterMessagingService }, { provide: ModalServiceAbstraction, useClass: ModalService }, - { - provide: StateMigrationServiceAbstraction, - useClass: StateMigrationService, - deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY], - }, StateService, { provide: BaseStateServiceAbstraction, diff --git a/apps/web/src/app/core/state-migration.service.ts b/apps/web/src/app/core/state-migration.service.ts deleted file mode 100644 index c1d6e2ded5d..00000000000 --- a/apps/web/src/app/core/state-migration.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { StateMigrationService as BaseStateMigrationService } from "@bitwarden/common/platform/services/state-migration.service"; - -import { Account } from "./state/account"; -import { GlobalState } from "./state/global-state"; - -export class StateMigrationService extends BaseStateMigrationService { - protected async migrationStateFrom1To2(): Promise { - await super.migrateStateFrom1To2(); - const globals = (await this.get("global")) ?? this.stateFactory.createGlobal(null); - globals.rememberEmail = (await this.get("rememberEmail")) ?? globals.rememberEmail; - await this.set("global", globals); - } -} diff --git a/apps/web/src/app/core/state/state.service.ts b/apps/web/src/app/core/state/state.service.ts index 60f09ceae36..c95077bfbcc 100644 --- a/apps/web/src/app/core/state/state.service.ts +++ b/apps/web/src/app/core/state/state.service.ts @@ -7,7 +7,6 @@ import { STATE_SERVICE_USE_CACHE, } from "@bitwarden/angular/services/injection-tokens"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { StateMigrationService } from "@bitwarden/common/platform/abstractions/state-migration.service"; import { AbstractMemoryStorageService, AbstractStorageService, @@ -30,7 +29,6 @@ export class StateService extends BaseStateService { @Inject(SECURE_STORAGE) secureStorageService: AbstractStorageService, @Inject(MEMORY_STORAGE) memoryStorageService: AbstractMemoryStorageService, logService: LogService, - stateMigrationService: StateMigrationService, @Inject(STATE_FACTORY) stateFactory: StateFactory, @Inject(STATE_SERVICE_USE_CACHE) useAccountCache = true ) { @@ -39,7 +37,6 @@ export class StateService extends BaseStateService { secureStorageService, memoryStorageService, logService, - stateMigrationService, stateFactory, useAccountCache ); diff --git a/apps/web/src/app/vault/settings/premium.component.html b/apps/web/src/app/vault/settings/premium.component.html index b47bfc1be2b..2c8c9d6146a 100644 --- a/apps/web/src/app/vault/settings/premium.component.html +++ b/apps/web/src/app/vault/settings/premium.component.html @@ -21,7 +21,7 @@
  • - {{ "premiumSignUpTwoStep" | i18n }} + {{ "premiumSignUpTwoStepOptions" | i18n }}
  • diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 7d68eb6f4c6..4b80c3c2119 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GG geënkripteerde berging vir lêeraanhegsels." }, - "premiumSignUpTwoStep": { - "message": "Bykomende tweestapaantekenopsies soos YubiKey, FIDO U2F en Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Noodtoegang" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index c5b8fb72e39..f43798b93c1 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "الوصول الطارئ" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index a51fb880f62..6e037753488 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "Fayl qoşmaları üçün 1 GB şifrələnmiş saxlama sahəsi." }, - "premiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F və Duo kimi iki mərhələli giriş seçimləri." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Fövqəladə vəziyyət müraciəti" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Ödənilməmiş abunəliklər üçün ödəniş üsulunuzdan ödəniş alınacaq." + }, "paymentChargedWithTrial": { "message": "Planınızda 7 günlük ödənişsiz sınaq var. Sınaq müddəti bitənə qədər ödəniş metodundan pul çıxılmayacaq. Faktura, hər $INTERVAL$ bir müntəzəm olaraq icra ediləcək. İstənilən vaxt imtina edə bilərsiniz." }, diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 798ed6258c2..305dcf75092 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 ГБ зашыфраванага сховішча для далучаных файлаў." }, - "premiumSignUpTwoStep": { - "message": "Дадатковыя варыянты двухэтапнага ўваходу, такія як YubiKey, FIDO U2F і Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Экстранны доступ" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "У ваш тарыфны план уключаны выпрабавальны перыяд на 7 дзён. У вас не будзе спагнана плата згодна з выбраным спосабам аплаты пакуль не завяршыцца выпрабавальны перыяд. Вы можаце скасаваць яго ў любы момант." }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index c3530f9b835..9b32d828818 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB пространство за файлове, които се шифрират." }, - "premiumSignUpTwoStep": { - "message": "Двустепенно удостоверяване чрез YubiKey, FIDO U2F и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Авариен достъп" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Сумата за всички неплатени абонаменти ще бъде изискана от Вашия метод за плащане." + }, "paymentChargedWithTrial": { "message": "Планът ви идва с пробен период от 7 дена. Плащането няма се изиска преди това, след което ще се повтаря всеки $INTERVAL$. Може да се откажете по всяко време." }, @@ -5170,10 +5173,10 @@ "message": "Премахване на спонсорирането" }, "removeSponsorshipConfirmation": { - "message": "After removing a sponsorship, you will be responsible for this subscription and related invoices. Are you sure you want to continue?" + "message": "След като премахнете спонсорството, Вие ще поемете отговорността за този абонамент и свързаните с него плащания. Наистина ли искате да продължите?" }, "sponsorshipCreated": { - "message": "Sponsorship created" + "message": "Спонсорството е създадено" }, "emailSent": { "message": "Писмото е изпратено" @@ -5290,7 +5293,7 @@ "message": "Migrated to Key Connector" }, "paymentSponsored": { - "message": "Please provide a payment method to associate with the organization. Don't worry, we won't charge you anything unless you select additional features or your sponsorship expires. " + "message": "Посочете метод за плащане, който да бъде свързан с организацията. Не се притеснявайте – от него няма да бъдат изискани никакви суми, докато не изберете допълнителни функционалности или докато не изтече периода на спонсорирането Ви. " }, "orgCreatedSponsorshipInvalid": { "message": "The sponsorship offer has expired. You may delete the organization you created to avoid a charge at the end of your 7 day trial. Otherwise you may close this prompt to keep the organization and assume billing responsibility." diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 07f6478c2f6..c970aacffb7 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "ফাইল সংযুক্তির জন্য ১ জিবি এনক্রিপ্টেড স্থান।" }, - "premiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F, ও Duo এর মতো অতিরিক্ত দ্বি-পদক্ষেপ লগইন বিকল্পগুলি।" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index b1810d3616a..ea7b495a715 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index bae96a1ffc2..498f1a56615 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB d'emmagatzematge xifrat per als fitxers adjunts." }, - "premiumSignUpTwoStep": { - "message": "Opcions addicionals d'inici de sessió en dues passes com ara YubiKey, FIDO U2F i Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Accés d’emergència" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "El vostre pla inclou una prova gratuïta de 7 dies. El mètode de pagament no es cobrarà fins que no s'haja acabat la prova. Podeu cancel·lar-ho en qualsevol moment." }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 8f06a3acb29..c18a9fa2044 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB šifrovaného úložiště pro přílohy." }, - "premiumSignUpTwoStep": { - "message": "Další možnosti dvoufázového přihlášení, jako je například YubiKey, FIDO U2F a Duo." + "premiumSignUpTwoStepOptions": { + "message": "Volby proprietálních dvoufázových přihlášení jako je YubiKey a Duo." }, "premiumSignUpEmergency": { "message": "Nouzový přístup" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Za nezaplacené předplatné bude naúčtována částka z Vaší platební metody." + }, "paymentChargedWithTrial": { "message": "Vybraný plán obsahuje bezplatnou 7denní zkušební dobu. Částka z Vašeho účtu nebude stržena, dokud tato zkušební doba neuplyne. Předplatné můžete kdykoli zrušit." }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index e416094e183..a71551e7710 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 0124b315f95..3ed144bcaf5 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB krypteret lager til vedhæftede filer." }, - "premiumSignUpTwoStep": { - "message": "Yderligere totrins login-muligheder, såsom YubiKey, FIDO U2F og Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietære totrins-login muligheder, såsom YubiKey og Duo." }, "premiumSignUpEmergency": { "message": "Nødadgang" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Der vil ske opkrævning for evt. ubetalte abonnementer via betalingsmetoden." + }, "paymentChargedWithTrial": { "message": "Dit abonnement indeholder en gratis 7-dages prøveperiode. Din betalingsmetode vil ikke blive debiteret, før prøveperioden er slut. Du kan til enhver tid annullere." }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 615f5397c95..07f5f1d5e7a 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB verschlüsselter Speicherplatz für Dateianhänge." }, - "premiumSignUpTwoStep": { - "message": "Zusätzliche Zwei-Faktor-Authentifizierungsmöglichkeiten wie z.B. YubiKey, FIDO U2F und Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Notfallzugriff" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Deine Zahlungsmethode wird für alle unbezahlten Abonnements belastet." + }, "paymentChargedWithTrial": { "message": "Dein Tarif beinhaltet eine kostenlose 7-Tage-Testversion. Deine Zahlungsart wird nicht belastet, bis die Testphase abgelaufen ist. Du erhältst pro $INTERVAL$ eine Rechnung. Eine Kündigung ist zu jeder Zeit möglich." }, diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 834f96c5069..5103325a343 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB κρυπτογραφημένο αποθηκευτικό χώρο για συνημμένα αρχεία." }, - "premiumSignUpTwoStep": { - "message": "Πρόσθετες επιλογές σύνδεσης δύο παραγόντων, όπως το YubiKey, το FIDO U2F και το Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Πρόσβαση Έκτακτης Ανάγκης" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Το πακέτο σας έρχεται με δωρεάν δοκιμή 7 ημερών. Ο τρόπος πληρωμής σας δεν θα χρεωθεί μέχρι να τελειώσει η δοκιμή. Η χρέωση θα πραγματοποιείται σε επαναλαμβανόμενη βάση κάθε $INTERVAL$. Μπορείτε να το ακυρώσετε οποιαδήποτε στιγμή." }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 02970032d3d..2e747f3cefd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index c772bd2c33b..f342a7e81f3 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 4cfdf991cc0..5f31a22c729 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency Access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. Billing will occur on a recurring basis each $INTERVAL$. You may cancel at any time." }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index e4ab167381f..8034131dcc5 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB ĉifrita stokado por dosieraj aldonaĵoj." }, - "premiumSignUpTwoStep": { - "message": "Pliaj du-paŝaj ensalutaj opcioj kiel YubiKey, FIDO U2F kaj Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Kriza Aliro" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Via plano venas kun senpaga 7-taga provado. Via pagmaniero ne estos ŝargita ĝis la proceso finiĝos. Fakturado okazos ĉiufoje $INTERVAL$. Vi rajtas nuligi iam ajn." }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 7b59b2e8cd4..50e743cb655 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB de almacenamiento de archivos cifrados." }, - "premiumSignUpTwoStep": { - "message": "Opciones adicionales de inicio de sesión de dos pasos como YubiKey, Fido U2F y Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Acceso de emergencia" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your card will not be charged until the trial has ended and on a recurring basis each $INTERVAL$. You may cancel at any time." }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 104b001178c..f31e0832366 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB ulatuses krüpteeritud salvestusruum." }, - "premiumSignUpTwoStep": { - "message": "Lisavõimalused kaheastmeliseks kinnitamiseks, näiteks YubiKey, FIDO U2F ja Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Hädaolukorra ligipääs" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Valitud pakett sisaldab 7 päevast prooviperioodi. Krediitkaardilt ei võeta raha enne, kui prooviperiood läbi saab. Väljatoodud summa debiteeritakse iga $INTERVAL$. Tellimust on võimalik igal ajal tühistada." }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index e4f714fe225..793f4612ca2 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "Eranskinentzako 1GB-eko zifratutako biltegia." }, - "premiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F eta Duo bezalako bi urratseko saio hasierarako aukera gehigarriak." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Larrialdietarako sarbidea" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Zure planak 7 eguneko doako probaldia du. Zure ordainketa ez da kobratuko probaldia amaitu arte. Edozein unetan utz dezakezu bertan behera." }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 73f4c95644c..fe48e771fa0 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "۱ گیگابایت فضای ذخیره‌سازی رمزنگاری شده برای پرونده‌های پیوست." }, - "premiumSignUpTwoStep": { - "message": "گزینه‌های ورود دو مرحله‌ای اضافی مانند YubiKey, FIDO U2F و Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "دسترسی اضطراری" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "طرح شما با یک دوره آزمایشی رایگان ۷ روزه همراه است. تا پایان دوره آزمایشی از روش پرداخت شما هزینه ای کسر نمی‌شود. هر زمان که مایل بودید می‌توانید آن را لغو کنید." }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 016f1ee256d..1606ca35ff6 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 Gt salattua tallennustilaa tiedostoliitteille." }, - "premiumSignUpTwoStep": { - "message": "Muita kaksivaiheisen kirjautumisen todentajia, kuten YubiKey, FIDO U2F ja Duo Security." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Varmuuskäyttö" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Maksutavaltasi veloitetaan kaikki maksamattomat tilauksesta." + }, "paymentChargedWithTrial": { "message": "Tilauksesi sisältää ilmaisen 7 päivän kokeilujakson. Maksutapaasi ei veloiteta ennen kokeilujakson päättymistä. Voit irtisanoa tilauksen koska tahansa." }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 8cc5a44b3f1..1b11567b782 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1GB na naka-encrypt na storage para sa mga file attachment." }, - "premiumSignUpTwoStep": { - "message": "Karagdagang mga opsyon para sa dalawang-hakbang na pag-log in tulad ng YubiKey, FIDO U2F, at Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "May libreng 7 araw na trial ang plano mo. Hindi sisingilin ang paraan mo sa pagbabayad hanggang sa matapos ang libreng trial. Makakapagkansela ka kailanman." }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 62da8e84e12..6bc5b0a1454 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 Go de stockage chiffré pour les fichiers joints." }, - "premiumSignUpTwoStep": { - "message": "Options additionnelles d'authentification à deux facteurs telles que YubiKey, FIDO U2F et Duo." + "premiumSignUpTwoStepOptions": { + "message": "Options de connexion propriétaires à deux facteurs telles que YubiKey et Duo." }, "premiumSignUpEmergency": { "message": "Accès d'urgence" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Votre mode de paiement sera facturé pour tous les abonnements impayés." + }, "paymentChargedWithTrial": { "message": "Votre offre comprend un essai gratuit de 7 jours. Votre mode de paiement ne sera pas facturé avant la fin de la période d'essai. Vous pouvez annuler à tout moment." }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index e416094e183..a71551e7710 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index dbb202a515e..716f2d46071 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 ג'יגה של מקום אחסון מוצפן עבור קבצים מצורפים." }, - "premiumSignUpTwoStep": { - "message": "אפשרויות כניסה דו שלבית מתקדמות כמו YubiKey, FIDO U2F, וגם Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "התוכנית שבחרת מגיעה עם 7 ימי נסיון חינמי. שיטת התשלום שבחרת לא תחויב עד לתום תקופת הנסיון. ביצוע החשבון יתבצע על בסיס מתחדש בכל $INTERVAL$. באפשרותך לבטל בכל עת." }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 53f6e802ee9..cb737f84f44 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index d1682ac5301..3d8d176514c 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB šifriranog prostora za pohranu podataka." }, - "premiumSignUpTwoStep": { - "message": "Dodatne mogućnosti za prijavu dvostrukom autentifikacijom kao što su YubiKey, FIDO U2F i Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Pristup u nuždi" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Plan dolazi s besplatnom probnom verzijom od 7 dana. Tvoj način plaćanja neće biti terećen dok ne završi probno razdoblje. Možeš otkazati u bilo kojem trenutku." }, diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index ddd4f5872bb..c8c8439626f 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB titkosított tárhely a fájlmellékleteknek." }, - "premiumSignUpTwoStep": { - "message": "További olyan kétlépcsős bejelentkezési opciók mint a YubiKey, FIDO U2F és Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Sürgősségi hozzáférés" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "A fizetési mód minden ki nem fizetett előfizetésért megterhelésre kerül." + }, "paymentChargedWithTrial": { "message": "A jelenlegi csomag 7 napos ingyenes próbaidőszakot tartalmaz. A fizetési módot az időszak végéig nem terheljük. A csomag bármikor lemondható." }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 344f13e2df0..a88799f1adc 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "Penyimpanan terenkripsi 1 GB untuk lampiran file." }, - "premiumSignUpTwoStep": { - "message": "Opsi login dua langkah tambahan seperti YubiKey, FIDO U2F, dan Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Akses darurat" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Paket Anda dilengkapi dengan uji coba gratis selama 7 hari. Metode pembayaran Anda tidak akan ditagih hingga uji coba berakhir. Penagihan akan dilakukan secara berulang setiap $INTERVAL$. Anda dapat membatalkannya kapan saja." }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 4900c68ae7c..c082b3dd9ca 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB di spazio di archiviazione criptato per gli allegati." }, - "premiumSignUpTwoStep": { - "message": "Più opzioni di verifica in due passaggi come YubiKey, FIDO U2F, e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Accesso di emergenza" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Eventuali abbonamenti non pagati saranno addebitati sul tuo metodo di pagamento." + }, "paymentChargedWithTrial": { "message": "Il tuo piano include una prova gratis di 7 giorni. Il tuo metodo di pagamento non sarà addebitato fino alla fine del periodo di prova. Puoi cancellarlo in qualsiasi momento." }, @@ -7160,7 +7163,7 @@ "message": "Accesso effettuato!" }, "smBetaEndedDesc": { - "message": "La beta del Gestore dei Segreti è terminata in $BETA_ENDING_DATE$. Ti rimangono $DAYS$ giorni per aggiungere il Gestore dei Segreti al tuo abbonamento a pagamento e mantenere l'accesso ai dati del Gestore dei Segreti. Contatta il Successo del Cliente per aggiungere il Gestore dei Segreti al tuo abbonamento.", + "message": "La beta del Gestore dei Segreti è terminata in $BETA_ENDING_DATE$. Ti rimangono $DAYS$ giorni per aggiungere il Gestore dei Segreti al tuo abbonamento a pagamento e mantenere l'accesso ai dati del Gestore dei Segreti. Contatta l'assistenza per aggiungere il Gestore dei Segreti al tuo abbonamento.", "placeholders": { "beta_ending_date": { "content": "$1", diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 7a6c9c17439..95647694572 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1GB の暗号化されたファイルストレージ" }, - "premiumSignUpTwoStep": { - "message": "YubiKey、FIDO U2F、Duoなどの追加の2段階認証ログインオプション" + "premiumSignUpTwoStepOptions": { + "message": "YubiKey、Duo などのプロプライエタリな2段階認証オプション。" }, "premiumSignUpEmergency": { "message": "緊急アクセス" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "未払いのサブスクリプションについては、指定した支払い方法へ請求されます。" + }, "paymentChargedWithTrial": { "message": "ご利用のプランでは、7日間の無料トライアルが可能です。トライアル期間が終わるまでは課金されません。トライアル終了後、$INTERVAL$毎に請求されます。いつでもキャンセルできます。" }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index b0b9f6bd139..5f461f4b70b 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index e416094e183..a71551e7710 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index d22c69e97e2..94145e99c35 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "ಫೈಲ್ ಲಗತ್ತುಗಳಿಗಾಗಿ 1 ಜಿಬಿ ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ ಸಂಗ್ರಹ." }, - "premiumSignUpTwoStep": { - "message": "ಹೆಚ್ಚುವರಿ ಎರಡು-ಹಂತದ ಲಾಗಿನ್ ಆಯ್ಕೆಗಳಾದ ಯೂಬಿಕೆ, ಎಫ್‌ಐಡಿಒ ಯು 2 ಎಫ್, ಮತ್ತು ಡ್ಯುವೋ." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "ತುರ್ತು ಪ್ರವೇಶ" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "ನಿಮ್ಮ ಯೋಜನೆ 7 ದಿನಗಳ ಉಚಿತ ಪ್ರಯೋಗದೊಂದಿಗೆ ಬರುತ್ತದೆ. ಪ್ರಯೋಗ ಮುಗಿಯುವವರೆಗೆ ನಿಮ್ಮ ಪಾವತಿ ವಿಧಾನಕ್ಕೆ ಶುಲ್ಕ ವಿಧಿಸಲಾಗುವುದಿಲ್ಲ. ಪ್ರತಿ $INTERVAL$ ಮರುಕಳಿಸುವ ಆಧಾರದ ಮೇಲೆ ಬಿಲ್ಲಿಂಗ್ ಸಂಭವಿಸುತ್ತದೆ. ನೀವು ಯಾವುದೇ ಸಮಯದಲ್ಲಿ ರದ್ದುಗೊಳಿಸಬಹುದು." }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index bd8ee6b5db8..9900cfa742e 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1GB의 암호화된 파일 저장소." }, - "premiumSignUpTwoStep": { - "message": "YubiKey나 FIDO U2F, Duo 등의 추가적인 2단계 인증 옵션." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "긴급 접근" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "귀하의 플랜은 7일 무료 평가판입니다. 평가 기간이 만료될 때까지 카드에서 대금이 지불되지 않습니다. 이후 정기적으로 매 $INTERVAL$ 청구됩니다. 언제든지 취소할 수 있습니다." }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index d2984de94bc..a1f9a5bc382 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB šifrētas krātuves datņu pielikumiem." }, - "premiumSignUpTwoStep": { - "message": "Tādas papildu divpakāpju pieteikšanās iespējas kā YubiKey, FIDO U2F un Duo." + "premiumSignUpTwoStepOptions": { + "message": "Tādas slēgtā pirmavota divpakāpju pieteikšanās iespējas kā YubiKey un Duo." }, "premiumSignUpEmergency": { "message": "Ārkārtas piekļuve" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Izvēlētais maksājumu veids tiks izmantots jebkuru neapmaksātu abonementu apmaksai." + }, "paymentChargedWithTrial": { "message": "Pašreizējā plānā ir iekļauts bezmaksas 7 dienu izmēģinājuma laiks. Izvēlētais apmaksas veids netiks izmantots līdz izmēģinājuma beigā. Norēķini notiks katru $INTERVAL$. To var atcelt jebkurā brīdī." }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 483a78d6491..1623cf8c2f4 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "ഫയൽ അറ്റാച്ചുമെന്റുകൾക്കായി 1 GB എൻക്രിപ്റ്റുചെയ്‌ത സ്റ്റോറേജ്." }, - "premiumSignUpTwoStep": { - "message": "രണ്ട്-ഘട്ട പ്രവേശന ഓപ്ഷനുകളായ Yubikey, FIDO U2F, Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. Billing will occur on a recurring basis each $INTERVAL$. You may cancel at any time." }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index e416094e183..a71551e7710 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index e416094e183..a71551e7710 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 4f996710ba8..5d0b7f5eafd 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB med kryptert fillagring." }, - "premiumSignUpTwoStep": { - "message": "Ytterligere 2-trinnsinnloggingsmuligheter, slik som YubiKey, FIDO U2F, og Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Nødtilgang" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Funksjonsplanen din kommer med en gratis 7-dagersprøveperiode. Din betalingsmetode vil ikke bli trekt før prøveperiode har utløpt. Regningstrekk vil skje på en gjentakende basis hver(t) $INTERVAL$. Du kan avbryte når som helst." }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index c28f306acb3..4a8c2e06c0c 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 6b12ef13058..dcead7cd8fd 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB versleutelde opslag voor bijlagen." }, - "premiumSignUpTwoStep": { - "message": "Extra tweestapsaanmeldingsopties zoals YubiKey, FIDO U2F en Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Noodtoegang" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "We brengen onbetaalde abonnementen in rekening bij je betalingsmethode." + }, "paymentChargedWithTrial": { "message": "Je lidmaatschap omvat een gratis proefperiode van 7 dagen. Kosten worden pas in rekening gebracht als de proefperiode voorbij is. De betaling vindt ieder(e) $INTERVAL$ op terugkerende basis plaats. Je kunt op ieder moment opzeggen." }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 81ccc76c41c..22e4a7d30e1 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index e416094e183..a71551e7710 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 757d09a97e5..80e7dd695d9 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB przestrzeni na zaszyfrowane załączniki." }, - "premiumSignUpTwoStep": { - "message": "Dodatkowe opcje logowania dwustopniowego, takie jak klucze YubiKey, FIDO U2F oraz Duo." + "premiumSignUpTwoStepOptions": { + "message": "Własnościowe opcje logowania dwuetapowego, takie jak YubiKey i Duo." }, "premiumSignUpEmergency": { "message": "Dostęp awaryjny" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Twoja metoda płatności zostanie obciążona opłatą za wszystkie nieopłacone subskrypcje." + }, "paymentChargedWithTrial": { "message": "Twój plan zawiera 7-dniowy okres próbny. W tym czasie nie poniesiesz żadnych kosztów. Możesz zrezygnować z niego w każdej chwili." }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 2b9a1844de8..3d931f81d07 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB de armazenamento de arquivos encriptados." }, - "premiumSignUpTwoStep": { - "message": "Opções adicionais de login em duas etapas, como YubiKey, FIDO U2F e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Acesso de Emergência" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Seu método de pagamento será cobrado por qualquer assinatura não paga." + }, "paymentChargedWithTrial": { "message": "Seu plano vem com um teste gratuito de 7 dias. Seu cartão não será cobrado até que o período de teste termine e de forma recorrente a cada $INTERVAL$. Você pode cancelar a qualquer momento." }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index fe115a0ff91..0bf7e684824 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB de armazenamento encriptado para anexos de ficheiros." }, - "premiumSignUpTwoStep": { - "message": "Opções adicionais de verificação de dois passos, como YubiKey, FIDO U2F e Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Acesso de emergência" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "O seu método de pagamento será cobrado por quaisquer subscrições não pagas." + }, "paymentChargedWithTrial": { "message": "O seu plano inclui um teste gratuito de 7 dias. O seu método de pagamento não será cobrado até ao fim do período de avaliação. Pode cancelar a qualquer momento." }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 118c3570ecc..27e9d4c17b7 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB stocare criptată pentru fișiere atașate." }, - "premiumSignUpTwoStep": { - "message": "Opțiuni suplimentare de conectare în două etape, cum ar fi YubiKey, FIDO U2F și Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Acces de urgență" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Planul dvs. vine cu o încercare gratuită de 7 zile. Metoda dvs. de plată nu va fi facturată până la sfârșitul perioadei de încercare. Puteți anula în orice moment." }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 55b6724ad61..af9a088993f 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 ГБ зашифрованного хранилища для вложенных файлов." }, - "premiumSignUpTwoStep": { - "message": "Дополнительные варианты двухэтапной аутентификации, такие как YubiKey, FIDO U2F и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Проприетарные варианты двухэтапной аутентификации, такие как YubiKey или Duo." }, "premiumSignUpEmergency": { "message": "Экстренный доступ" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "За любые неоплаченные подписки будет взиматься плата с вашего способа оплаты." + }, "paymentChargedWithTrial": { "message": "Ваш план включает семидневную бесплатную пробную версию. Ваш метод оплаты не будет использован до окончания пробной версии. Оплата будет выполняться каждый $INTERVAL$. Вы можете отказаться в любое время." }, diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 855a48deda9..adff63eb2fc 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 0ab0af5a3e6..fd1d6d707bf 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB šifrovaného úložiska pre prílohy." }, - "premiumSignUpTwoStep": { - "message": "Ďalšie možnosti dvojstupňového prihlásenia ako YubiKey, FIDO U2F a Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Núdzový prístup" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Všetky nezaplatené predplatné budú účtované prostredníctvom vášho spôsobu platby." + }, "paymentChargedWithTrial": { "message": "Váš plán ponúka 7-dňovú skúšobnú dobu zadarmo. Z vašej platobnej metódy nebude stiahnutý poplatok, kým sa neskončí skúšobná doba. Plán môžete kedykoľvek zrušiť." }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 1cb26cd5d8e..c3f3e479f1f 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Dostop v sili" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 3683cf2b20c..f1ae3e829c1 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -955,7 +955,7 @@ "message": "Копирај верификациони код" }, "copyUuid": { - "message": "Copy UUID" + "message": "Копирај UUID" }, "warning": { "message": "Упозорење" @@ -1365,7 +1365,7 @@ "message": "Прикажи иконе сајтова" }, "faviconDesc": { - "message": "Прикажи препознатљиву слику поред сваке ставке за пријаву." + "message": "Прикажи препознатљиву иконицу поред сваке ставке за пријаву." }, "enableFullWidth": { "message": "Упали пуни ширину распореда", @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1ГБ шифровано складиште за прилоге." }, - "premiumSignUpTwoStep": { - "message": "Додатне опције пријаве у два корака као што су YubiKey, FIDO U2F, и Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Улаз у хитним случајевима" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Ваш план долази са бесплатним 7-дневним пробним периодом. Начин плаћања неће бити наплаћен док се пробно време не заврши. Наплата ће се вршити периодично, сваки $INTERVAL$. Можете отказати било када." }, @@ -3537,7 +3540,7 @@ "message": "Изаберите када ће сеф истећи и да изврши одабрану радњу." }, "vaultTimeoutLogoutDesc": { - "message": "Choose when your vault will be logged out." + "message": "Одаберите када ће ваш сеф бити одјављен." }, "oneMinute": { "message": "1 минут" @@ -4936,7 +4939,7 @@ "message": "Онемогућите извоз личног сефа" }, "disablePersonalVaultExportDescription": { - "message": "Do not allow members to export data from their individual vault." + "message": "Не дозволи члановима да извозе податке из свог индивидуалног сефа." }, "vaultExportDisabled": { "message": "Извоз сефа онемогућен" @@ -5440,7 +5443,7 @@ "message": "Извоз сефа организације" }, "exportingIndividualVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.", + "message": "Само појединачне ставке сефа повезане са $EMAIL$ ће бити извењене. Ставке организационог сефа неће бити укључене. Само информације о ставкама из сефа ће бити извезене и неће укључивати повезане прилоге.", "placeholders": { "email": { "content": "$1", @@ -6839,58 +6842,58 @@ "message": "Ажурирати KDF подешавања" }, "loginInitiated": { - "message": "Login initiated" + "message": "Пријава је покренута" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Потребно је одобрење уређаја. Изаберите опцију одобрења испод:" }, "rememberThisDevice": { - "message": "Remember this device" + "message": "Запамти овај уређај" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Искључите ако се користи јавни уређај" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "Одобри са мојим другим уређајем" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "Затражити одобрење администратора" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "Одобрити са главном лозинком" }, "trustedDeviceEncryption": { - "message": "Trusted device encryption" + "message": "Шифровање поузданог уређаја" }, "trustedDevices": { "message": "Поуздани уређаји" }, "memberDecryptionOptionTdeDescriptionPartOne": { - "message": "Once authenticated, members will decrypt vault data using a key stored on their device. The", + "message": "Када се аутентификују, чланови ће дешифровати податке из сефљ користећи кључ сачуван на њиховом уређају", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO Required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkOne": { - "message": "single organization", + "message": "јединствена организација", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartTwo": { - "message": "policy,", + "message": "полиса,", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkTwo": { - "message": "SSO required", + "message": "SSO потребан", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartThree": { - "message": "policy, and", + "message": "полиса, и", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionLinkThree": { - "message": "account recovery administration", + "message": "администрација опоравка налога", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescriptionPartFour": { - "message": "policy with automatic enrollment will turn on when this option is used.", + "message": "полисе са аутоматским уписом ће се укључити када се користи ова опција.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The single organization policy, SSO required policy, and account recovery administration policy with automatic enrollment will turn on when this option is used.'" }, "notFound": { @@ -6981,7 +6984,7 @@ "message": "Уклањање чланова који немају главну лозинку без постављања једне за њих може ограничити приступ њиховом пуном налогу." }, "approvedAuthRequest": { - "message": "Approved device for $ID$.", + "message": "Одобрен уређај за $ID$.", "placeholders": { "id": { "content": "$1", @@ -6990,7 +6993,7 @@ } }, "rejectedAuthRequest": { - "message": "Denied device for $ID$.", + "message": "Одбијен уређај за $ID$.", "placeholders": { "id": { "content": "$1", @@ -6999,7 +7002,7 @@ } }, "requestedDeviceApproval": { - "message": "Requested device approval." + "message": "Затражено је одобрење уређаја." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Започните своју 7-дневну бесплатну пробну Bitwarden-а за $ORG$", @@ -7023,28 +7026,28 @@ "message": "Одабрана застава" }, "accountSuccessfullyCreated": { - "message": "Account successfully created!" + "message": "Налог је успешно креиран!" }, "adminApprovalRequested": { - "message": "Admin approval requested" + "message": "Захтевано је одобрење администратора" }, "adminApprovalRequestSentToAdmins": { - "message": "Your request has been sent to your admin." + "message": "Ваш захтев је послат вашем администратору." }, "youWillBeNotifiedOnceApproved": { - "message": "You will be notified once approved." + "message": "Бићете обавештени када буде одобрено." }, "troubleLoggingIn": { - "message": "Trouble logging in?" + "message": "Имате проблема са пријављивањем?" }, "loginApproved": { - "message": "Login approved" + "message": "Пријава је одобрена" }, "userEmailMissing": { - "message": "User email missing" + "message": "Недостаје имејл корисника" }, "deviceTrusted": { - "message": "Device trusted" + "message": "Уређај поуздан" }, "sendsNoItemsTitle": { "message": "Нема активних Sends", @@ -7070,13 +7073,13 @@ "message": "For engineering and DevOps teams to manage secrets throughout the software development lifecycle." }, "free2PersonOrganization": { - "message": "Free 2-person Organizations" + "message": "Бесплатна организација за 2 особе" }, "unlimitedSecrets": { - "message": "Unlimited secrets" + "message": "Неограничене тајне" }, "unlimitedProjects": { - "message": "Unlimited projects" + "message": "Неограничени пројекти" }, "projectsIncluded": { "message": "$COUNT$ projects included", @@ -7106,13 +7109,13 @@ } }, "addSecretsManager": { - "message": "Add Secrets Manager" + "message": "Додати Менаџер Тајни" }, "addSecretsManagerUpgradeDesc": { "message": "Add Secrets Manager to your upgraded plan to maintain access to any secrets created with your previous plan." }, "additionalServiceAccounts": { - "message": "Additional service accounts" + "message": "Додатни сервисни налози" }, "includedServiceAccounts": { "message": "Ваш план долази са $COUNT$ налога сервиса.", @@ -7133,16 +7136,16 @@ } }, "passwordManagerPlanPrice": { - "message": "Password Manager plan price" + "message": "Цена плана менаџера лозинки" }, "secretsManagerPlanPrice": { - "message": "Secrets Manager plan price" + "message": "Цена плана менаџера тајни" }, "passwordManager": { "message": "Менаџер лозинки" }, "freeOrganization": { - "message": "Free Organization" + "message": "Бесплатна организација" }, "limitServiceAccounts": { "message": "Limit service accounts (optional)" @@ -7157,7 +7160,7 @@ "message": "Max potential service account cost" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "Пријављено!" }, "smBetaEndedDesc": { "message": "Бета менаџера тајни се завршио $BETA_ENDING_DATE$. Остало вам је $DAYS$ дана да додате Менаџер тајни у вашу претплату и да задржите приступ подацима Манагера тајни. Контактирајте подршку да додате Менаџер тајни у своју претплату.", @@ -7179,6 +7182,6 @@ "message": "Бета" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "Већ имате налог?" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 3daeae10af7..a50306355db 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index df8d315d6d7..7d2e12239f7 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB krypterad lagring." }, - "premiumSignUpTwoStep": { - "message": "Ytterligare alternativ för tvåstegsverifiering såsom YubiKey, FIDO U2F och Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Nödåtkomst" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Din betalningsmetod kommer att debiteras för eventuella obetalda prenumerationer." + }, "paymentChargedWithTrial": { "message": "Din plan kommer med en kostnadsfri 7-dagars provperiod. Din betalningsmetod kommer inte att debiteras förrän provperioden har upphört. Du kan avbryta när som helst." }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index e416094e183..a71551e7710 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 7173b6d8d90..25206db2e37 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Your plan comes with a free 7 day trial. Your payment method will not be charged until the trial has ended. You may cancel at any time." }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 9111d52fce2..55b2c51548e 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "Dosya ekleri için 1 GB şifrelenmiş depolama." }, - "premiumSignUpTwoStep": { - "message": "YubiKey, FIDO U2F ve Duo gibi iki aşamalı giriş seçenekleri." + "premiumSignUpTwoStepOptions": { + "message": "YubiKey ve Duo gibi marka bazlı iki aşamalı giriş seçenekleri." }, "premiumSignUpEmergency": { "message": "Acil durum erişimi" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Paketiniz 7 günlük ücretsiz deneme süresiyle geliyor. Deneme süresi bitene kadar sizden ücret alınmayacak. İstediğiniz zaman aboneliğinizi iptal edebilirsiniz." }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index f3e5519cd76..4c336c0b30d 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." }, - "premiumSignUpTwoStep": { - "message": "Додаткові можливості двоетапної перевірки, наприклад, YubiKey, FIDO U2F та Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Екстрений доступ" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Ваш тарифний план має 7 днів безплатного пробного періоду. З вас не буде стягнуто плату до завершення цього періоду. Ви можете скасувати це в будь-який час." }, diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 2095ec36cb1..fb25525d61a 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB encrypted storage for file attachments." }, - "premiumSignUpTwoStep": { - "message": "Additional two-step login options such as YubiKey, FIDO U2F, and Duo." + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "Emergency access" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "Gói của bạn đi kèm với 7 ngày dùng thử miễn phí. Phương thức thanh toán của bạn sẽ không bị tính phí cho đến khi hết thời gian dùng thử. Việc thanh toán sẽ thực hiện định kỳ mỗi $INTERVAL$. Bạn có thể hủy bỏ bất cứ lúc nào." }, diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index f38192eb899..be07ab7e208 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -606,7 +606,7 @@ "message": "登录或者创建一个账户来访问您的安全密码库。" }, "loginWithDevice": { - "message": "设备登录" + "message": "使用设备登录" }, "loginWithDeviceEnabledNote": { "message": "设备登录必须在 Bitwarden 应用程序的设置中设启用。需要其他选项吗?" @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "1 GB 文件附件加密存储。" }, - "premiumSignUpTwoStep": { - "message": "额外的两步登录选项,如 YubiKey、FIDO U2F 和 Duo。" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "紧急访问" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "任何未付费订阅都将通过您的付款方式收取费用。" + }, "paymentChargedWithTrial": { "message": "您的计划包含了 7 天的免费试用期。在试用期结束前,不会从您的付款方式中扣款。您可以随时取消。" }, @@ -2448,7 +2451,7 @@ "message": "组织已创建" }, "organizationReadyToGo": { - "message": "你的组织准备好了!" + "message": "您的新组织已准备就绪!" }, "organizationUpgraded": { "message": "组织已升级" @@ -6936,7 +6939,7 @@ "message": "设备信息" }, "time": { - "message": "Time" + "message": "时间" }, "denyAllRequests": { "message": "拒绝所有请求" diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index c988b11a946..acdd1aa1a79 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -1924,8 +1924,8 @@ "premiumSignUpStorage": { "message": "用於檔案附件的 1 GB 的加密檔案儲存空間。" }, - "premiumSignUpTwoStep": { - "message": "YubiKey、FIDO U2F 和 Duo 等額外的兩步驟登入選項。" + "premiumSignUpTwoStepOptions": { + "message": "Proprietary two-step login options such as YubiKey and Duo." }, "premiumSignUpEmergency": { "message": "緊急存取" @@ -2040,6 +2040,9 @@ } } }, + "paymentChargedWithUnpaidSubscription": { + "message": "Your payment method will be charged for any unpaid subscriptions." + }, "paymentChargedWithTrial": { "message": "您的方案包含了 7 天的免費試用。在試用期結束之前,不會從您的付款方式中扣款。您可以隨時取消。" }, diff --git a/apps/web/src/scss/variables.scss b/apps/web/src/scss/variables.scss index 222c43fed19..af61daff512 100644 --- a/apps/web/src/scss/variables.scss +++ b/apps/web/src/scss/variables.scss @@ -4,10 +4,10 @@ $primary: #175ddc; $primary-accent: #1252a3; $secondary: #ced4da; $secondary-alt: #1a3b66; -$success: #00a65a; +$success: #017e45; $info: #555555; -$warning: #bf7e16; -$danger: #dd4b39; +$warning: #8b6609; +$danger: #c83522; $white: #ffffff; // Bootstrap Variable Overrides @@ -88,6 +88,7 @@ $mfaTypes: 0, 2, 3, 4, 6; $lightDangerHover: #c43421; $lightInputColor: #465057; $lightInputPlaceholderColor: #b6b8b8; +$lightCodeColor: #c01176; // Dark @@ -107,6 +108,7 @@ $darkDarkBlue1: #2f343d; $darkDarkBlue2: #1f242e; $darkInputColor: $white; $darkInputPlaceholderColor: $darkGrey1; +$darkCodeColor: #f08dc7; $themes: ( light: ( @@ -167,7 +169,7 @@ $themes: ( calloutBackground: #fafafa, calloutColor: #212529, cdkDraggingBackground: $white, - codeColor: #e83e8c, + codeColor: $lightCodeColor, dropdownBackground: $white, dropdownHover: rgba(0, 0, 0, 0.06), dropdownTextColor: $body-color, @@ -276,7 +278,7 @@ $themes: ( calloutBackground: $darkBlue2, calloutColor: $white, cdkDraggingBackground: $darkDarkBlue1, - codeColor: #e83e8c, + codeColor: $darkCodeColor, dropdownBackground: $darkDarkBlue1, dropdownHover: rgba(255, 255, 255, 0.03), dropdownTextColor: $white, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index c6a807f7395..426542823f9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -40,7 +40,7 @@ export class SecretDialogComponent implements OnInit { validators: [Validators.required, Validators.maxLength(500), BitValidators.trimValidator], updateOn: "submit", }), - value: new FormControl("", [Validators.required, Validators.maxLength(3500)]), + value: new FormControl("", [Validators.required, Validators.maxLength(25000)]), notes: new FormControl("", { validators: [Validators.maxLength(7000), BitValidators.trimValidator], updateOn: "submit", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html index e065141e828..a6660b3d3ac 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html @@ -65,16 +65,12 @@
    -
    -
    +
    {{ secret.name }}
    {{ secret.id }}
    -
    {{ secret.name }}
    diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.html index e4990f0111a..03dc17b98d5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.html @@ -9,4 +9,5 @@ (restoreSecretsEvent)="openRestoreSecret($event)" [secrets]="secrets$ | async" [trash]="true" + (copySecretUuidEvent)="copySecretUuid($event)" > diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts index 83f510ed569..e92a01ed279 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts @@ -2,10 +2,13 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; import { SecretListView } from "../models/view/secret-list.view"; import { SecretService } from "../secrets/secret.service"; +import { SecretsListComponent } from "../shared/secrets-list.component"; import { SecretHardDeleteDialogComponent, @@ -28,6 +31,8 @@ export class TrashComponent implements OnInit { constructor( private route: ActivatedRoute, private secretService: SecretService, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, private dialogService: DialogService ) {} @@ -65,4 +70,8 @@ export class TrashComponent implements OnInit { }, }); } + + copySecretUuid(id: string) { + SecretsListComponent.copySecretUuid(id, this.platformUtilsService, this.i18nService); + } } diff --git a/libs/angular/src/components/settings/vault-timeout-input.component.ts b/libs/angular/src/components/settings/vault-timeout-input.component.ts index b0dec5affb7..d23fbe29367 100644 --- a/libs/angular/src/components/settings/vault-timeout-input.component.ts +++ b/libs/angular/src/components/settings/vault-timeout-input.component.ts @@ -15,6 +15,14 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +interface VaultTimeoutFormValue { + vaultTimeout: number | null; + custom: { + hours: number | null; + minutes: number | null; + }; +} + @Directive() export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy, OnChanges @@ -70,26 +78,38 @@ export class VaultTimeoutInputComponent this.applyVaultTimeoutPolicy(); }); - this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => { - if (this.onChange) { - this.onChange(this.getVaultTimeout(value)); - } - }); + this.form.valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe((value: VaultTimeoutFormValue) => { + if (this.onChange) { + this.onChange(this.getVaultTimeout(value)); + } + }); - // Assign the previous value to the custom fields + // Assign the current value to the custom fields + // so that if the user goes from a numeric value to custom + // we can initialize the custom fields with the current value + // ex: user picks 5 min, goes to custom, we want to show 0 hr, 5 min in the custom fields this.form.controls.vaultTimeout.valueChanges .pipe( filter((value) => value !== VaultTimeoutInputComponent.CUSTOM_VALUE), takeUntil(this.destroy$) ) - .subscribe((_) => { - const current = Math.max(this.form.value.vaultTimeout, 0); - this.form.patchValue({ - custom: { - hours: Math.floor(current / 60), - minutes: current % 60, + .subscribe((value) => { + const current = Math.max(value, 0); + + // This cannot emit an event b/c it would cause form.valueChanges to fire again + // and we are already handling that above so just silently update + // custom fields when vaultTimeout changes to a non-custom value + this.form.patchValue( + { + custom: { + hours: Math.floor(current / 60), + minutes: current % 60, + }, }, - }); + { emitEvent: false } + ); }); this.canLockVault$ = this.vaultTimeoutSettingsService @@ -113,7 +133,7 @@ export class VaultTimeoutInputComponent } } - getVaultTimeout(value: any) { + getVaultTimeout(value: VaultTimeoutFormValue) { if (value.vaultTimeout !== VaultTimeoutInputComponent.CUSTOM_VALUE) { return value.vaultTimeout; } diff --git a/libs/angular/src/directives/copy-text.directive.ts b/libs/angular/src/directives/copy-text.directive.ts index e3298c214c0..b595085e43b 100644 --- a/libs/angular/src/directives/copy-text.directive.ts +++ b/libs/angular/src/directives/copy-text.directive.ts @@ -1,5 +1,6 @@ import { Directive, ElementRef, HostListener, Input } from "@angular/core"; +import { ClientType } from "@bitwarden/common/enums"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @Directive({ @@ -15,6 +16,9 @@ export class CopyTextDirective { return; } - this.platformUtilsService.copyToClipboard(this.copyText, { window: window }); + const timeout = this.platformUtilsService.getClientType() === ClientType.Desktop ? 100 : 0; + setTimeout(() => { + this.platformUtilsService.copyToClipboard(this.copyText, { window: window }); + }, timeout); } } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 14b26ca43da..df64c25c914 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -77,7 +77,6 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/platform/abstractions/state-migration.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -94,7 +93,6 @@ import { EncryptServiceImplementation } from "@bitwarden/common/platform/service import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/multithread-encrypt.service.implementation"; import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; -import { StateMigrationService } from "@bitwarden/common/platform/services/state-migration.service"; import { StateService } from "@bitwarden/common/platform/services/state.service"; import { ValidationService } from "@bitwarden/common/platform/services/validation.service"; import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; @@ -480,16 +478,10 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; SECURE_STORAGE, MEMORY_STORAGE, LogService, - StateMigrationServiceAbstraction, STATE_FACTORY, STATE_SERVICE_USE_CACHE, ], }, - { - provide: StateMigrationServiceAbstraction, - useClass: StateMigrationService, - deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY], - }, { provide: VaultExportServiceAbstraction, useClass: VaultExportService, diff --git a/libs/common/src/auth/login-strategies/login.strategy.spec.ts b/libs/common/src/auth/login-strategies/login.strategy.spec.ts index f7128b35dfb..735135f4061 100644 --- a/libs/common/src/auth/login-strategies/login.strategy.spec.ts +++ b/libs/common/src/auth/login-strategies/login.strategy.spec.ts @@ -41,10 +41,7 @@ import { IdentityCaptchaResponse } from "../models/response/identity-captcha.res import { IdentityTokenResponse } from "../models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response"; import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response"; -import { - IUserDecryptionOptionsServerResponse, - UserDecryptionOptionsResponse, -} from "../models/response/user-decryption-options/user-decryption-options.response"; +import { IUserDecryptionOptionsServerResponse } from "../models/response/user-decryption-options/user-decryption-options.response"; import { PasswordLogInStrategy } from "./password-login.strategy"; @@ -65,10 +62,6 @@ const name = "NAME"; const defaultUserDecryptionOptionsServerResponse: IUserDecryptionOptionsServerResponse = { HasMasterPassword: true, }; -const userDecryptionOptions = new UserDecryptionOptionsResponse( - defaultUserDecryptionOptionsServerResponse -); -const acctDecryptionOptions = AccountDecryptionOptions.fromResponse(userDecryptionOptions); const decodedToken = { sub: userId, @@ -197,7 +190,7 @@ describe("LogInStrategy", () => { }, }, keys: new AccountKeys(), - decryptionOptions: acctDecryptionOptions, + decryptionOptions: AccountDecryptionOptions.fromResponse(idTokenResponse), }) ); expect(messagingService.send).toHaveBeenCalledWith("loggedIn"); diff --git a/libs/common/src/auth/login-strategies/login.strategy.ts b/libs/common/src/auth/login-strategies/login.strategy.ts index 7bc83580164..6e51f215012 100644 --- a/libs/common/src/auth/login-strategies/login.strategy.ts +++ b/libs/common/src/auth/login-strategies/login.strategy.ts @@ -143,9 +143,7 @@ export abstract class LogInStrategy { }, }, keys: accountKeys, - decryptionOptions: AccountDecryptionOptions.fromResponse( - tokenResponse.userDecryptionOptions - ), + decryptionOptions: AccountDecryptionOptions.fromResponse(tokenResponse), adminAuthRequest: adminAuthRequest?.toJSON(), }) ); diff --git a/libs/common/src/auth/login-strategies/sso-login.strategy.spec.ts b/libs/common/src/auth/login-strategies/sso-login.strategy.spec.ts index 099a3a02a2b..f078a7b86b1 100644 --- a/libs/common/src/auth/login-strategies/sso-login.strategy.spec.ts +++ b/libs/common/src/auth/login-strategies/sso-login.strategy.spec.ts @@ -266,7 +266,61 @@ describe("SsoLogInStrategy", () => { describe("Key Connector", () => { let tokenResponse: IdentityTokenResponse; beforeEach(() => { - tokenResponse = identityTokenResponseFactory(null, { HasMasterPassword: false }); + tokenResponse = identityTokenResponseFactory(null, { + HasMasterPassword: false, + KeyConnectorOption: { KeyConnectorUrl: keyConnectorUrl }, + }); + tokenResponse.keyConnectorUrl = keyConnectorUrl; + }); + + it("gets and sets the master key if Key Connector is enabled and the user doesn't have a master password", async () => { + const masterKey = new SymmetricCryptoKey( + new Uint8Array(64).buffer as CsprngArray + ) as MasterKey; + + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + cryptoService.getMasterKey.mockResolvedValue(masterKey); + + await ssoLogInStrategy.logIn(credentials); + + expect(keyConnectorService.setMasterKeyFromUrl).toHaveBeenCalledWith(keyConnectorUrl); + }); + + it("converts new SSO user with no master password to Key Connector on first login", async () => { + tokenResponse.key = null; + + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + + await ssoLogInStrategy.logIn(credentials); + + expect(keyConnectorService.convertNewSsoUserToKeyConnector).toHaveBeenCalledWith( + tokenResponse, + ssoOrgId + ); + }); + + it("decrypts and sets the user key if Key Connector is enabled and the user doesn't have a master password", async () => { + const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey; + const masterKey = new SymmetricCryptoKey( + new Uint8Array(64).buffer as CsprngArray + ) as MasterKey; + + apiService.postIdentityToken.mockResolvedValue(tokenResponse); + cryptoService.getMasterKey.mockResolvedValue(masterKey); + cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); + + await ssoLogInStrategy.logIn(credentials); + + expect(cryptoService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(masterKey); + expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey); + }); + }); + + describe("Key Connector Pre-TDE", () => { + let tokenResponse: IdentityTokenResponse; + beforeEach(() => { + tokenResponse = identityTokenResponseFactory(); + tokenResponse.userDecryptionOptions = null; tokenResponse.keyConnectorUrl = keyConnectorUrl; }); diff --git a/libs/common/src/auth/login-strategies/sso-login.strategy.ts b/libs/common/src/auth/login-strategies/sso-login.strategy.ts index 3e9a7e33f33..09dbca72fea 100644 --- a/libs/common/src/auth/login-strategies/sso-login.strategy.ts +++ b/libs/common/src/auth/login-strategies/sso-login.strategy.ts @@ -101,16 +101,22 @@ export class SsoLogInStrategy extends LogInStrategy { private shouldSetMasterKeyFromKeyConnector(tokenResponse: IdentityTokenResponse): boolean { const userDecryptionOptions = tokenResponse?.userDecryptionOptions; - // If the user has a master password, this means that they need to migrate to Key Connector, so we won't set the key here. - // We default to false here because old server versions won't have hasMasterPassword and in that case we want to rely solely on the keyConnectorUrl. - // TODO: remove null default after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537) - const userHasMasterPassword = userDecryptionOptions?.hasMasterPassword ?? false; + if (userDecryptionOptions != null) { + const userHasMasterPassword = userDecryptionOptions.hasMasterPassword; + const userHasKeyConnectorUrl = + userDecryptionOptions.keyConnectorOption?.keyConnectorUrl != null; - const keyConnectorUrl = this.getKeyConnectorUrl(tokenResponse); - - // In order for us to set the master key from Key Connector, we need to have a Key Connector URL - // and the user must not have a master password. - return keyConnectorUrl != null && !userHasMasterPassword; + // In order for us to set the master key from Key Connector, we need to have a Key Connector URL + // and the user must not have a master password. + return userHasKeyConnectorUrl && !userHasMasterPassword; + } else { + // In pre-TDE versions of the server, the userDecryptionOptions will not be present. + // In this case, we can determine if the user has a master password and has a Key Connector URL by + // just checking the keyConnectorUrl property. This is because the server short-circuits on the response + // and will not pass back the URL in the response if the user has a master password. + // TODO: remove compatibility check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537) + return tokenResponse.keyConnectorUrl != null; + } } private getKeyConnectorUrl(tokenResponse: IdentityTokenResponse): string { diff --git a/libs/common/src/auth/services/two-factor.service.ts b/libs/common/src/auth/services/two-factor.service.ts index b391b7d39e1..71687b675ee 100644 --- a/libs/common/src/auth/services/two-factor.service.ts +++ b/libs/common/src/auth/services/two-factor.service.ts @@ -55,7 +55,7 @@ export const TwoFactorProviders: Partial Promise; - migrate: () => Promise; -} diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 4a2b515b74a..82813718de3 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -495,8 +495,6 @@ export abstract class StateService { setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise; getApproveLoginRequests: (options?: StorageOptions) => Promise; setApproveLoginRequests: (value: boolean, options?: StorageOptions) => Promise; - getStateVersion: () => Promise; - setStateVersion: (value: number) => Promise; getWindow: () => Promise; setWindow: (value: WindowState) => Promise; /** diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 95a5a899129..09dc6971dcf 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -11,7 +11,7 @@ import { EnvironmentUrls } from "../../../auth/models/domain/environment-urls"; import { ForceResetPasswordReason } from "../../../auth/models/domain/force-reset-password-reason"; import { KeyConnectorUserDecryptionOption } from "../../../auth/models/domain/user-decryption-options/key-connector-user-decryption-option"; import { TrustedDeviceUserDecryptionOption } from "../../../auth/models/domain/user-decryption-options/trusted-device-user-decryption-option"; -import { UserDecryptionOptionsResponse } from "../../../auth/models/response/user-decryption-options/user-decryption-options.response"; +import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response"; import { KdfType, UriMatchType } from "../../../enums"; import { EventData } from "../../../models/data/event.data"; import { GeneratedPasswordHistory } from "../../../tools/generator/password"; @@ -311,28 +311,46 @@ export class AccountDecryptionOptions { // return this.keyConnectorOption !== null && this.keyConnectorOption !== undefined; // } - static fromResponse(response: UserDecryptionOptionsResponse): AccountDecryptionOptions { + static fromResponse(response: IdentityTokenResponse): AccountDecryptionOptions { if (response == null) { return null; } const accountDecryptionOptions = new AccountDecryptionOptions(); - accountDecryptionOptions.hasMasterPassword = response.hasMasterPassword; - if (response.trustedDeviceOption) { - accountDecryptionOptions.trustedDeviceOption = new TrustedDeviceUserDecryptionOption( - response.trustedDeviceOption.hasAdminApproval, - response.trustedDeviceOption.hasLoginApprovingDevice, - response.trustedDeviceOption.hasManageResetPasswordPermission - ); + if (response.userDecryptionOptions) { + // If the response has userDecryptionOptions, this means it's on a post-TDE server version and can interrogate + // the new decryption options. + const responseOptions = response.userDecryptionOptions; + accountDecryptionOptions.hasMasterPassword = responseOptions.hasMasterPassword; + + if (responseOptions.trustedDeviceOption) { + accountDecryptionOptions.trustedDeviceOption = new TrustedDeviceUserDecryptionOption( + responseOptions.trustedDeviceOption.hasAdminApproval, + responseOptions.trustedDeviceOption.hasLoginApprovingDevice, + responseOptions.trustedDeviceOption.hasManageResetPasswordPermission + ); + } + + if (responseOptions.keyConnectorOption) { + accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption( + responseOptions.keyConnectorOption.keyConnectorUrl + ); + } + } else { + // If the response does not have userDecryptionOptions, this means it's on a pre-TDE server version and so + // we must base our decryption options on the presence of the keyConnectorUrl. + // Note that the presence of keyConnectorUrl implies that the user does not have a master password, as in pre-TDE + // server versions, a master password short-circuited the addition of the keyConnectorUrl to the response. + // TODO: remove this check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537) + const usingKeyConnector = response.keyConnectorUrl != null; + accountDecryptionOptions.hasMasterPassword = !usingKeyConnector; + if (usingKeyConnector) { + accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption( + response.keyConnectorUrl + ); + } } - - if (response.keyConnectorOption) { - accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption( - response.keyConnectorOption.keyConnectorUrl - ); - } - return accountDecryptionOptions; } diff --git a/libs/common/src/platform/models/domain/global-state.ts b/libs/common/src/platform/models/domain/global-state.ts index dfe3c6c417f..30ad32124cf 100644 --- a/libs/common/src/platform/models/domain/global-state.ts +++ b/libs/common/src/platform/models/domain/global-state.ts @@ -1,5 +1,5 @@ import { EnvironmentUrls } from "../../../auth/models/domain/environment-urls"; -import { StateVersion, ThemeType } from "../../../enums"; +import { ThemeType } from "../../../enums"; import { WindowState } from "../../../models/domain/window-state"; export class GlobalState { @@ -25,7 +25,6 @@ export class GlobalState { enableBiometrics?: boolean; biometricText?: string; noAutoPromptBiometricsText?: string; - stateVersion: StateVersion = StateVersion.One; environmentUrls: EnvironmentUrls = new EnvironmentUrls(); enableTray?: boolean; enableMinimizeToTray?: boolean; diff --git a/libs/common/src/platform/services/state-migration.service.spec.ts b/libs/common/src/platform/services/state-migration.service.spec.ts deleted file mode 100644 index 7bbd19106d5..00000000000 --- a/libs/common/src/platform/services/state-migration.service.spec.ts +++ /dev/null @@ -1,216 +0,0 @@ -// eslint-disable-next-line no-restricted-imports -import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; -import { MockProxy, any, mock } from "jest-mock-extended"; - -import { StateVersion } from "../../enums"; -import { AbstractStorageService } from "../abstractions/storage.service"; -import { StateFactory } from "../factories/state-factory"; -import { Account } from "../models/domain/account"; -import { GlobalState } from "../models/domain/global-state"; - -import { StateMigrationService } from "./state-migration.service"; - -const userId = "USER_ID"; - -// Note: each test calls the private migration method for that migration, -// so that we don't accidentally run all following migrations as well - -describe("State Migration Service", () => { - let storageService: MockProxy; - let secureStorageService: SubstituteOf; - let stateFactory: SubstituteOf; - - let stateMigrationService: StateMigrationService; - - beforeEach(() => { - storageService = mock(); - secureStorageService = Substitute.for(); - stateFactory = Substitute.for(); - - stateMigrationService = new StateMigrationService( - storageService, - secureStorageService, - stateFactory - ); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe("StateVersion 3 to 4 migration", () => { - beforeEach(() => { - const globalVersion3: Partial = { - stateVersion: StateVersion.Three, - }; - - storageService.get.calledWith("global", any()).mockResolvedValue(globalVersion3); - storageService.get.calledWith("authenticatedAccounts", any()).mockResolvedValue([userId]); - }); - - it("clears everBeenUnlocked", async () => { - const accountVersion3: Account = { - profile: { - apiKeyClientId: null, - convertAccountToKeyConnector: null, - email: "EMAIL", - emailVerified: true, - everBeenUnlocked: true, - hasPremiumPersonally: false, - kdfIterations: 100000, - kdfType: 0, - keyHash: "KEY_HASH", - lastSync: "LAST_SYNC", - userId: userId, - usesKeyConnector: false, - forcePasswordResetReason: null, - }, - }; - - const expectedAccountVersion4: Account = { - profile: { - ...accountVersion3.profile, - }, - }; - delete expectedAccountVersion4.profile.everBeenUnlocked; - - storageService.get.calledWith(userId, any()).mockResolvedValue(accountVersion3); - - await (stateMigrationService as any).migrateStateFrom3To4(); - - expect(storageService.save).toHaveBeenCalledTimes(2); - expect(storageService.save).toHaveBeenCalledWith(userId, expectedAccountVersion4, any()); - }); - - it("updates StateVersion number", async () => { - await (stateMigrationService as any).migrateStateFrom3To4(); - - expect(storageService.save).toHaveBeenCalledWith( - "global", - { stateVersion: StateVersion.Four }, - any() - ); - expect(storageService.save).toHaveBeenCalledTimes(1); - }); - }); - - describe("StateVersion 4 to 5 migration", () => { - it("migrates organization keys to new format", async () => { - const accountVersion4 = new Account({ - keys: { - organizationKeys: { - encrypted: { - orgOneId: "orgOneEncKey", - orgTwoId: "orgTwoEncKey", - orgThreeId: "orgThreeEncKey", - }, - }, - }, - } as any); - - const expectedAccount = new Account({ - keys: { - organizationKeys: { - encrypted: { - orgOneId: { - type: "organization", - key: "orgOneEncKey", - }, - orgTwoId: { - type: "organization", - key: "orgTwoEncKey", - }, - orgThreeId: { - type: "organization", - key: "orgThreeEncKey", - }, - }, - } as any, - } as any, - }); - - const migratedAccount = await (stateMigrationService as any).migrateAccountFrom4To5( - accountVersion4 - ); - - expect(migratedAccount).toEqual(expectedAccount); - }); - }); - - describe("StateVersion 5 to 6 migration", () => { - it("deletes account.keys.legacyEtmKey value", async () => { - const accountVersion5 = new Account({ - keys: { - legacyEtmKey: "legacy key", - }, - } as any); - - const migratedAccount = await (stateMigrationService as any).migrateAccountFrom5To6( - accountVersion5 - ); - - expect(migratedAccount.keys.legacyEtmKey).toBeUndefined(); - }); - }); - - describe("StateVersion 6 to 7 migration", () => { - it("should delete global.noAutoPromptBiometrics value", async () => { - storageService.get - .calledWith("global", any()) - .mockResolvedValue({ stateVersion: StateVersion.Six, noAutoPromptBiometrics: true }); - storageService.get.calledWith("authenticatedAccounts", any()).mockResolvedValue([]); - - await stateMigrationService.migrate(); - - expect(storageService.save).toHaveBeenCalledWith( - "global", - { - stateVersion: StateVersion.Seven, - }, - any() - ); - }); - - it("should call migrateStateFrom6To7 on each account", async () => { - const accountVersion6 = new Account({ - otherStuff: "other stuff", - } as any); - - storageService.get - .calledWith("global", any()) - .mockResolvedValue({ stateVersion: StateVersion.Six, noAutoPromptBiometrics: true }); - storageService.get.calledWith("authenticatedAccounts", any()).mockResolvedValue([userId]); - storageService.get.calledWith(userId, any()).mockResolvedValue(accountVersion6); - - const migrateSpy = jest.fn(); - (stateMigrationService as any).migrateAccountFrom6To7 = migrateSpy; - - await stateMigrationService.migrate(); - - expect(migrateSpy).toHaveBeenCalledWith(true, accountVersion6); - }); - - it("should update account.settings.disableAutoBiometricsPrompt value if global is no prompt", async () => { - const result = await (stateMigrationService as any).migrateAccountFrom6To7(true, { - otherStuff: "other stuff", - }); - - expect(result).toEqual({ - otherStuff: "other stuff", - settings: { - disableAutoBiometricsPrompt: true, - }, - }); - }); - - it("should not update account.settings.disableAutoBiometricsPrompt value if global auto prompt is enabled", async () => { - const result = await (stateMigrationService as any).migrateAccountFrom6To7(false, { - otherStuff: "other stuff", - }); - - expect(result).toEqual({ - otherStuff: "other stuff", - }); - }); - }); -}); diff --git a/libs/common/src/platform/services/state-migration.service.ts b/libs/common/src/platform/services/state-migration.service.ts deleted file mode 100644 index 234d1b2bff8..00000000000 --- a/libs/common/src/platform/services/state-migration.service.ts +++ /dev/null @@ -1,587 +0,0 @@ -import { OrganizationData } from "../../admin-console/models/data/organization.data"; -import { PolicyData } from "../../admin-console/models/data/policy.data"; -import { ProviderData } from "../../admin-console/models/data/provider.data"; -import { EnvironmentUrls } from "../../auth/models/domain/environment-urls"; -import { TokenService } from "../../auth/services/token.service"; -import { StateVersion, ThemeType, KdfType, HtmlStorageLocation } from "../../enums"; -import { EventData } from "../../models/data/event.data"; -import { GeneratedPasswordHistory } from "../../tools/generator/password"; -import { SendData } from "../../tools/send/models/data/send.data"; -import { CipherData } from "../../vault/models/data/cipher.data"; -import { CollectionData } from "../../vault/models/data/collection.data"; -import { FolderData } from "../../vault/models/data/folder.data"; -import { AbstractStorageService } from "../abstractions/storage.service"; -import { StateFactory } from "../factories/state-factory"; -import { - Account, - AccountSettings, - EncryptionPair, - AccountSettingsSettings, -} from "../models/domain/account"; -import { EncString } from "../models/domain/enc-string"; -import { GlobalState } from "../models/domain/global-state"; -import { StorageOptions } from "../models/domain/storage-options"; - -// Originally (before January 2022) storage was handled as a flat key/value pair store. -// With the move to a typed object for state storage these keys should no longer be in use anywhere outside of this migration. -const v1Keys: { [key: string]: string } = { - accessToken: "accessToken", - alwaysShowDock: "alwaysShowDock", - autoConfirmFingerprints: "autoConfirmFingerprints", - autoFillOnPageLoadDefault: "autoFillOnPageLoadDefault", - biometricAwaitingAcceptance: "biometricAwaitingAcceptance", - biometricFingerprintValidated: "biometricFingerprintValidated", - biometricText: "biometricText", - biometricUnlock: "biometric", - clearClipboard: "clearClipboardKey", - clientId: "apikey_clientId", - clientSecret: "apikey_clientSecret", - collapsedGroupings: "collapsedGroupings", - convertAccountToKeyConnector: "convertAccountToKeyConnector", - defaultUriMatch: "defaultUriMatch", - disableAddLoginNotification: "disableAddLoginNotification", - disableAutoBiometricsPrompt: "noAutoPromptBiometrics", - disableAutoTotpCopy: "disableAutoTotpCopy", - disableBadgeCounter: "disableBadgeCounter", - disableChangedPasswordNotification: "disableChangedPasswordNotification", - disableContextMenuItem: "disableContextMenuItem", - disableFavicon: "disableFavicon", - disableGa: "disableGa", - dontShowCardsCurrentTab: "dontShowCardsCurrentTab", - dontShowIdentitiesCurrentTab: "dontShowIdentitiesCurrentTab", - emailVerified: "emailVerified", - enableAlwaysOnTop: "enableAlwaysOnTopKey", - enableAutoFillOnPageLoad: "enableAutoFillOnPageLoad", - enableBiometric: "enabledBiometric", - enableBrowserIntegration: "enableBrowserIntegration", - enableBrowserIntegrationFingerprint: "enableBrowserIntegrationFingerprint", - enableCloseToTray: "enableCloseToTray", - enableFullWidth: "enableFullWidth", - enableMinimizeToTray: "enableMinimizeToTray", - enableStartToTray: "enableStartToTrayKey", - enableTray: "enableTray", - encKey: "encKey", // Generated Symmetric Key - encOrgKeys: "encOrgKeys", - encPrivate: "encPrivateKey", - encProviderKeys: "encProviderKeys", - entityId: "entityId", - entityType: "entityType", - environmentUrls: "environmentUrls", - equivalentDomains: "equivalentDomains", - eventCollection: "eventCollection", - forcePasswordReset: "forcePasswordReset", - history: "generatedPasswordHistory", - installedVersion: "installedVersion", - kdf: "kdf", - kdfIterations: "kdfIterations", - key: "key", // Master Key - keyHash: "keyHash", - lastActive: "lastActive", - localData: "sitesLocalData", - locale: "locale", - mainWindowSize: "mainWindowSize", - minimizeOnCopyToClipboard: "minimizeOnCopyToClipboardKey", - neverDomains: "neverDomains", - noAutoPromptBiometricsText: "noAutoPromptBiometricsText", - openAtLogin: "openAtLogin", - passwordGenerationOptions: "passwordGenerationOptions", - pinProtected: "pinProtectedKey", - protectedPin: "protectedPin", - refreshToken: "refreshToken", - ssoCodeVerifier: "ssoCodeVerifier", - ssoIdentifier: "ssoOrgIdentifier", - ssoState: "ssoState", - stamp: "securityStamp", - theme: "theme", - userEmail: "userEmail", - userId: "userId", - usesConnector: "usesKeyConnector", - vaultTimeoutAction: "vaultTimeoutAction", - vaultTimeout: "lockOption", - rememberedEmail: "rememberedEmail", -}; - -const v1KeyPrefixes: { [key: string]: string } = { - ciphers: "ciphers_", - collections: "collections_", - folders: "folders_", - lastSync: "lastSync_", - policies: "policies_", - twoFactorToken: "twoFactorToken_", - organizations: "organizations_", - providers: "providers_", - sends: "sends_", - settings: "settings_", -}; - -const keys = { - global: "global", - authenticatedAccounts: "authenticatedAccounts", - activeUserId: "activeUserId", - tempAccountSettings: "tempAccountSettings", // used to hold account specific settings (i.e clear clipboard) between initial migration and first account authentication - accountActivity: "accountActivity", -}; - -const partialKeys = { - autoKey: "_masterkey_auto", - biometricKey: "_masterkey_biometric", - masterKey: "_masterkey", -}; - -export class StateMigrationService< - TGlobalState extends GlobalState = GlobalState, - TAccount extends Account = Account -> { - constructor( - protected storageService: AbstractStorageService, - protected secureStorageService: AbstractStorageService, - protected stateFactory: StateFactory - ) {} - - async needsMigration(): Promise { - const currentStateVersion = await this.getCurrentStateVersion(); - return currentStateVersion == null || currentStateVersion < StateVersion.Latest; - } - - async migrate(): Promise { - let currentStateVersion = await this.getCurrentStateVersion(); - while (currentStateVersion < StateVersion.Latest) { - switch (currentStateVersion) { - case StateVersion.One: - await this.migrateStateFrom1To2(); - break; - case StateVersion.Two: - await this.migrateStateFrom2To3(); - break; - case StateVersion.Three: - await this.migrateStateFrom3To4(); - break; - case StateVersion.Four: { - const authenticatedAccounts = await this.getAuthenticatedAccounts(); - for (const account of authenticatedAccounts) { - const migratedAccount = await this.migrateAccountFrom4To5(account); - await this.set(account.profile.userId, migratedAccount); - } - await this.setCurrentStateVersion(StateVersion.Five); - break; - } - case StateVersion.Five: { - const authenticatedAccounts = await this.getAuthenticatedAccounts(); - for (const account of authenticatedAccounts) { - const migratedAccount = await this.migrateAccountFrom5To6(account); - await this.set(account.profile.userId, migratedAccount); - } - await this.setCurrentStateVersion(StateVersion.Six); - break; - } - case StateVersion.Six: { - const authenticatedAccounts = await this.getAuthenticatedAccounts(); - const globals = (await this.getGlobals()) as any; - for (const account of authenticatedAccounts) { - const migratedAccount = await this.migrateAccountFrom6To7( - globals?.noAutoPromptBiometrics, - account - ); - await this.set(account.profile.userId, migratedAccount); - } - if (globals) { - delete globals.noAutoPromptBiometrics; - } - await this.set(keys.global, globals); - await this.setCurrentStateVersion(StateVersion.Seven); - } - } - - currentStateVersion += 1; - } - } - - protected async migrateStateFrom1To2(): Promise { - const clearV1Keys = async (clearingUserId?: string) => { - for (const key in v1Keys) { - if (key == null) { - continue; - } - await this.set(v1Keys[key], null); - } - if (clearingUserId != null) { - for (const keyPrefix in v1KeyPrefixes) { - if (keyPrefix == null) { - continue; - } - await this.set(v1KeyPrefixes[keyPrefix] + userId, null); - } - } - }; - - // Some processes, like biometrics, may have already defined a value before migrations are run. - // We don't want to null out those values if they don't exist in the old storage scheme (like for new installs) - // So, the OOO for migration is that we: - // 1. Check for an existing storage value from the old storage structure OR - // 2. Check for a value already set by processes that run before migration OR - // 3. Assign the default value - const globals: any = - (await this.get(keys.global)) ?? this.stateFactory.createGlobal(null); - globals.stateVersion = StateVersion.Two; - globals.environmentUrls = - (await this.get(v1Keys.environmentUrls)) ?? globals.environmentUrls; - globals.locale = (await this.get(v1Keys.locale)) ?? globals.locale; - globals.noAutoPromptBiometrics = - (await this.get(v1Keys.disableAutoBiometricsPrompt)) ?? - globals.noAutoPromptBiometrics; - globals.noAutoPromptBiometricsText = - (await this.get(v1Keys.noAutoPromptBiometricsText)) ?? - globals.noAutoPromptBiometricsText; - globals.ssoCodeVerifier = - (await this.get(v1Keys.ssoCodeVerifier)) ?? globals.ssoCodeVerifier; - globals.ssoOrganizationIdentifier = - (await this.get(v1Keys.ssoIdentifier)) ?? globals.ssoOrganizationIdentifier; - globals.ssoState = (await this.get(v1Keys.ssoState)) ?? globals.ssoState; - globals.rememberedEmail = - (await this.get(v1Keys.rememberedEmail)) ?? globals.rememberedEmail; - globals.theme = (await this.get(v1Keys.theme)) ?? globals.theme; - globals.vaultTimeout = (await this.get(v1Keys.vaultTimeout)) ?? globals.vaultTimeout; - globals.vaultTimeoutAction = - (await this.get(v1Keys.vaultTimeoutAction)) ?? globals.vaultTimeoutAction; - globals.window = (await this.get(v1Keys.mainWindowSize)) ?? globals.window; - globals.enableTray = (await this.get(v1Keys.enableTray)) ?? globals.enableTray; - globals.enableMinimizeToTray = - (await this.get(v1Keys.enableMinimizeToTray)) ?? globals.enableMinimizeToTray; - globals.enableCloseToTray = - (await this.get(v1Keys.enableCloseToTray)) ?? globals.enableCloseToTray; - globals.enableStartToTray = - (await this.get(v1Keys.enableStartToTray)) ?? globals.enableStartToTray; - globals.openAtLogin = (await this.get(v1Keys.openAtLogin)) ?? globals.openAtLogin; - globals.alwaysShowDock = - (await this.get(v1Keys.alwaysShowDock)) ?? globals.alwaysShowDock; - globals.enableBrowserIntegration = - (await this.get(v1Keys.enableBrowserIntegration)) ?? - globals.enableBrowserIntegration; - globals.enableBrowserIntegrationFingerprint = - (await this.get(v1Keys.enableBrowserIntegrationFingerprint)) ?? - globals.enableBrowserIntegrationFingerprint; - - const userId = - (await this.get(v1Keys.userId)) ?? (await this.get(v1Keys.entityId)); - - const defaultAccount = this.stateFactory.createAccount(null); - const accountSettings: AccountSettings = { - autoConfirmFingerPrints: - (await this.get(v1Keys.autoConfirmFingerprints)) ?? - defaultAccount.settings.autoConfirmFingerPrints, - autoFillOnPageLoadDefault: - (await this.get(v1Keys.autoFillOnPageLoadDefault)) ?? - defaultAccount.settings.autoFillOnPageLoadDefault, - biometricUnlock: - (await this.get(v1Keys.biometricUnlock)) ?? - defaultAccount.settings.biometricUnlock, - clearClipboard: - (await this.get(v1Keys.clearClipboard)) ?? defaultAccount.settings.clearClipboard, - defaultUriMatch: - (await this.get(v1Keys.defaultUriMatch)) ?? defaultAccount.settings.defaultUriMatch, - disableAddLoginNotification: - (await this.get(v1Keys.disableAddLoginNotification)) ?? - defaultAccount.settings.disableAddLoginNotification, - disableAutoBiometricsPrompt: - (await this.get(v1Keys.disableAutoBiometricsPrompt)) ?? - defaultAccount.settings.disableAutoBiometricsPrompt, - disableAutoTotpCopy: - (await this.get(v1Keys.disableAutoTotpCopy)) ?? - defaultAccount.settings.disableAutoTotpCopy, - disableBadgeCounter: - (await this.get(v1Keys.disableBadgeCounter)) ?? - defaultAccount.settings.disableBadgeCounter, - disableChangedPasswordNotification: - (await this.get(v1Keys.disableChangedPasswordNotification)) ?? - defaultAccount.settings.disableChangedPasswordNotification, - disableContextMenuItem: - (await this.get(v1Keys.disableContextMenuItem)) ?? - defaultAccount.settings.disableContextMenuItem, - disableGa: (await this.get(v1Keys.disableGa)) ?? defaultAccount.settings.disableGa, - dontShowCardsCurrentTab: - (await this.get(v1Keys.dontShowCardsCurrentTab)) ?? - defaultAccount.settings.dontShowCardsCurrentTab, - dontShowIdentitiesCurrentTab: - (await this.get(v1Keys.dontShowIdentitiesCurrentTab)) ?? - defaultAccount.settings.dontShowIdentitiesCurrentTab, - enableAlwaysOnTop: - (await this.get(v1Keys.enableAlwaysOnTop)) ?? - defaultAccount.settings.enableAlwaysOnTop, - enableAutoFillOnPageLoad: - (await this.get(v1Keys.enableAutoFillOnPageLoad)) ?? - defaultAccount.settings.enableAutoFillOnPageLoad, - enableBiometric: - (await this.get(v1Keys.enableBiometric)) ?? - defaultAccount.settings.enableBiometric, - enableFullWidth: - (await this.get(v1Keys.enableFullWidth)) ?? - defaultAccount.settings.enableFullWidth, - environmentUrls: globals.environmentUrls ?? defaultAccount.settings.environmentUrls, - equivalentDomains: - (await this.get(v1Keys.equivalentDomains)) ?? - defaultAccount.settings.equivalentDomains, - minimizeOnCopyToClipboard: - (await this.get(v1Keys.minimizeOnCopyToClipboard)) ?? - defaultAccount.settings.minimizeOnCopyToClipboard, - neverDomains: - (await this.get(v1Keys.neverDomains)) ?? defaultAccount.settings.neverDomains, - passwordGenerationOptions: - (await this.get(v1Keys.passwordGenerationOptions)) ?? - defaultAccount.settings.passwordGenerationOptions, - pinProtected: Object.assign(new EncryptionPair(), { - decrypted: null, - encrypted: await this.get(v1Keys.pinProtected), - }), - protectedPin: await this.get(v1Keys.protectedPin), - settings: - userId == null - ? null - : await this.get(v1KeyPrefixes.settings + userId), - vaultTimeout: - (await this.get(v1Keys.vaultTimeout)) ?? defaultAccount.settings.vaultTimeout, - vaultTimeoutAction: - (await this.get(v1Keys.vaultTimeoutAction)) ?? - defaultAccount.settings.vaultTimeoutAction, - }; - - // (userId == null) = no logged in user (so no known userId) and we need to temporarily store account specific settings in state to migrate on first auth - // (userId != null) = we have a currently authed user (so known userId) with encrypted data and other key settings we can move, no need to temporarily store account settings - if (userId == null) { - await this.set(keys.tempAccountSettings, accountSettings); - await this.set(keys.global, globals); - await this.set(keys.authenticatedAccounts, []); - await this.set(keys.activeUserId, null); - await clearV1Keys(); - return; - } - - globals.twoFactorToken = await this.get(v1KeyPrefixes.twoFactorToken + userId); - await this.set(keys.global, globals); - await this.set(userId, { - data: { - addEditCipherInfo: null, - ciphers: { - decrypted: null, - encrypted: await this.get<{ [id: string]: CipherData }>(v1KeyPrefixes.ciphers + userId), - }, - collapsedGroupings: null, - collections: { - decrypted: null, - encrypted: await this.get<{ [id: string]: CollectionData }>( - v1KeyPrefixes.collections + userId - ), - }, - eventCollection: await this.get(v1Keys.eventCollection), - folders: { - decrypted: null, - encrypted: await this.get<{ [id: string]: FolderData }>(v1KeyPrefixes.folders + userId), - }, - localData: null, - organizations: await this.get<{ [id: string]: OrganizationData }>( - v1KeyPrefixes.organizations + userId - ), - passwordGenerationHistory: { - decrypted: null, - encrypted: await this.get(v1Keys.history), - }, - policies: { - decrypted: null, - encrypted: await this.get<{ [id: string]: PolicyData }>(v1KeyPrefixes.policies + userId), - }, - providers: await this.get<{ [id: string]: ProviderData }>(v1KeyPrefixes.providers + userId), - sends: { - decrypted: null, - encrypted: await this.get<{ [id: string]: SendData }>(v1KeyPrefixes.sends + userId), - }, - }, - keys: { - apiKeyClientSecret: await this.get(v1Keys.clientSecret), - cryptoMasterKey: null, - cryptoMasterKeyAuto: null, - cryptoMasterKeyB64: null, - cryptoMasterKeyBiometric: null, - cryptoSymmetricKey: { - encrypted: await this.get(v1Keys.encKey), - decrypted: null, - }, - legacyEtmKey: null, - organizationKeys: { - decrypted: null, - encrypted: await this.get(v1Keys.encOrgKeys), - }, - privateKey: { - decrypted: null, - encrypted: await this.get(v1Keys.encPrivate), - }, - providerKeys: { - decrypted: null, - encrypted: await this.get(v1Keys.encProviderKeys), - }, - publicKey: null, - }, - profile: { - apiKeyClientId: await this.get(v1Keys.clientId), - authenticationStatus: null, - convertAccountToKeyConnector: await this.get(v1Keys.convertAccountToKeyConnector), - email: await this.get(v1Keys.userEmail), - emailVerified: await this.get(v1Keys.emailVerified), - entityId: null, - entityType: null, - everBeenUnlocked: null, - forcePasswordReset: null, - hasPremiumPersonally: null, - kdfIterations: await this.get(v1Keys.kdfIterations), - kdfType: await this.get(v1Keys.kdf), - keyHash: await this.get(v1Keys.keyHash), - lastSync: null, - userId: userId, - usesKeyConnector: null, - }, - settings: accountSettings, - tokens: { - accessToken: await this.get(v1Keys.accessToken), - decodedToken: null, - refreshToken: await this.get(v1Keys.refreshToken), - securityStamp: null, - }, - }); - - await this.set(keys.authenticatedAccounts, [userId]); - await this.set(keys.activeUserId, userId); - - const accountActivity: { [userId: string]: number } = { - [userId]: await this.get(v1Keys.lastActive), - }; - accountActivity[userId] = await this.get(v1Keys.lastActive); - await this.set(keys.accountActivity, accountActivity); - - await clearV1Keys(userId); - - if (await this.secureStorageService.has(v1Keys.key, { keySuffix: "biometric" })) { - await this.secureStorageService.save( - `${userId}${partialKeys.biometricKey}`, - await this.secureStorageService.get(v1Keys.key, { keySuffix: "biometric" }), - { keySuffix: "biometric" } - ); - await this.secureStorageService.remove(v1Keys.key, { keySuffix: "biometric" }); - } - - if (await this.secureStorageService.has(v1Keys.key, { keySuffix: "auto" })) { - await this.secureStorageService.save( - `${userId}${partialKeys.autoKey}`, - await this.secureStorageService.get(v1Keys.key, { keySuffix: "auto" }), - { keySuffix: "auto" } - ); - await this.secureStorageService.remove(v1Keys.key, { keySuffix: "auto" }); - } - - if (await this.secureStorageService.has(v1Keys.key)) { - await this.secureStorageService.save( - `${userId}${partialKeys.masterKey}`, - await this.secureStorageService.get(v1Keys.key) - ); - await this.secureStorageService.remove(v1Keys.key); - } - } - - protected async migrateStateFrom2To3(): Promise { - const authenticatedUserIds = await this.get(keys.authenticatedAccounts); - await Promise.all( - authenticatedUserIds.map(async (userId) => { - const account = await this.get(userId); - if ( - account?.profile?.hasPremiumPersonally === null && - account.tokens?.accessToken != null - ) { - const decodedToken = await TokenService.decodeToken(account.tokens.accessToken); - account.profile.hasPremiumPersonally = decodedToken.premium; - await this.set(userId, account); - } - }) - ); - - const globals = await this.getGlobals(); - globals.stateVersion = StateVersion.Three; - await this.set(keys.global, globals); - } - - protected async migrateStateFrom3To4(): Promise { - const authenticatedUserIds = await this.get(keys.authenticatedAccounts); - await Promise.all( - authenticatedUserIds.map(async (userId) => { - const account = await this.get(userId); - if (account?.profile?.everBeenUnlocked != null) { - delete account.profile.everBeenUnlocked; - return this.set(userId, account); - } - }) - ); - - const globals = await this.getGlobals(); - globals.stateVersion = StateVersion.Four; - await this.set(keys.global, globals); - } - - protected async migrateAccountFrom4To5(account: TAccount): Promise { - const encryptedOrgKeys = account.keys?.organizationKeys?.encrypted; - if (encryptedOrgKeys != null) { - for (const [orgId, encKey] of Object.entries(encryptedOrgKeys)) { - encryptedOrgKeys[orgId] = { - type: "organization", - key: encKey as unknown as string, // Account v4 does not reflect the current account model so we have to cast - }; - } - } - - return account; - } - - protected async migrateAccountFrom5To6(account: TAccount): Promise { - delete (account as any).keys?.legacyEtmKey; - return account; - } - - protected async migrateAccountFrom6To7( - globalSetting: boolean, - account: TAccount - ): Promise { - if (globalSetting) { - account.settings = Object.assign({}, account.settings, { disableAutoBiometricsPrompt: true }); - } - return account; - } - - protected get options(): StorageOptions { - return { htmlStorageLocation: HtmlStorageLocation.Local }; - } - - protected get(key: string): Promise { - return this.storageService.get(key, this.options); - } - - protected set(key: string, value: any): Promise { - if (value == null) { - return this.storageService.remove(key, this.options); - } - return this.storageService.save(key, value, this.options); - } - - protected async getGlobals(): Promise { - return await this.get(keys.global); - } - - protected async getCurrentStateVersion(): Promise { - return (await this.getGlobals())?.stateVersion ?? StateVersion.One; - } - - protected async setCurrentStateVersion(newVersion: StateVersion): Promise { - const globals = await this.getGlobals(); - globals.stateVersion = newVersion; - await this.set(keys.global, globals); - } - - protected async getAuthenticatedAccounts(): Promise { - const authenticatedUserIds = await this.get(keys.authenticatedAccounts); - return Promise.all(authenticatedUserIds.map((id) => this.get(id))); - } -} diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 14ba4abfd39..5fdf40e8458 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -21,6 +21,7 @@ import { import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { EventData } from "../../models/data/event.data"; import { WindowState } from "../../models/domain/window-state"; +import { migrate } from "../../state-migrations"; import { GeneratedPasswordHistory } from "../../tools/generator/password"; import { SendData } from "../../tools/send/models/data/send.data"; import { SendView } from "../../tools/send/models/view/send.view"; @@ -32,7 +33,6 @@ import { CipherView } from "../../vault/models/view/cipher.view"; import { CollectionView } from "../../vault/models/view/collection.view"; import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info"; import { LogService } from "../abstractions/log.service"; -import { StateMigrationService } from "../abstractions/state-migration.service"; import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; import { AbstractMemoryStorageService, @@ -61,6 +61,7 @@ import { const keys = { state: "state", + stateVersion: "stateVersion", global: "global", authenticatedAccounts: "authenticatedAccounts", activeUserId: "activeUserId", @@ -106,7 +107,6 @@ export class StateService< protected secureStorageService: AbstractStorageService, protected memoryStorageService: AbstractMemoryStorageService, protected logService: LogService, - protected stateMigrationService: StateMigrationService, protected stateFactory: StateFactory, protected useAccountCache: boolean = true ) { @@ -133,9 +133,7 @@ export class StateService< return; } - if (await this.stateMigrationService.needsMigration()) { - await this.stateMigrationService.migrate(); - } + await migrate(this.storageService, this.logService); await this.state().then(async (state) => { if (state == null) { @@ -2724,16 +2722,6 @@ export class StateService< ); } - async getStateVersion(): Promise { - return (await this.getGlobals(await this.defaultOnDiskLocalOptions())).stateVersion ?? 1; - } - - async setStateVersion(value: number): Promise { - const globals = await this.getGlobals(await this.defaultOnDiskOptions()); - globals.stateVersion = value; - await this.saveGlobals(globals, await this.defaultOnDiskOptions()); - } - async getWindow(): Promise { const globals = await this.getGlobals(await this.defaultOnDiskOptions()); return globals?.window != null && Object.keys(globals.window).length > 0 @@ -2838,7 +2826,11 @@ export class StateService< globals = await this.getGlobalsFromDisk(options); } - return globals ?? this.createGlobals(); + if (globals == null) { + globals = this.createGlobals(); + } + + return globals; } protected async saveGlobals(globals: TGlobalState, options: StorageOptions) { diff --git a/libs/common/src/state-migrations/.eslintrc.json b/libs/common/src/state-migrations/.eslintrc.json new file mode 100644 index 00000000000..4b66f0a32fa --- /dev/null +++ b/libs/common/src/state-migrations/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "overrides": [ + { + "files": ["*"], + "rules": { + "import/no-restricted-paths": [ + "error", + { + "basePath": "libs/common/src/state-migrations", + "zones": [ + { + "target": "./", + "from": "../", + // Relative to from, not basePath + "except": ["state-migrations"], + "message": "State migrations should rarely import from the greater codebase. If you need to import from another location, take into account the likelihood of change in that code and consider copying to the migration instead." + } + ] + } + ] + } + } + ] +} diff --git a/libs/common/src/state-migrations/index.ts b/libs/common/src/state-migrations/index.ts new file mode 100644 index 00000000000..c883b1ca811 --- /dev/null +++ b/libs/common/src/state-migrations/index.ts @@ -0,0 +1 @@ +export { migrate, CURRENT_VERSION } from "./migrate"; diff --git a/libs/common/src/state-migrations/migrate.spec.ts b/libs/common/src/state-migrations/migrate.spec.ts new file mode 100644 index 00000000000..ade3d261f69 --- /dev/null +++ b/libs/common/src/state-migrations/migrate.spec.ts @@ -0,0 +1,67 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages +import { LogService } from "../platform/abstractions/log.service"; +// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations +import { AbstractStorageService } from "../platform/abstractions/storage.service"; + +import { CURRENT_VERSION, currentVersion, migrate } from "./migrate"; +import { MigrationBuilder } from "./migration-builder"; + +jest.mock("./migration-builder", () => { + return { + MigrationBuilder: { + create: jest.fn().mockReturnThis(), + }, + }; +}); + +describe("migrate", () => { + it("should not run migrations if state is empty", async () => { + const storage = mock(); + const logService = mock(); + storage.get.mockReturnValueOnce(null); + await migrate(storage, logService); + expect(MigrationBuilder.create).not.toHaveBeenCalled(); + }); + + it("should set to current version if state is empty", async () => { + const storage = mock(); + const logService = mock(); + storage.get.mockReturnValueOnce(null); + await migrate(storage, logService); + expect(storage.save).toHaveBeenCalledWith("stateVersion", CURRENT_VERSION); + }); +}); + +describe("currentVersion", () => { + let storage: MockProxy; + let logService: MockProxy; + + beforeEach(() => { + storage = mock(); + logService = mock(); + }); + + it("should return -1 if no version", async () => { + storage.get.mockReturnValueOnce(null); + expect(await currentVersion(storage, logService)).toEqual(-1); + }); + + it("should return version", async () => { + storage.get.calledWith("stateVersion").mockReturnValueOnce(1 as any); + expect(await currentVersion(storage, logService)).toEqual(1); + }); + + it("should return version from global", async () => { + storage.get.calledWith("stateVersion").mockReturnValueOnce(null); + storage.get.calledWith("global").mockReturnValueOnce({ stateVersion: 1 } as any); + expect(await currentVersion(storage, logService)).toEqual(1); + }); + + it("should prefer root version to global", async () => { + storage.get.calledWith("stateVersion").mockReturnValue(1 as any); + storage.get.calledWith("global").mockReturnValue({ stateVersion: 2 } as any); + expect(await currentVersion(storage, logService)).toEqual(1); + }); +}); diff --git a/libs/common/src/state-migrations/migrate.ts b/libs/common/src/state-migrations/migrate.ts new file mode 100644 index 00000000000..483c4f2e8eb --- /dev/null +++ b/libs/common/src/state-migrations/migrate.ts @@ -0,0 +1,60 @@ +// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages +import { LogService } from "../platform/abstractions/log.service"; +// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations +import { AbstractStorageService } from "../platform/abstractions/storage.service"; + +import { MigrationBuilder } from "./migration-builder"; +import { MigrationHelper } from "./migration-helper"; +import { FixPremiumMigrator } from "./migrations/3-fix-premium"; +import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked"; +import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys"; +import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key"; +import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account"; +import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; +import { MinVersionMigrator } from "./migrations/min-version"; + +export const MIN_VERSION = 2; +export const CURRENT_VERSION = 8; +export type MinVersion = typeof MIN_VERSION; + +export async function migrate( + storageService: AbstractStorageService, + logService: LogService +): Promise { + const migrationHelper = new MigrationHelper( + await currentVersion(storageService, logService), + storageService, + logService + ); + if (migrationHelper.currentVersion < 0) { + // Cannot determine state, assuming empty so we don't repeatedly apply a migration. + await storageService.save("stateVersion", CURRENT_VERSION); + return; + } + MigrationBuilder.create() + .with(MinVersionMigrator) + .with(FixPremiumMigrator, 2, 3) + .with(RemoveEverBeenUnlockedMigrator, 3, 4) + .with(AddKeyTypeToOrgKeysMigrator, 4, 5) + .with(RemoveLegacyEtmKeyMigrator, 5, 6) + .with(MoveBiometricAutoPromptToAccount, 6, 7) + .with(MoveStateVersionMigrator, 7, CURRENT_VERSION) + .migrate(migrationHelper); +} + +export async function currentVersion( + storageService: AbstractStorageService, + logService: LogService +) { + let state = await storageService.get("stateVersion"); + if (state == null) { + // Pre v8 + state = (await storageService.get<{ stateVersion: number }>("global"))?.stateVersion; + } + if (state == null) { + logService.info("No state version found, assuming empty state."); + return -1; + } + logService.info(`State version: ${state}`); + return state; +} diff --git a/libs/common/src/state-migrations/migration-builder.spec.ts b/libs/common/src/state-migrations/migration-builder.spec.ts new file mode 100644 index 00000000000..fa53544f133 --- /dev/null +++ b/libs/common/src/state-migrations/migration-builder.spec.ts @@ -0,0 +1,117 @@ +import { mock } from "jest-mock-extended"; + +import { MigrationBuilder } from "./migration-builder"; +import { MigrationHelper } from "./migration-helper"; +import { Migrator } from "./migrator"; + +describe("MigrationBuilder", () => { + class TestMigrator extends Migrator<0, 1> { + async migrate(helper: MigrationHelper): Promise { + await helper.set("test", "test"); + return; + } + + async rollback(helper: MigrationHelper): Promise { + await helper.set("test", "rollback"); + return; + } + } + + let sut: MigrationBuilder; + + beforeEach(() => { + sut = MigrationBuilder.create(); + }); + + class TestBadMigrator extends Migrator<1, 0> { + async migrate(helper: MigrationHelper): Promise { + await helper.set("test", "test"); + } + + async rollback(helper: MigrationHelper): Promise { + await helper.set("test", "rollback"); + } + } + + it("should throw if instantiated incorrectly", () => { + expect(() => MigrationBuilder.create().with(TestMigrator, null, null)).toThrow(); + expect(() => + MigrationBuilder.create().with(TestMigrator, 0, 1).with(TestBadMigrator, 1, 0) + ).toThrow(); + }); + + it("should be able to create a new MigrationBuilder", () => { + expect(sut).toBeInstanceOf(MigrationBuilder); + }); + + it("should be able to add a migrator", () => { + const newBuilder = sut.with(TestMigrator, 0, 1); + const migrations = newBuilder["migrations"]; + expect(migrations.length).toBe(1); + expect(migrations[0]).toMatchObject({ migrator: expect.any(TestMigrator), direction: "up" }); + }); + + it("should be able to add a rollback", () => { + const newBuilder = sut.with(TestMigrator, 0, 1).rollback(TestMigrator, 1, 0); + const migrations = newBuilder["migrations"]; + expect(migrations.length).toBe(2); + expect(migrations[1]).toMatchObject({ migrator: expect.any(TestMigrator), direction: "down" }); + }); + + describe("migrate", () => { + let migrator: TestMigrator; + let rollback_migrator: TestMigrator; + + beforeEach(() => { + sut = sut.with(TestMigrator, 0, 1).rollback(TestMigrator, 1, 0); + migrator = (sut as any).migrations[0].migrator; + rollback_migrator = (sut as any).migrations[1].migrator; + }); + + it("should migrate", async () => { + const helper = new MigrationHelper(0, mock(), mock()); + const spy = jest.spyOn(migrator, "migrate"); + await sut.migrate(helper); + expect(spy).toBeCalledWith(helper); + }); + + it("should rollback", async () => { + const helper = new MigrationHelper(1, mock(), mock()); + const spy = jest.spyOn(rollback_migrator, "rollback"); + await sut.migrate(helper); + expect(spy).toBeCalledWith(helper); + }); + + it("should update version on migrate", async () => { + const helper = new MigrationHelper(0, mock(), mock()); + const spy = jest.spyOn(migrator, "updateVersion"); + await sut.migrate(helper); + expect(spy).toBeCalledWith(helper, "up"); + }); + + it("should update version on rollback", async () => { + const helper = new MigrationHelper(1, mock(), mock()); + const spy = jest.spyOn(rollback_migrator, "updateVersion"); + await sut.migrate(helper); + expect(spy).toBeCalledWith(helper, "down"); + }); + + it("should not run the migrator if the current version does not match the from version", async () => { + const helper = new MigrationHelper(3, mock(), mock()); + const migrate = jest.spyOn(migrator, "migrate"); + const rollback = jest.spyOn(rollback_migrator, "rollback"); + await sut.migrate(helper); + expect(migrate).not.toBeCalled(); + expect(rollback).not.toBeCalled(); + }); + + it("should not update version if the current version does not match the from version", async () => { + const helper = new MigrationHelper(3, mock(), mock()); + const migrate = jest.spyOn(migrator, "updateVersion"); + const rollback = jest.spyOn(rollback_migrator, "updateVersion"); + await sut.migrate(helper); + expect(migrate).not.toBeCalled(); + expect(rollback).not.toBeCalled(); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migration-builder.ts b/libs/common/src/state-migrations/migration-builder.ts new file mode 100644 index 00000000000..776295a6b8f --- /dev/null +++ b/libs/common/src/state-migrations/migration-builder.ts @@ -0,0 +1,106 @@ +import { MigrationHelper } from "./migration-helper"; +import { Direction, Migrator, VersionFrom, VersionTo } from "./migrator"; + +export class MigrationBuilder { + /** Create a new MigrationBuilder with an empty buffer of migrations to perform. + * + * Add migrations to the buffer with {@link with} and {@link rollback}. + * @returns A new MigrationBuilder. + */ + static create(): MigrationBuilder<0> { + return new MigrationBuilder([]); + } + + private constructor( + private migrations: readonly { migrator: Migrator; direction: Direction }[] + ) {} + + /** Add a migrator to the MigrationBuilder. Types are updated such that the chained MigrationBuilder must currently be + * at state version equal to the from version of the migrator. Return as MigrationBuilder where TTo is the to + * version of the migrator, so that the next migrator can be chained. + * + * @param migrate A migrator class or a tuple of a migrator class, the from version, and the to version. A tuple is + * required to instantiate version numbers unless a default constructor is defined. + * @returns A new MigrationBuilder with the to version of the migrator as the current version. + */ + with< + TMigrator extends Migrator, + TFrom extends VersionFrom & TCurrent, + TTo extends VersionTo + >( + ...migrate: [new () => TMigrator] | [new (from: TFrom, to: TTo) => TMigrator, TFrom, TTo] + ): MigrationBuilder { + return this.addMigrator(migrate, "up"); + } + + /** Add a migrator to rollback on the MigrationBuilder's list of migrations. As with {@link with}, types of + * MigrationBuilder and Migrator must align. However, this time the migration is reversed so TCurrent of the + * MigrationBuilder must be equal to the to version of the migrator. Return as MigrationBuilder where TFrom + * is the from version of the migrator, so that the next migrator can be chained. + * + * @param migrate A migrator class or a tuple of a migrator class, the from version, and the to version. A tuple is + * required to instantiate version numbers unless a default constructor is defined. + * @returns A new MigrationBuilder with the from version of the migrator as the current version. + */ + rollback< + TMigrator extends Migrator, + TFrom extends VersionFrom, + TTo extends VersionTo & TCurrent + >( + ...migrate: [new () => TMigrator] | [new (from: TFrom, to: TTo) => TMigrator, TTo, TFrom] + ): MigrationBuilder { + if (migrate.length === 3) { + migrate = [migrate[0], migrate[2], migrate[1]]; + } + return this.addMigrator(migrate, "down"); + } + + /** Execute the migrations as defined in the MigrationBuilder's migrator buffer */ + migrate(helper: MigrationHelper): Promise { + return this.migrations.reduce( + (promise, migrator) => + promise.then(async () => { + await this.runMigrator(migrator.migrator, helper, migrator.direction); + }), + Promise.resolve() + ); + } + + private addMigrator< + TMigrator extends Migrator, + TFrom extends VersionFrom & TCurrent, + TTo extends VersionTo + >( + migrate: [new () => TMigrator] | [new (from: TFrom, to: TTo) => TMigrator, TFrom, TTo], + direction: Direction = "up" + ) { + const newMigration = + migrate.length === 1 + ? { migrator: new migrate[0](), direction } + : { migrator: new migrate[0](migrate[1], migrate[2]), direction }; + + return new MigrationBuilder([...this.migrations, newMigration]); + } + + private async runMigrator( + migrator: Migrator, + helper: MigrationHelper, + direction: Direction + ): Promise { + const shouldMigrate = await migrator.shouldMigrate(helper, direction); + helper.info( + `Migrator ${migrator.constructor.name} (to version ${migrator.toVersion}) should migrate: ${shouldMigrate} - ${direction}` + ); + if (shouldMigrate) { + const method = direction === "up" ? migrator.migrate : migrator.rollback; + await method(helper); + helper.info( + `Migrator ${migrator.constructor.name} (to version ${migrator.toVersion}) migrated - ${direction}` + ); + await migrator.updateVersion(helper, direction); + helper.info( + `Migrator ${migrator.constructor.name} (to version ${migrator.toVersion}) updated version - ${direction}` + ); + } + } +} diff --git a/libs/common/src/state-migrations/migration-helper.spec.ts b/libs/common/src/state-migrations/migration-helper.spec.ts new file mode 100644 index 00000000000..5b8a0f2eb4f --- /dev/null +++ b/libs/common/src/state-migrations/migration-helper.spec.ts @@ -0,0 +1,84 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages +import { LogService } from "../platform/abstractions/log.service"; +// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations +import { AbstractStorageService } from "../platform/abstractions/storage.service"; + +import { MigrationHelper } from "./migration-helper"; + +const exampleJSON = { + authenticatedAccounts: [ + "c493ed01-4e08-4e88-abc7-332f380ca760", + "23e61a5f-2ece-4f5e-b499-f0bc489482a9", + ], + "c493ed01-4e08-4e88-abc7-332f380ca760": { + otherStuff: "otherStuff1", + }, + "23e61a5f-2ece-4f5e-b499-f0bc489482a9": { + otherStuff: "otherStuff2", + }, +}; + +describe("RemoveLegacyEtmKeyMigrator", () => { + let storage: MockProxy; + let logService: MockProxy; + let sut: MigrationHelper; + + beforeEach(() => { + logService = mock(); + storage = mock(); + storage.get.mockImplementation((key) => (exampleJSON as any)[key]); + + sut = new MigrationHelper(0, storage, logService); + }); + + describe("get", () => { + it("should delegate to storage.get", async () => { + await sut.get("key"); + expect(storage.get).toHaveBeenCalledWith("key"); + }); + }); + + describe("set", () => { + it("should delegate to storage.save", async () => { + await sut.set("key", "value"); + expect(storage.save).toHaveBeenCalledWith("key", "value"); + }); + }); + + describe("getAccounts", () => { + it("should return all accounts", async () => { + const accounts = await sut.getAccounts(); + expect(accounts).toEqual([ + { userId: "c493ed01-4e08-4e88-abc7-332f380ca760", account: { otherStuff: "otherStuff1" } }, + { userId: "23e61a5f-2ece-4f5e-b499-f0bc489482a9", account: { otherStuff: "otherStuff2" } }, + ]); + }); + + it("should handle missing authenticatedAccounts", async () => { + storage.get.mockImplementation((key) => + key === "authenticatedAccounts" ? undefined : (exampleJSON as any)[key] + ); + const accounts = await sut.getAccounts(); + expect(accounts).toEqual([]); + }); + }); +}); + +/** Helper to create well-mocked migration helpers in migration tests */ +export function mockMigrationHelper(storageJson: any): MockProxy { + const logService: MockProxy = mock(); + const storage: MockProxy = mock(); + storage.get.mockImplementation((key) => (storageJson as any)[key]); + storage.save.mockImplementation(async (key, value) => { + (storageJson as any)[key] = value; + }); + const helper = new MigrationHelper(0, storage, logService); + + const mockHelper = mock(); + mockHelper.get.mockImplementation((key) => helper.get(key)); + mockHelper.set.mockImplementation((key, value) => helper.set(key, value)); + mockHelper.getAccounts.mockImplementation(() => helper.getAccounts()); + return mockHelper; +} diff --git a/libs/common/src/state-migrations/migration-helper.ts b/libs/common/src/state-migrations/migration-helper.ts new file mode 100644 index 00000000000..a185aa69a99 --- /dev/null +++ b/libs/common/src/state-migrations/migration-helper.ts @@ -0,0 +1,37 @@ +// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages +import { LogService } from "../platform/abstractions/log.service"; +// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations +import { AbstractStorageService } from "../platform/abstractions/storage.service"; + +export class MigrationHelper { + constructor( + public currentVersion: number, + private storageService: AbstractStorageService, + public logService: LogService + ) {} + + get(key: string): Promise { + return this.storageService.get(key); + } + + set(key: string, value: T): Promise { + this.logService.info(`Setting ${key}`); + return this.storageService.save(key, value); + } + + info(message: string): void { + this.logService.info(message); + } + + async getAccounts(): Promise< + { userId: string; account: ExpectedAccountType }[] + > { + const userIds = (await this.get("authenticatedAccounts")) ?? []; + return Promise.all( + userIds.map(async (userId) => ({ + userId, + account: await this.get(userId), + })) + ); + } +} diff --git a/libs/common/src/state-migrations/migrations/3-fix-premium.spec.ts b/libs/common/src/state-migrations/migrations/3-fix-premium.spec.ts new file mode 100644 index 00000000000..1ef910d4569 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/3-fix-premium.spec.ts @@ -0,0 +1,111 @@ +import { MockProxy } from "jest-mock-extended"; + +// eslint-disable-next-line import/no-restricted-paths -- Used for testing migration, which requires import +import { TokenService } from "../../auth/services/token.service"; +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { FixPremiumMigrator } from "./3-fix-premium"; + +function migrateExampleJSON() { + return { + global: { + stateVersion: 2, + otherStuff: "otherStuff1", + }, + authenticatedAccounts: [ + "c493ed01-4e08-4e88-abc7-332f380ca760", + "23e61a5f-2ece-4f5e-b499-f0bc489482a9", + ], + "c493ed01-4e08-4e88-abc7-332f380ca760": { + profile: { + otherStuff: "otherStuff2", + hasPremiumPersonally: null as boolean, + }, + tokens: { + otherStuff: "otherStuff3", + accessToken: "accessToken", + }, + otherStuff: "otherStuff4", + }, + "23e61a5f-2ece-4f5e-b499-f0bc489482a9": { + profile: { + otherStuff: "otherStuff5", + hasPremiumPersonally: true, + }, + tokens: { + otherStuff: "otherStuff6", + accessToken: "accessToken", + }, + otherStuff: "otherStuff7", + }, + otherStuff: "otherStuff8", + }; +} + +jest.mock("../../auth/services/token.service", () => ({ + TokenService: { + decodeToken: jest.fn(), + }, +})); + +describe("FixPremiumMigrator", () => { + let helper: MockProxy; + let sut: FixPremiumMigrator; + const decodeTokenSpy = TokenService.decodeToken as jest.Mock; + + beforeEach(() => { + helper = mockMigrationHelper(migrateExampleJSON()); + sut = new FixPremiumMigrator(2, 3); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("migrate", () => { + it("should migrate hasPremiumPersonally", async () => { + decodeTokenSpy.mockResolvedValueOnce({ premium: true }); + await sut.migrate(helper); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("c493ed01-4e08-4e88-abc7-332f380ca760", { + profile: { + otherStuff: "otherStuff2", + hasPremiumPersonally: true, + }, + tokens: { + otherStuff: "otherStuff3", + accessToken: "accessToken", + }, + otherStuff: "otherStuff4", + }); + }); + + it("should not migrate if decode throws", async () => { + decodeTokenSpy.mockRejectedValueOnce(new Error("test")); + await sut.migrate(helper); + + expect(helper.set).not.toHaveBeenCalled(); + }); + + it("should not migrate if decode returns null", async () => { + decodeTokenSpy.mockResolvedValueOnce(null); + await sut.migrate(helper); + + expect(helper.set).not.toHaveBeenCalled(); + }); + }); + + describe("updateVersion", () => { + it("should update version", async () => { + await sut.updateVersion(helper, "up"); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("global", { + stateVersion: 3, + otherStuff: "otherStuff1", + }); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/3-fix-premium.ts b/libs/common/src/state-migrations/migrations/3-fix-premium.ts new file mode 100644 index 00000000000..b6c69a99168 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/3-fix-premium.ts @@ -0,0 +1,48 @@ +// eslint-disable-next-line import/no-restricted-paths -- Used for token decoding, which are valid for days. We want the latest +import { TokenService } from "../../auth/services/token.service"; +import { MigrationHelper } from "../migration-helper"; +import { Migrator, IRREVERSIBLE, Direction } from "../migrator"; + +type ExpectedAccountType = { + profile?: { hasPremiumPersonally?: boolean }; + tokens?: { accessToken?: string }; +}; + +export class FixPremiumMigrator extends Migrator<2, 3> { + async migrate(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + + async function fixPremium(userId: string, account: ExpectedAccountType) { + if (account?.profile?.hasPremiumPersonally === null && account.tokens?.accessToken != null) { + let decodedToken: { premium: boolean }; + try { + decodedToken = await TokenService.decodeToken(account.tokens.accessToken); + } catch { + return; + } + + if (decodedToken?.premium == null) { + return; + } + + account.profile.hasPremiumPersonally = decodedToken?.premium; + return helper.set(userId, account); + } + } + + await Promise.all(accounts.map(({ userId, account }) => fixPremium(userId, account))); + } + + rollback(helper: MigrationHelper): Promise { + throw IRREVERSIBLE; + } + + // Override is necessary because default implementation assumes `stateVersion` at the root, but for this version + // it is nested inside a global object. + override async updateVersion(helper: MigrationHelper, direction: Direction): Promise { + const endVersion = direction === "up" ? this.toVersion : this.fromVersion; + helper.currentVersion = endVersion; + const global: Record = (await helper.get("global")) || {}; + await helper.set("global", { ...global, stateVersion: endVersion }); + } +} diff --git a/libs/common/src/state-migrations/migrations/4-remove-ever-been-unlocked.spec.ts b/libs/common/src/state-migrations/migrations/4-remove-ever-been-unlocked.spec.ts new file mode 100644 index 00000000000..1701762118d --- /dev/null +++ b/libs/common/src/state-migrations/migrations/4-remove-ever-been-unlocked.spec.ts @@ -0,0 +1,75 @@ +import { MockProxy } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { RemoveEverBeenUnlockedMigrator } from "./4-remove-ever-been-unlocked"; + +function migrateExampleJSON() { + return { + global: { + stateVersion: 3, + otherStuff: "otherStuff1", + }, + authenticatedAccounts: [ + "c493ed01-4e08-4e88-abc7-332f380ca760", + "23e61a5f-2ece-4f5e-b499-f0bc489482a9", + ], + "c493ed01-4e08-4e88-abc7-332f380ca760": { + profile: { + otherStuff: "otherStuff2", + everBeenUnlocked: true, + }, + otherStuff: "otherStuff3", + }, + "23e61a5f-2ece-4f5e-b499-f0bc489482a9": { + profile: { + otherStuff: "otherStuff4", + everBeenUnlocked: false, + }, + otherStuff: "otherStuff5", + }, + otherStuff: "otherStuff6", + }; +} + +describe("RemoveEverBeenUnlockedMigrator", () => { + let helper: MockProxy; + let sut: RemoveEverBeenUnlockedMigrator; + + beforeEach(() => { + helper = mockMigrationHelper(migrateExampleJSON()); + sut = new RemoveEverBeenUnlockedMigrator(3, 4); + }); + + describe("migrate", () => { + it("should remove everBeenUnlocked from profile", async () => { + await sut.migrate(helper); + expect(helper.set).toHaveBeenCalledTimes(2); + expect(helper.set).toHaveBeenCalledWith("c493ed01-4e08-4e88-abc7-332f380ca760", { + profile: { + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }); + expect(helper.set).toHaveBeenCalledWith("23e61a5f-2ece-4f5e-b499-f0bc489482a9", { + profile: { + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }); + }); + }); + + describe("updateVersion", () => { + it("should update version up", async () => { + await sut.updateVersion(helper, "up"); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("global", { + stateVersion: 4, + otherStuff: "otherStuff1", + }); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/4-remove-ever-been-unlocked.ts b/libs/common/src/state-migrations/migrations/4-remove-ever-been-unlocked.ts new file mode 100644 index 00000000000..cfa45958d06 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/4-remove-ever-been-unlocked.ts @@ -0,0 +1,32 @@ +import { MigrationHelper } from "../migration-helper"; +import { Direction, IRREVERSIBLE, Migrator } from "../migrator"; + +type ExpectedAccountType = { profile?: { everBeenUnlocked?: boolean } }; + +export class RemoveEverBeenUnlockedMigrator extends Migrator<3, 4> { + async migrate(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + + async function removeEverBeenUnlocked(userId: string, account: ExpectedAccountType) { + if (account?.profile?.everBeenUnlocked != null) { + delete account.profile.everBeenUnlocked; + return helper.set(userId, account); + } + } + + Promise.all(accounts.map(({ userId, account }) => removeEverBeenUnlocked(userId, account))); + } + + rollback(helper: MigrationHelper): Promise { + throw IRREVERSIBLE; + } + + // Override is necessary because default implementation assumes `stateVersion` at the root, but for this version + // it is nested inside a global object. + override async updateVersion(helper: MigrationHelper, direction: Direction): Promise { + const endVersion = direction === "up" ? this.toVersion : this.fromVersion; + helper.currentVersion = endVersion; + const global: { stateVersion: number } = (await helper.get("global")) || ({} as any); + await helper.set("global", { ...global, stateVersion: endVersion }); + } +} diff --git a/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.spec.ts b/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.spec.ts new file mode 100644 index 00000000000..028a0b879b1 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.spec.ts @@ -0,0 +1,141 @@ +import { MockProxy } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { AddKeyTypeToOrgKeysMigrator } from "./5-add-key-type-to-org-keys"; + +function migrateExampleJSON() { + return { + global: { + stateVersion: 4, + otherStuff: "otherStuff1", + }, + authenticatedAccounts: [ + "c493ed01-4e08-4e88-abc7-332f380ca760", + "23e61a5f-2ece-4f5e-b499-f0bc489482a9", + ], + "c493ed01-4e08-4e88-abc7-332f380ca760": { + keys: { + organizationKeys: { + encrypted: { + orgOneId: "orgOneEncKey", + orgTwoId: "orgTwoEncKey", + }, + }, + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }, + }; +} + +function rollbackExampleJSON() { + return { + global: { + stateVersion: 5, + otherStuff: "otherStuff1", + }, + authenticatedAccounts: [ + "c493ed01-4e08-4e88-abc7-332f380ca760", + "23e61a5f-2ece-4f5e-b499-f0bc489482a9", + ], + "c493ed01-4e08-4e88-abc7-332f380ca760": { + keys: { + organizationKeys: { + encrypted: { + orgOneId: { + type: "organization", + key: "orgOneEncKey", + }, + orgTwoId: { + type: "organization", + key: "orgTwoEncKey", + }, + }, + }, + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }, + }; +} + +describe("AddKeyTypeToOrgKeysMigrator", () => { + let helper: MockProxy; + let sut: AddKeyTypeToOrgKeysMigrator; + + describe("migrate", () => { + beforeEach(() => { + helper = mockMigrationHelper(migrateExampleJSON()); + sut = new AddKeyTypeToOrgKeysMigrator(4, 5); + }); + + it("should add organization type to organization keys", async () => { + await sut.migrate(helper); + + expect(helper.set).toHaveBeenCalledWith("c493ed01-4e08-4e88-abc7-332f380ca760", { + keys: { + organizationKeys: { + encrypted: { + orgOneId: { + type: "organization", + key: "orgOneEncKey", + }, + orgTwoId: { + type: "organization", + key: "orgTwoEncKey", + }, + }, + }, + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }); + }); + + it("should update version", async () => { + await sut.updateVersion(helper, "up"); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("global", { + stateVersion: 5, + otherStuff: "otherStuff1", + }); + }); + }); + + describe("rollback", () => { + beforeEach(() => { + helper = mockMigrationHelper(rollbackExampleJSON()); + sut = new AddKeyTypeToOrgKeysMigrator(4, 5); + }); + + it("should remove type from orgainzation keys", async () => { + await sut.rollback(helper); + + expect(helper.set).toHaveBeenCalledWith("c493ed01-4e08-4e88-abc7-332f380ca760", { + keys: { + organizationKeys: { + encrypted: { + orgOneId: "orgOneEncKey", + orgTwoId: "orgTwoEncKey", + }, + }, + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }); + }); + + it("should update version down", async () => { + await sut.updateVersion(helper, "down"); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("global", { + stateVersion: 4, + otherStuff: "otherStuff1", + }); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.ts b/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.ts new file mode 100644 index 00000000000..ab1550c52e3 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/5-add-key-type-to-org-keys.ts @@ -0,0 +1,67 @@ +import { MigrationHelper } from "../migration-helper"; +import { Direction, Migrator } from "../migrator"; + +type ExpectedAccountType = { keys?: { organizationKeys?: { encrypted: Record } } }; +type NewAccountType = { + keys?: { + organizationKeys?: { encrypted: Record }; + }; +}; + +export class AddKeyTypeToOrgKeysMigrator extends Migrator<4, 5> { + async migrate(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + + async function updateOrgKey(userId: string, account: ExpectedAccountType) { + const encryptedOrgKeys = account?.keys?.organizationKeys?.encrypted; + if (encryptedOrgKeys == null) { + return; + } + + const newOrgKeys: Record = {}; + + Object.entries(encryptedOrgKeys).forEach(([orgId, encKey]) => { + newOrgKeys[orgId] = { + type: "organization", + key: encKey, + }; + }); + (account as any).keys.organizationKeys.encrypted = newOrgKeys; + + await helper.set(userId, account); + } + + Promise.all(accounts.map(({ userId, account }) => updateOrgKey(userId, account))); + } + + async rollback(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + + async function updateOrgKey(userId: string, account: NewAccountType) { + const encryptedOrgKeys = account?.keys?.organizationKeys?.encrypted; + if (encryptedOrgKeys == null) { + return; + } + + const newOrgKeys: Record = {}; + + Object.entries(encryptedOrgKeys).forEach(([orgId, encKey]) => { + newOrgKeys[orgId] = encKey.key; + }); + (account as any).keys.organizationKeys.encrypted = newOrgKeys; + + await helper.set(userId, account); + } + + Promise.all(accounts.map(async ({ userId, account }) => updateOrgKey(userId, account))); + } + + // Override is necessary because default implementation assumes `stateVersion` at the root, but for this version + // it is nested inside a global object. + override async updateVersion(helper: MigrationHelper, direction: Direction): Promise { + const endVersion = direction === "up" ? this.toVersion : this.fromVersion; + helper.currentVersion = endVersion; + const global: { stateVersion: number } = (await helper.get("global")) || ({} as any); + await helper.set("global", { ...global, stateVersion: endVersion }); + } +} diff --git a/libs/common/src/state-migrations/migrations/6-remove-legacy-etm-key.spec.ts b/libs/common/src/state-migrations/migrations/6-remove-legacy-etm-key.spec.ts new file mode 100644 index 00000000000..bc7b862f6cf --- /dev/null +++ b/libs/common/src/state-migrations/migrations/6-remove-legacy-etm-key.spec.ts @@ -0,0 +1,80 @@ +import { MockProxy } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { RemoveLegacyEtmKeyMigrator } from "./6-remove-legacy-etm-key"; + +function exampleJSON() { + return { + global: { + stateVersion: 5, + otherStuff: "otherStuff1", + }, + authenticatedAccounts: [ + "c493ed01-4e08-4e88-abc7-332f380ca760", + "23e61a5f-2ece-4f5e-b499-f0bc489482a9", + "fd005ea6-a16a-45ef-ba4a-a194269bfd73", + ], + "c493ed01-4e08-4e88-abc7-332f380ca760": { + keys: { + legacyEtmKey: "legacyEtmKey", + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }, + "23e61a5f-2ece-4f5e-b499-f0bc489482a9": { + keys: { + legacyEtmKey: "legacyEtmKey", + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }, + }; +} + +describe("RemoveLegacyEtmKeyMigrator", () => { + let helper: MockProxy; + let sut: RemoveLegacyEtmKeyMigrator; + + beforeEach(() => { + helper = mockMigrationHelper(exampleJSON()); + sut = new RemoveLegacyEtmKeyMigrator(5, 6); + }); + + describe("migrate", () => { + it("should remove legacyEtmKey from all accounts", async () => { + await sut.migrate(helper); + expect(helper.set).toHaveBeenCalledWith("c493ed01-4e08-4e88-abc7-332f380ca760", { + keys: { + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }); + expect(helper.set).toHaveBeenCalledWith("23e61a5f-2ece-4f5e-b499-f0bc489482a9", { + keys: { + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }); + }); + }); + + describe("rollback", () => { + it("should throw", async () => { + await expect(sut.rollback(helper)).rejects.toThrow(); + }); + }); + + describe("updateVersion", () => { + it("should update version up", async () => { + await sut.updateVersion(helper, "up"); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("global", { + stateVersion: 6, + otherStuff: "otherStuff1", + }); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/6-remove-legacy-etm-key.ts b/libs/common/src/state-migrations/migrations/6-remove-legacy-etm-key.ts new file mode 100644 index 00000000000..2a06916ea33 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/6-remove-legacy-etm-key.ts @@ -0,0 +1,32 @@ +import { MigrationHelper } from "../migration-helper"; +import { Direction, IRREVERSIBLE, Migrator } from "../migrator"; + +type ExpectedAccountType = { keys?: { legacyEtmKey?: string } }; + +export class RemoveLegacyEtmKeyMigrator extends Migrator<5, 6> { + async migrate(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + + async function updateAccount(userId: string, account: ExpectedAccountType) { + if (account?.keys?.legacyEtmKey) { + delete account.keys.legacyEtmKey; + await helper.set(userId, account); + } + } + + await Promise.all(accounts.map(({ userId, account }) => updateAccount(userId, account))); + } + + async rollback(helper: MigrationHelper): Promise { + throw IRREVERSIBLE; + } + + // Override is necessary because default implementation assumes `stateVersion` at the root, but for this version + // it is nested inside a global object. + override async updateVersion(helper: MigrationHelper, direction: Direction): Promise { + const endVersion = direction === "up" ? this.toVersion : this.fromVersion; + helper.currentVersion = endVersion; + const global: { stateVersion: number } = (await helper.get("global")) || ({} as any); + await helper.set("global", { ...global, stateVersion: endVersion }); + } +} diff --git a/libs/common/src/state-migrations/migrations/7-move-biometric-auto-prompt-to-account.spec.ts b/libs/common/src/state-migrations/migrations/7-move-biometric-auto-prompt-to-account.spec.ts new file mode 100644 index 00000000000..fe73f8a9bc4 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/7-move-biometric-auto-prompt-to-account.spec.ts @@ -0,0 +1,102 @@ +import { MockProxy, any, matches } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { MoveBiometricAutoPromptToAccount } from "./7-move-biometric-auto-prompt-to-account"; + +function exampleJSON() { + return { + global: { + stateVersion: 6, + noAutoPromptBiometrics: true, + otherStuff: "otherStuff1", + }, + authenticatedAccounts: [ + "c493ed01-4e08-4e88-abc7-332f380ca760", + "23e61a5f-2ece-4f5e-b499-f0bc489482a9", + "fd005ea6-a16a-45ef-ba4a-a194269bfd73", + ], + "c493ed01-4e08-4e88-abc7-332f380ca760": { + settings: { + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }, + "23e61a5f-2ece-4f5e-b499-f0bc489482a9": { + settings: { + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }, + }; +} + +describe("RemoveLegacyEtmKeyMigrator", () => { + let helper: MockProxy; + let sut: MoveBiometricAutoPromptToAccount; + + beforeEach(() => { + helper = mockMigrationHelper(exampleJSON()); + sut = new MoveBiometricAutoPromptToAccount(6, 7); + }); + + describe("migrate", () => { + it("should remove noAutoPromptBiometrics from global", async () => { + await sut.migrate(helper); + expect(helper.set).toHaveBeenCalledWith("global", { + otherStuff: "otherStuff1", + stateVersion: 6, + }); + }); + + it("should set disableAutoBiometricsPrompt to true on all accounts", async () => { + await sut.migrate(helper); + expect(helper.set).toHaveBeenCalledWith("c493ed01-4e08-4e88-abc7-332f380ca760", { + settings: { + disableAutoBiometricsPrompt: true, + otherStuff: "otherStuff2", + }, + otherStuff: "otherStuff3", + }); + expect(helper.set).toHaveBeenCalledWith("23e61a5f-2ece-4f5e-b499-f0bc489482a9", { + settings: { + disableAutoBiometricsPrompt: true, + otherStuff: "otherStuff4", + }, + otherStuff: "otherStuff5", + }); + }); + + it("should not set disableAutoBiometricsPrompt to true on accounts if noAutoPromptBiometrics is false", async () => { + const json = exampleJSON(); + json.global.noAutoPromptBiometrics = false; + helper = mockMigrationHelper(json); + await sut.migrate(helper); + expect(helper.set).not.toHaveBeenCalledWith( + matches((s) => s != "global"), + any() + ); + }); + }); + + describe("rollback", () => { + it("should throw", async () => { + await expect(sut.rollback(helper)).rejects.toThrow(); + }); + }); + + describe("updateVersion", () => { + it("should update version up", async () => { + await sut.updateVersion(helper, "up"); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith( + "global", + Object.assign({}, exampleJSON().global, { + stateVersion: 7, + }) + ); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/7-move-biometric-auto-prompt-to-account.ts b/libs/common/src/state-migrations/migrations/7-move-biometric-auto-prompt-to-account.ts new file mode 100644 index 00000000000..0ac065d60c1 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/7-move-biometric-auto-prompt-to-account.ts @@ -0,0 +1,45 @@ +import { MigrationHelper } from "../migration-helper"; +import { Direction, IRREVERSIBLE, Migrator } from "../migrator"; + +type ExpectedAccountType = { settings?: { disableAutoBiometricsPrompt?: boolean } }; + +export class MoveBiometricAutoPromptToAccount extends Migrator<6, 7> { + async migrate(helper: MigrationHelper): Promise { + const global = await helper.get<{ noAutoPromptBiometrics?: boolean }>("global"); + const noAutoPromptBiometrics = global?.noAutoPromptBiometrics ?? false; + + const accounts = await helper.getAccounts(); + async function updateAccount(userId: string, account: ExpectedAccountType) { + if (account == null) { + return; + } + + if (noAutoPromptBiometrics) { + account.settings = Object.assign(account?.settings ?? {}, { + disableAutoBiometricsPrompt: true, + }); + await helper.set(userId, account); + } + } + + delete global.noAutoPromptBiometrics; + + await Promise.all([ + ...accounts.map(({ userId, account }) => updateAccount(userId, account)), + helper.set("global", global), + ]); + } + + async rollback(helper: MigrationHelper): Promise { + throw IRREVERSIBLE; + } + + // Override is necessary because default implementation assumes `stateVersion` at the root, but for this version + // it is nested inside a global object. + override async updateVersion(helper: MigrationHelper, direction: Direction): Promise { + const endVersion = direction === "up" ? this.toVersion : this.fromVersion; + helper.currentVersion = endVersion; + const global: { stateVersion: number } = (await helper.get("global")) || ({} as any); + await helper.set("global", { ...global, stateVersion: endVersion }); + } +} diff --git a/libs/common/src/state-migrations/migrations/8-move-state-version.spec.ts b/libs/common/src/state-migrations/migrations/8-move-state-version.spec.ts new file mode 100644 index 00000000000..8c84fd642ea --- /dev/null +++ b/libs/common/src/state-migrations/migrations/8-move-state-version.spec.ts @@ -0,0 +1,90 @@ +import { MockProxy } from "jest-mock-extended"; + +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { MoveStateVersionMigrator } from "./8-move-state-version"; + +function migrateExampleJSON() { + return { + global: { + stateVersion: 6, + otherStuff: "otherStuff1", + }, + otherStuff: "otherStuff2", + }; +} + +function rollbackExampleJSON() { + return { + global: { + otherStuff: "otherStuff1", + }, + stateVersion: 7, + otherStuff: "otherStuff2", + }; +} + +describe("moveStateVersion", () => { + let helper: MockProxy; + let sut: MoveStateVersionMigrator; + + describe("migrate", () => { + beforeEach(() => { + helper = mockMigrationHelper(migrateExampleJSON()); + sut = new MoveStateVersionMigrator(7, 8); + }); + + it("should move state version to root", async () => { + await sut.migrate(helper); + expect(helper.set).toHaveBeenCalledWith("stateVersion", 6); + }); + + it("should remove state version from global", async () => { + await sut.migrate(helper); + expect(helper.set).toHaveBeenCalledWith("global", { + otherStuff: "otherStuff1", + }); + }); + + it("should throw if state version not found", async () => { + helper.get.mockReturnValue({ otherStuff: "otherStuff1" } as any); + await expect(sut.migrate(helper)).rejects.toThrow( + "Migration failed, state version not found" + ); + }); + + it("should update version up", async () => { + await sut.updateVersion(helper, "up"); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("stateVersion", 8); + }); + }); + + describe("rollback", () => { + beforeEach(() => { + helper = mockMigrationHelper(rollbackExampleJSON()); + sut = new MoveStateVersionMigrator(7, 8); + }); + + it("should move state version to global", async () => { + await sut.rollback(helper); + expect(helper.set).toHaveBeenCalledWith("global", { + stateVersion: 7, + otherStuff: "otherStuff1", + }); + expect(helper.set).toHaveBeenCalledWith("stateVersion", undefined); + }); + + it("should update version down", async () => { + await sut.updateVersion(helper, "down"); + + expect(helper.set).toHaveBeenCalledTimes(1); + expect(helper.set).toHaveBeenCalledWith("global", { + stateVersion: 7, + otherStuff: "otherStuff1", + }); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/8-move-state-version.ts b/libs/common/src/state-migrations/migrations/8-move-state-version.ts new file mode 100644 index 00000000000..cbcdf423843 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/8-move-state-version.ts @@ -0,0 +1,37 @@ +import { JsonObject } from "type-fest"; + +import { MigrationHelper } from "../migration-helper"; +import { Direction, Migrator } from "../migrator"; + +export class MoveStateVersionMigrator extends Migrator<7, 8> { + async migrate(helper: MigrationHelper): Promise { + const global = await helper.get<{ stateVersion: number }>("global"); + if (global.stateVersion) { + await helper.set("stateVersion", global.stateVersion); + delete global.stateVersion; + await helper.set("global", global); + } else { + throw new Error("Migration failed, state version not found"); + } + } + + async rollback(helper: MigrationHelper): Promise { + const version = await helper.get("stateVersion"); + const global = await helper.get("global"); + await helper.set("global", { ...global, stateVersion: version }); + await helper.set("stateVersion", undefined); + } + + // Override is necessary because default implementation assumes `stateVersion` at the root, but this migration moves + // it from a `global` object to root.This makes for unique rollback versioning. + override async updateVersion(helper: MigrationHelper, direction: Direction): Promise { + const endVersion = direction === "up" ? this.toVersion : this.fromVersion; + helper.currentVersion = endVersion; + if (direction === "up") { + await helper.set("stateVersion", endVersion); + } else { + const global: { stateVersion: number } = (await helper.get("global")) || ({} as any); + await helper.set("global", { ...global, stateVersion: endVersion }); + } + } +} diff --git a/libs/common/src/state-migrations/migrations/min-version.spec.ts b/libs/common/src/state-migrations/migrations/min-version.spec.ts new file mode 100644 index 00000000000..26e106c19a9 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/min-version.spec.ts @@ -0,0 +1,29 @@ +import { MockProxy } from "jest-mock-extended"; + +import { MIN_VERSION } from "../migrate"; +import { MigrationHelper } from "../migration-helper"; +import { mockMigrationHelper } from "../migration-helper.spec"; + +import { MinVersionMigrator } from "./min-version"; + +describe("MinVersionMigrator", () => { + let helper: MockProxy; + let sut: MinVersionMigrator; + + beforeEach(() => { + helper = mockMigrationHelper(null); + sut = new MinVersionMigrator(); + }); + + describe("shouldMigrate", () => { + it("should return true if current version is less than min version", async () => { + helper.currentVersion = MIN_VERSION - 1; + expect(await sut.shouldMigrate(helper)).toBe(true); + }); + + it("should return false if current version is greater than min version", async () => { + helper.currentVersion = MIN_VERSION + 1; + expect(await sut.shouldMigrate(helper)).toBe(false); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/min-version.ts b/libs/common/src/state-migrations/migrations/min-version.ts new file mode 100644 index 00000000000..a417cc51a3c --- /dev/null +++ b/libs/common/src/state-migrations/migrations/min-version.ts @@ -0,0 +1,26 @@ +import { MinVersion, MIN_VERSION } from "../migrate"; +import { MigrationHelper } from "../migration-helper"; +import { IRREVERSIBLE, Migrator } from "../migrator"; + +export function minVersionError(current: number) { + return `Your local data is too old to be migrated. Your current state version is ${current}, but minimum version is ${MIN_VERSION}.`; +} + +export class MinVersionMigrator extends Migrator<0, MinVersion> { + constructor() { + super(0, MIN_VERSION); + } + + // Overrides the default implementation to catch any version that may be passed in. + override shouldMigrate(helper: MigrationHelper): Promise { + return Promise.resolve(helper.currentVersion < MIN_VERSION); + } + async migrate(helper: MigrationHelper): Promise { + if (helper.currentVersion < MIN_VERSION) { + throw new Error(minVersionError(helper.currentVersion)); + } + } + async rollback(helper: MigrationHelper): Promise { + throw IRREVERSIBLE; + } +} diff --git a/libs/common/src/state-migrations/migrator.spec.ts b/libs/common/src/state-migrations/migrator.spec.ts new file mode 100644 index 00000000000..3abaa277273 --- /dev/null +++ b/libs/common/src/state-migrations/migrator.spec.ts @@ -0,0 +1,75 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages +import { LogService } from "../platform/abstractions/log.service"; +// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations +import { AbstractStorageService } from "../platform/abstractions/storage.service"; + +import { MigrationHelper } from "./migration-helper"; +import { Migrator } from "./migrator"; + +describe("migrator default methods", () => { + class TestMigrator extends Migrator<0, 1> { + async migrate(helper: MigrationHelper): Promise { + await helper.set("test", "test"); + } + async rollback(helper: MigrationHelper): Promise { + await helper.set("test", "rollback"); + } + } + + let storage: MockProxy; + let logService: MockProxy; + let helper: MigrationHelper; + let sut: TestMigrator; + + beforeEach(() => { + storage = mock(); + logService = mock(); + helper = new MigrationHelper(0, storage, logService); + sut = new TestMigrator(0, 1); + }); + + describe("shouldMigrate", () => { + describe("up", () => { + it("should return true if the current version equals the from version", async () => { + expect(await sut.shouldMigrate(helper, "up")).toBe(true); + }); + + it("should return false if the current version does not equal the from version", async () => { + helper.currentVersion = 1; + expect(await sut.shouldMigrate(helper, "up")).toBe(false); + }); + }); + + describe("down", () => { + it("should return true if the current version equals the to version", async () => { + helper.currentVersion = 1; + expect(await sut.shouldMigrate(helper, "down")).toBe(true); + }); + + it("should return false if the current version does not equal the to version", async () => { + expect(await sut.shouldMigrate(helper, "down")).toBe(false); + }); + }); + }); + + describe("updateVersion", () => { + describe("up", () => { + it("should update the version", async () => { + await sut.updateVersion(helper, "up"); + expect(storage.save).toBeCalledWith("stateVersion", 1); + expect(helper.currentVersion).toBe(1); + }); + }); + + describe("down", () => { + it("should update the version", async () => { + helper.currentVersion = 1; + await sut.updateVersion(helper, "down"); + expect(storage.save).toBeCalledWith("stateVersion", 0); + expect(helper.currentVersion).toBe(0); + }); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrator.ts b/libs/common/src/state-migrations/migrator.ts new file mode 100644 index 00000000000..aba81372d49 --- /dev/null +++ b/libs/common/src/state-migrations/migrator.ts @@ -0,0 +1,40 @@ +import { NonNegativeInteger } from "type-fest"; + +import { MigrationHelper } from "./migration-helper"; + +export const IRREVERSIBLE = new Error("Irreversible migration"); + +export type VersionFrom = T extends Migrator + ? TFrom extends NonNegativeInteger + ? TFrom + : never + : never; +export type VersionTo = T extends Migrator + ? TTo extends NonNegativeInteger + ? TTo + : never + : never; +export type Direction = "up" | "down"; + +export abstract class Migrator { + constructor(public fromVersion: TFrom, public toVersion: TTo) { + if (fromVersion == null || toVersion == null) { + throw new Error("Invalid migration"); + } + if (fromVersion > toVersion) { + throw new Error("Invalid migration"); + } + } + + shouldMigrate(helper: MigrationHelper, direction: Direction): Promise { + const startVersion = direction === "up" ? this.fromVersion : this.toVersion; + return Promise.resolve(helper.currentVersion === startVersion); + } + abstract migrate(helper: MigrationHelper): Promise; + abstract rollback(helper: MigrationHelper): Promise; + async updateVersion(helper: MigrationHelper, direction: Direction): Promise { + const endVersion = direction === "up" ? this.toVersion : this.fromVersion; + helper.currentVersion = endVersion; + await helper.set("stateVersion", endVersion); + } +} diff --git a/libs/components/src/multi-select/multi-select.component.html b/libs/components/src/multi-select/multi-select.component.html index d66a06e9e56..1c972fbea46 100644 --- a/libs/components/src/multi-select/multi-select.component.html +++ b/libs/components/src/multi-select/multi-select.component.html @@ -11,7 +11,6 @@ notFoundText="{{ 'multiSelectNotFound' | i18n }}" clearAllText="{{ 'multiSelectClearAll' | i18n }}" [multiple]="true" - [selectOnTab]="true" [closeOnSelect]="false" (close)="onDropdownClosed()" [disabled]="disabled" diff --git a/libs/components/src/multi-select/multi-select.component.ts b/libs/components/src/multi-select/multi-select.component.ts index 89f9e5cb91d..79d32c44cd5 100644 --- a/libs/components/src/multi-select/multi-select.component.ts +++ b/libs/components/src/multi-select/multi-select.component.ts @@ -75,12 +75,6 @@ export class MultiSelectComponent implements OnInit, BitFormFieldControl, Contro return false; } - if (this.select.isOpen && event.key === "Enter" && !hasModifierKey(event)) { - this.select.close(); - event.preventDefault(); - return false; - } - if (this.select.isOpen && event.key === "Escape" && !hasModifierKey(event)) { this.selectedItems = []; this.select.close(); diff --git a/libs/components/src/tw-theme.css b/libs/components/src/tw-theme.css index 420b2cfd483..1ff2064fdbd 100644 --- a/libs/components/src/tw-theme.css +++ b/libs/components/src/tw-theme.css @@ -151,7 +151,7 @@ --color-text-main: 253 246 227; --color-text-muted: 147 161 161; - --color-text-contrast: 0 43 54; + --color-text-contrast: 0 0 0; --color-text-alt2: 255 255 255; --color-text-code: 240 141 199; diff --git a/libs/components/src/variables.scss b/libs/components/src/variables.scss index 1abfd645fd2..88e3cba5c3c 100644 --- a/libs/components/src/variables.scss +++ b/libs/components/src/variables.scss @@ -4,10 +4,10 @@ $primary: #175ddc; $primary-accent: #1252a3; $secondary: #ced4da; $secondary-alt: #1a3b66; -$success: #00a65a; +$success: #017e45; $info: #555555; -$warning: #bf7e16; -$danger: #dd4b39; +$warning: #8b6609; +$danger: #c83522; $white: #ffffff; // Bootstrap Variable Overrides @@ -85,7 +85,7 @@ $mfaTypes: 0, 2, 3, 4, 6; // Theme Variables // Light -$lightDangerHover: #c43421; +$lightDangerHover: #c83522; $lightInputColor: #465057; $lightInputPlaceholderColor: #b6b8b8; diff --git a/package-lock.json b/package-lock.json index 2c955a3ec02..0b364d58ed7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -231,7 +231,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2023.8.2", + "version": "2023.8.3", "hasInstallScript": true, "license": "GPL-3.0" },