diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index b1242ee23ed..5e64dc35875 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -149,7 +149,11 @@ jobs: FILES=$(find . -maxdepth 1 -type f) for FILE in $FILES; do cp "$FILE" browser-source/; done - # Copy apps/browser to Browser source directory + # Copy patches to the Browser source directory + mkdir -p browser-source/patches + cp -r patches/* browser-source/patches + + # Copy apps/browser to the Browser source directory mkdir -p browser-source/apps/browser cp -r apps/browser/* browser-source/apps/browser diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index e47a2f40f53..7f6d5cc11c4 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -37,7 +37,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@44caff7e88d584b04f79f04e31e819f9a95d4d8f + uses: chromaui/action@a45a922b9a7522a4cbb59a7bb7b288a768968924 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 22b76f46408..3dbc08f9859 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Branch check - if: ${{ inputs.release_type != 'Dry Run' }} + if: ${{ github.event.inputs.release_type != 'Dry Run' }} run: | if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc-desktop" ]]; then echo "===================================" @@ -93,7 +93,7 @@ jobs: esac - name: Create GitHub deployment - if: ${{ inputs.release_type != 'Dry Run' }} + if: ${{ github.event.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: ${{ inputs.release_type != 'Dry Run' }} + if: ${{ github.event.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: ${{ inputs.release_type == 'Dry Run' }} + if: ${{ github.event.inputs.release_type == 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@67ab95d7a466bcefdedf3f93cbc10bcff436edfe with: workflow: build-desktop.yml @@ -146,7 +146,7 @@ jobs: run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive - name: Set staged rollout percentage - if: ${{ inputs.electron_publish == 'true' }} + if: ${{ github.event.inputs.electron_publish == 'true' }} env: RELEASE_CHANNEL: ${{ steps.release-channel.outputs.channel }} ROLLOUT_PCT: ${{ inputs.rollout_percentage }} @@ -156,7 +156,7 @@ jobs: echo "stagingPercentage: ${ROLLOUT_PCT}" >> apps/desktop/artifacts/${RELEASE_CHANNEL}-mac.yml - name: Publish artifacts to S3 - if: ${{ inputs.release_type != 'Dry Run' && inputs.electron_publish == 'true' }} + if: ${{ github.event.inputs.release_type != 'Dry Run' && github.event.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: ${{ inputs.release_type != 'Dry Run' && inputs.electron_publish == 'true' }} + if: ${{ github.event.inputs.release_type != 'Dry Run' && github.event.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 }} @@ -192,7 +192,7 @@ jobs: - name: Create Release uses: ncipollo/release-action@a2e71bdd4e7dab70ca26a852f29600c98b33153e # v1.12.0 - if: ${{ steps.release-channel.outputs.channel == 'latest' && inputs.release_type != 'Dry Run' && inputs.github_release == 'true' }} + if: ${{ steps.release-channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' && github.event.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: ${{ inputs.release_type != 'Dry Run' && success() }} + if: ${{ github.event.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: ${{ inputs.release_type != 'Dry Run' && failure() }} + if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 with: token: '${{ secrets.GITHUB_TOKEN }}' @@ -249,7 +249,7 @@ jobs: name: Deploy Snap runs-on: ubuntu-22.04 needs: setup - if: ${{ inputs.snap_publish == 'true' }} + if: ${{ github.event.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: ${{ inputs.release_type != 'Dry Run' }} + if: ${{ github.event.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: ${{ inputs.release_type == 'Dry Run' }} + if: ${{ github.event.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: ${{ inputs.release_type != 'Dry Run' }} + if: ${{ github.event.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 == 'true' }} + if: ${{ github.event.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: ${{ inputs.release_type != 'Dry Run' }} + if: ${{ github.event.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: ${{ inputs.release_type == 'Dry Run' }} + if: ${{ github.event.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: ${{ inputs.release_type != 'Dry Run' }} + if: ${{ github.event.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-bump.yml b/.github/workflows/version-bump.yml index 563facdb40c..c650e42ecf4 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,5 +1,6 @@ --- name: Version Bump +run-name: Version Bump - ${{ github.ref_name }} on: workflow_dispatch: @@ -96,6 +97,26 @@ jobs: ######################## ### Browser + - name: Browser - Verify input version + if: ${{ inputs.bump_browser == true }} + env: + NEW_VERSION: ${{ inputs.version_number }} + run: | + CURRENT_VERSION=$(cat package.json | jq -r '.version') + + # Error if version has not changed. + if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then + echo "Version has not changed." + exit 1 + fi + + # Check if version is newer. + printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V + if [ $? -eq 0 ]; then + echo "Version check successful." + fi + working-directory: apps/browser + - name: Bump Browser Version if: ${{ inputs.bump_browser == true }} env: @@ -124,6 +145,26 @@ jobs: prettier --write apps/browser/src/manifest.v3.json ### CLI + - name: CLI - Verify input version + if: ${{ inputs.bump_cli == true }} + env: + NEW_VERSION: ${{ inputs.version_number }} + run: | + CURRENT_VERSION=$(cat package.json | jq -r '.version') + + # Error if version has not changed. + if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then + echo "Version has not changed." + exit 1 + fi + + # Check if version is newer. + printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V + if [ $? -eq 0 ]; then + echo "Version check successful." + fi + working-directory: apps/cli + - name: Bump CLI Version if: ${{ inputs.bump_cli == true }} env: @@ -131,6 +172,26 @@ jobs: run: npm version --workspace=@bitwarden/cli ${VERSION} ### Desktop + - name: Desktop - Verify input version + if: ${{ inputs.bump_desktop == true }} + env: + NEW_VERSION: ${{ inputs.version_number }} + run: | + CURRENT_VERSION=$(cat package.json | jq -r '.version') + + # Error if version has not changed. + if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then + echo "Version has not changed." + exit 1 + fi + + # Check if version is newer. + printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V + if [ $? -eq 0 ]; then + echo "Version check successful." + fi + working-directory: apps/desktop + - name: Bump Desktop Version - Root if: ${{ inputs.bump_desktop == true }} env: @@ -145,6 +206,26 @@ jobs: working-directory: "apps/desktop/src" ### Web + - name: Web - Verify input version + if: ${{ inputs.bump_web == true }} + env: + NEW_VERSION: ${{ inputs.version_number }} + run: | + CURRENT_VERSION=$(cat package.json | jq -r '.version') + + # Error if version has not changed. + if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then + echo "Version has not changed." + exit 1 + fi + + # Check if version is newer. + printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V + if [ $? -eq 0 ]; then + echo "Version check successful." + fi + working-directory: apps/web + - name: Bump Web Version if: ${{ inputs.bump_web == true }} env: @@ -176,13 +257,13 @@ jobs: run: git commit -m "Bumped ${CLIENT} version to ${VERSION}" -a - name: Push changes - if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} + if: ${{ (github.ref == 'refs/heads/master') && (steps.version-changed.outputs.changes_to_commit == 'TRUE') }} env: BRANCH: ${{ steps.branch.outputs.branch }} run: git push -u origin ${BRANCH} - name: Create Bump Version PR - if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} + if: ${{ (github.ref == 'refs/heads/master') && (steps.version-changed.outputs.changes_to_commit == 'TRUE') }} env: BASE_BRANCH: master BRANCH: ${{ steps.branch.outputs.branch }} diff --git a/apps/browser/package.json b/apps/browser/package.json index cec3a9caab1..fb7b8ad19bf 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2023.8.3", + "version": "2023.9.1", "scripts": { "build": "webpack", "build:mv3": "cross-env MANIFEST_VERSION=3 webpack", diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 774ca11ecf0..b83fb91390e 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -1992,7 +1992,7 @@ "message": "Şəxsi anbarın ixracı" }, "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": "Yalnız $EMAIL$ ilə əlaqələndirilmiş fərdi anbar elementləri xaricə köçürüləcək. Təşkilat anbar elementləri daxil edilməyəcək. Yalnız anbar element məlumatları xaricə köçürüləcək və əlaqələndirilmiş qoşmalar daxil edilməyəcək.", "placeholders": { "email": { "content": "$1", diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 08bef1a50c4..6bd4d028b41 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1992,7 +1992,7 @@ "message": "S'està exportant la caixa forta personal" }, "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": "Només s'exportaran els elements de la caixa forta individuals associats a $EMAIL$. Els elements de la caixa de l'organització no s'inclouran. Només s'exportarà la informació de l'element de la caixa forta i no inclourà els fitxers adjunts associats.", "placeholders": { "email": { "content": "$1", diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index fc1c858e7db..a292bf175c3 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -44,7 +44,7 @@ "message": "Η υπόδειξη του κύριου κωδικού μπορεί να σας βοηθήσει να θυμηθείτε τον κωδικό σας, σε περίπτωση που τον ξεχάσετε." }, "reTypeMasterPass": { - "message": "Εισάγετε Ξανά τον Κύριο Κωδικό σας" + "message": "Εισάγετε ξανά τον Κύριο Κωδικό" }, "masterPassHint": { "message": "Υπόδειξη Κύριου Κωδικού (προαιρετικό)" @@ -997,7 +997,7 @@ "message": "Μπορείτε να απενεργοποιήσετε την αυτόματη συμπλήρωση φόρτωσης σελίδας για μεμονωμένα στοιχεία σύνδεσης από την προβολή Επεξεργασία στοιχείου." }, "itemAutoFillOnPageLoad": { - "message": "Αυτόματη συμπλήρωση της Φόρτισης Σελίδας (αν είναι ενεργοποιημένη στις Επιλογές)" + "message": "Αυτόματη συμπλήρωση κατά τη φόρτωση της σελίδας (αν έχει ενεργοποιηθεί στις Ρυθμίσεις)" }, "autoFillOnPageLoadUseDefault": { "message": "Χρήση προεπιλεγμένης ρύθμισης" diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index e074e034412..4b15ef44e8a 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -771,8 +771,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "updateKey": { - "message": "You cannot use this feature until you update your encryption key." + "encryptionKeyMigrationRequired": { + "message": "Encryption key migration required. Please login through the web vault to update your encryption key." }, "premiumMembership": { "message": "Premium membership" diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 697f04154ce..5fafd94ccf8 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -1992,7 +1992,7 @@ "message": "Exporting individual vault" }, "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", diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 72d99fcecce..be561aad7c3 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -1992,7 +1992,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", diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 5325ef6a4f1..64df3ec8778 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -751,7 +751,7 @@ "message": "附件已删除" }, "newAttachment": { - "message": "添加新的附件" + "message": "添加新附件" }, "noAttachments": { "message": "没有附件。" @@ -853,7 +853,7 @@ "message": "请输入您的验证器应用中的 6 位验证码。" }, "enterVerificationCodeEmail": { - "message": "请输入通过电子邮件发送给 $EMAIL$ 的 6 位验证码。", + "message": "请输入发送给电子邮件 $EMAIL$ 的 6 位数验证码。", "placeholders": { "email": { "content": "$1", @@ -1036,7 +1036,7 @@ "message": "值" }, "newCustomField": { - "message": "新建自定义字段" + "message": "新增自定义字段" }, "dragToSort": { "message": "拖动排序" @@ -1059,7 +1059,7 @@ "description": "This describes a value that is 'linked' (tied) to another value." }, "popup2faCloseMessage": { - "message": "如果您点击弹窗外的任何区域,将导致弹窗关闭。您想在新窗口中打开此弹窗,以便它不会关闭吗?" + "message": "如果您点击弹窗外的区域以检查您的验证码电子邮件,将导致弹窗关闭。您想在新窗口中打开此弹窗,以便它不会关闭吗?" }, "popupU2fCloseMessage": { "message": "此浏览器无法处理此弹出窗口中的 U2F 请求。您想要在新窗口中打开此弹出窗口吗?" @@ -1182,7 +1182,7 @@ "message": "许可证号码" }, "email": { - "message": "Email" + "message": "电子邮件" }, "phone": { "message": "电话" @@ -1615,7 +1615,7 @@ "message": "未提供权限" }, "nativeMessaginPermissionErrorDesc": { - "message": "没有与 Bitwarden 桌面应用程序通信的权限,我们无法在浏览器扩展中提供生物识别。请再试一次。" + "message": "没有与 Bitwarden 桌面应用程序通信的权限,我们无法在浏览器扩展中提供生物识别。请重试。" }, "nativeMessaginPermissionSidebarTitle": { "message": "权限请求错误" @@ -1896,7 +1896,7 @@ "message": "选择文件夹..." }, "ssoCompleteRegistration": { - "message": "要完成 SSO 登陆配置,请设置一个主密码以访问和保护您的密码库。" + "message": "要完成 SSO 登录配置,请设置一个主密码以访问和保护您的密码库。" }, "hours": { "message": "小时" @@ -1986,13 +1986,13 @@ "message": "字符计数开关" }, "sessionTimeout": { - "message": "您的会话已超时。请返回并尝试重新登录。" + "message": "您的会话已超时。请返回然后尝试重新登录。" }, "exportingPersonalVaultTitle": { "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", @@ -2117,7 +2117,7 @@ } }, "loginWithMasterPassword": { - "message": "使用主密码登录" + "message": "主密码登录" }, "loggingInAs": { "message": "正登录为" diff --git a/apps/browser/src/auth/popup/home.component.html b/apps/browser/src/auth/popup/home.component.html index da208a8d50c..6b42033c4bc 100644 --- a/apps/browser/src/auth/popup/home.component.html +++ b/apps/browser/src/auth/popup/home.component.html @@ -9,7 +9,7 @@ - +
c.reprompt === CipherRepromptType.None); + cipher = ciphers[0]; } else { const ciphers = await this.cipherService.getAllDecrypted(); cipher = ciphers.find((c) => c.id === id); diff --git a/apps/browser/src/autofill/content/autofill.js b/apps/browser/src/autofill/content/autofill.js index 1833c09e159..2f3857d3fa8 100644 --- a/apps/browser/src/autofill/content/autofill.js +++ b/apps/browser/src/autofill/content/autofill.js @@ -986,13 +986,18 @@ styleTimeout = 200; /** - * Fll an element `el` using the value `op` from the fill script + * Fill an element `el` using the value `op` from the fill script * @param {HTMLElement} el * @param {string} op */ function fillTheElement(el, op) { var shouldCheck; if (el && null !== op && void 0 !== op && !(el.disabled || el.a || el.readOnly)) { + const tabURLChanged = !fillScript.savedUrls?.some(url => url.startsWith(window.location.origin)) + // Check to make sure the page location didn't change + if (tabURLChanged) { + return; + } switch (markTheFilling && el.form && !el.form.opfilled && (el.form.opfilled = true), el.type ? el.type.toLowerCase() : null) { case 'checkbox': diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 5fc4b76c4ca..e6a629cfc9d 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -75,7 +75,7 @@ describe("AutofillService", () => { const autofillV1Script = "autofill.js"; const autofillV2Script = "autofill-init.js"; const defaultAutofillScripts = ["autofiller.js", "notificationBar.js", "contextMenuHandler.js"]; - const defaultExecuteScriptOptions = { allFrames: true, runAt: "document_start" }; + const defaultExecuteScriptOptions = { runAt: "document_start" }; let tabMock: chrome.tabs.Tab; let sender: chrome.runtime.MessageSender; @@ -91,11 +91,13 @@ describe("AutofillService", () => { [autofillV1Script, ...defaultAutofillScripts].forEach((scriptName) => { expect(BrowserApi.executeScriptInTab).toHaveBeenCalledWith(tabMock.id, { file: `content/${scriptName}`, + frameId: sender.frameId, ...defaultExecuteScriptOptions, }); }); expect(BrowserApi.executeScriptInTab).not.toHaveBeenCalledWith(tabMock.id, { file: `content/${autofillV2Script}`, + frameId: sender.frameId, ...defaultExecuteScriptOptions, }); }); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index dc2a4918c3b..fcf4dffd4aa 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -62,7 +62,7 @@ export default class AutofillService implements AutofillServiceInterface { for (const injectedScript of injectedScripts) { await BrowserApi.executeScriptInTab(sender.tab.id, { file: `content/${injectedScript}`, - allFrames: true, + frameId: sender.frameId, runAt: "document_start", }); } diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts index 828d768ca25..0ab74875fbf 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts @@ -108,6 +108,7 @@ describe("InsertAutofillContentService", () => { jest.spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe"); jest.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill"); jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill"); + jest.spyOn(insertAutofillContentService as any, "tabURLChanged"); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -119,6 +120,7 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).not.toHaveBeenCalled(); + expect(insertAutofillContentService["tabURLChanged"]).not.toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); }); @@ -128,6 +130,7 @@ describe("InsertAutofillContentService", () => { .mockReturnValue(true); jest.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill"); jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill"); + jest.spyOn(insertAutofillContentService as any, "tabURLChanged"); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -139,6 +142,7 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).not.toHaveBeenCalled(); + expect(insertAutofillContentService["tabURLChanged"]).not.toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); }); @@ -150,6 +154,7 @@ describe("InsertAutofillContentService", () => { .spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill") .mockReturnValue(true); jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill"); + jest.spyOn(insertAutofillContentService as any, "tabURLChanged"); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -159,6 +164,7 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).not.toHaveBeenCalled(); + expect(insertAutofillContentService["tabURLChanged"]).not.toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); }); @@ -172,6 +178,7 @@ describe("InsertAutofillContentService", () => { jest .spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill") .mockReturnValue(true); + jest.spyOn(insertAutofillContentService as any, "tabURLChanged").mockReturnValue(false); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -181,6 +188,31 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).toHaveBeenCalled(); + expect(insertAutofillContentService["tabURLChanged"]).not.toHaveBeenCalled(); + expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); + }); + + it("returns early if the page location origin does not match against any of the cipher saved URLs", () => { + jest + .spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe") + .mockReturnValue(false); + jest + .spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill") + .mockReturnValue(false); + jest + .spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill") + .mockReturnValue(false); + jest.spyOn(insertAutofillContentService as any, "tabURLChanged").mockReturnValue(true); + jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); + + insertAutofillContentService.fillForm(fillScript); + + expect(insertAutofillContentService["fillingWithinSandboxedIframe"]).toHaveBeenCalled(); + expect(insertAutofillContentService["userCancelledInsecureUrlAutofill"]).toHaveBeenCalled(); + expect( + insertAutofillContentService["userCancelledUntrustedIframeAutofill"] + ).toHaveBeenCalled(); + expect(insertAutofillContentService["tabURLChanged"]).toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled(); }); @@ -194,6 +226,7 @@ describe("InsertAutofillContentService", () => { jest .spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill") .mockReturnValue(false); + jest.spyOn(insertAutofillContentService as any, "tabURLChanged").mockReturnValue(false); jest.spyOn(insertAutofillContentService as any, "runFillScriptAction"); insertAutofillContentService.fillForm(fillScript); @@ -203,6 +236,7 @@ describe("InsertAutofillContentService", () => { expect( insertAutofillContentService["userCancelledUntrustedIframeAutofill"] ).toHaveBeenCalled(); + expect(insertAutofillContentService["tabURLChanged"]).toHaveBeenCalled(); expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenCalledTimes(3); expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith( 1, diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.ts index 89f644ba6be..4e47e73704b 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.ts @@ -38,7 +38,8 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf !fillScript.script?.length || this.fillingWithinSandboxedIframe() || this.userCancelledInsecureUrlAutofill(fillScript.savedUrls) || - this.userCancelledUntrustedIframeAutofill(fillScript) + this.userCancelledUntrustedIframeAutofill(fillScript) || + this.tabURLChanged(fillScript.savedUrls) ) { return; } @@ -46,6 +47,16 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf fillScript.script.forEach(this.runFillScriptAction); } + /** + * Determines if the page URL no longer matches one of the cipher's savedURL domains + * @param {string[] | null} savedUrls + * @returns {boolean} + * @private + */ + private tabURLChanged(savedUrls?: AutofillScript["savedUrls"]): boolean { + return savedUrls && !savedUrls.some((url) => url.startsWith(window.location.origin)); + } + /** * Identifies if the execution of this script is happening * within a sandboxed iframe. diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index a0a9548c7f3..0f21a10a84f 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2023.8.3", + "version": "2023.9.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index af8700066f5..b02d7aec847 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2023.8.3", + "version": "2023.9.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index d8143859e8f..5ad5ea17317 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -213,7 +213,10 @@ export class BrowserApi { chrome.windows.create({ url, focused, type }); } + // Keep track of all the events registered in a Safari popup so we can remove + // them when the popup gets unloaded, otherwise we cause a memory leak private static registeredMessageListeners: any[] = []; + private static registeredStorageChangeListeners: any[] = []; static messageListener( name: string, @@ -232,21 +235,38 @@ export class BrowserApi { } ); - // Keep track of all the events registered in a Safari popup so we can remove - // them when the popup gets unloaded, otherwise we cause a memory leak if (BrowserApi.isSafariApi && !BrowserApi.isBackgroundPage(window)) { BrowserApi.registeredMessageListeners.push(callback); - - // The MDN recommend using 'visibilitychange' but that event is fired any time the popup window is obscured as well - // 'pagehide' works just like 'unload' but is compatible with the back/forward cache, so we prefer using that one - window.onpagehide = () => { - for (const callback of BrowserApi.registeredMessageListeners) { - chrome.runtime.onMessage.removeListener(callback); - } - }; + BrowserApi.setupUnloadListeners(); } } + static storageChangeListener( + callback: Parameters[0] + ) { + chrome.storage.onChanged.addListener(callback); + + if (BrowserApi.isSafariApi && !BrowserApi.isBackgroundPage(window)) { + BrowserApi.registeredStorageChangeListeners.push(callback); + BrowserApi.setupUnloadListeners(); + } + } + + // Setup the event to destroy all the listeners when the popup gets unloaded in Safari, otherwise we get a memory leak + private static setupUnloadListeners() { + // The MDN recommend using 'visibilitychange' but that event is fired any time the popup window is obscured as well + // 'pagehide' works just like 'unload' but is compatible with the back/forward cache, so we prefer using that one + window.onpagehide = () => { + for (const callback of BrowserApi.registeredMessageListeners) { + chrome.runtime.onMessage.removeListener(callback); + } + + for (const callback of BrowserApi.registeredStorageChangeListeners) { + chrome.storage.onChanged.removeListener(callback); + } + }; + } + static messageListener$() { return new Observable((subscriber) => { const handler = (message: unknown) => { diff --git a/apps/browser/src/platform/services/browser-state.service.ts b/apps/browser/src/platform/services/browser-state.service.ts index 5e356e7fbe8..ec6851beb8f 100644 --- a/apps/browser/src/platform/services/browser-state.service.ts +++ b/apps/browser/src/platform/services/browser-state.service.ts @@ -14,6 +14,7 @@ import { Account } from "../../models/account"; import { BrowserComponentState } from "../../models/browserComponentState"; import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; import { BrowserSendComponentState } from "../../models/browserSendComponentState"; +import { BrowserApi } from "../browser/browser-api"; import { browserSession, sessionSync } from "../decorators/session-sync-observable"; import { BrowserStateService as StateServiceAbstraction } from "./abstractions/browser-state.service"; @@ -56,7 +57,7 @@ export class BrowserStateService // the background page that can get out of sync. We need to work out the // best way to handle caching with multiple instances of the state service. if (useAccountCache) { - chrome.storage.onChanged.addListener((changes, namespace) => { + BrowserApi.storageChangeListener((changes, namespace) => { if (namespace === "local") { for (const key of Object.keys(changes)) { if (key !== "accountActivity" && this.accountDiskCache.value[key]) { diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index d5a50687734..b666f55fc9d 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -62,7 +62,6 @@ import { PrivateModeWarningComponent } from "./components/private-mode-warning.c import { SetPinComponent } from "./components/set-pin.component"; import { UserVerificationComponent } from "./components/user-verification.component"; import { ServicesModule } from "./services/services.module"; -import { AboutComponent } from "./settings/about.component"; import { AutofillComponent } from "./settings/autofill.component"; import { ExcludedDomainsComponent } from "./settings/excluded-domains.component"; import { FoldersComponent } from "./settings/folders.component"; @@ -152,7 +151,6 @@ import "../platform/popup/locales"; ViewCustomFieldsComponent, RemovePasswordComponent, VaultSelectComponent, - AboutComponent, Fido2Component, HelpAndFeedbackComponent, AutofillComponent, diff --git a/apps/browser/src/popup/scss/environment.scss b/apps/browser/src/popup/scss/environment.scss index 1ac0f4240bd..f6adba86c2e 100644 --- a/apps/browser/src/popup/scss/environment.scss +++ b/apps/browser/src/popup/scss/environment.scss @@ -51,15 +51,11 @@ html.browser_safari { color: $text-muted; line-height: 25px; font-weight: 400; - padding-left: 5px; + padding-left: 15px; - label { + span { font-weight: 600; - } - - a, - a label:hover { - cursor: pointer; + font-size: $font-size-small; } } @@ -91,7 +87,3 @@ html.browser_safari { } } } - -.environment-selector-padding { - padding-left: 10px; -} diff --git a/apps/browser/src/popup/scss/popup.scss b/apps/browser/src/popup/scss/popup.scss index 7d718b86645..0d7e4281386 100644 --- a/apps/browser/src/popup/scss/popup.scss +++ b/apps/browser/src/popup/scss/popup.scss @@ -12,3 +12,4 @@ @import "environment.scss"; @import "pages.scss"; @import "@angular/cdk/overlay-prebuilt.css"; +@import "../../../../../libs/components/src/multi-select/scss/bw.theme"; diff --git a/apps/browser/src/popup/settings/about.component.html b/apps/browser/src/popup/settings/about.component.html index 24fea4eb9da..b68f592492f 100644 --- a/apps/browser/src/popup/settings/about.component.html +++ b/apps/browser/src/popup/settings/about.component.html @@ -1,52 +1,46 @@ -
-
+
-
+
-
-
- - {{ "updateKeyTitle" | i18n }} -
-
-

{{ "updateEncryptionKeyShortDesc" | i18n }}

- -
-
-

{{ "people" | i18n }}

-
- - - -
+ + +
- - + [placeholder]="'search' | i18n" + >
- diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.ts index 3f365271168..6afa4ac9ff4 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.ts @@ -50,6 +50,7 @@ export class PeopleComponent userType = ProviderUserType; userStatusType = ProviderUserStatusType; + status: ProviderUserStatusType = null; providerId: string; accessEvents = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index cda2a108f7f..70bc6241b6f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -4,6 +4,7 @@ import { FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ModalService } from "@bitwarden/angular/services/modal.service"; +import { SearchModule } from "@bitwarden/components"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { AddOrganizationComponent } from "./clients/add-organization.component"; @@ -26,7 +27,14 @@ import { SetupProviderComponent } from "./setup/setup-provider.component"; import { SetupComponent } from "./setup/setup.component"; @NgModule({ - imports: [CommonModule, FormsModule, OssModule, JslibModule, ProvidersRoutingModule], + imports: [ + CommonModule, + FormsModule, + OssModule, + JslibModule, + SearchModule, + ProvidersRoutingModule, + ], declarations: [ AcceptProviderComponent, AccountComponent, diff --git a/libs/angular/src/auth/components/environment-selector.component.html b/libs/angular/src/auth/components/environment-selector.component.html index 4e8d33bde68..2765787c317 100644 --- a/libs/angular/src/auth/components/environment-selector.component.html +++ b/libs/angular/src/auth/components/environment-selector.component.html @@ -1,40 +1,51 @@ -