From 16fe609867cd4c56ab245ee1bd39de08870154ec Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:00:04 +0100 Subject: [PATCH 01/28] added checked or unchecked validation to send hide email policy subscriber (#6317) --- libs/angular/src/tools/send/add-edit.component.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/angular/src/tools/send/add-edit.component.ts b/libs/angular/src/tools/send/add-edit.component.ts index ffe0b6a4f9d..32d14db471c 100644 --- a/libs/angular/src/tools/send/add-edit.component.ts +++ b/libs/angular/src/tools/send/add-edit.component.ts @@ -154,8 +154,13 @@ export class AddEditComponent implements OnInit, OnDestroy { .policyAppliesToActiveUser$(PolicyType.SendOptions, (p) => p.data.disableHideEmail) .pipe(takeUntil(this.destroy$)) .subscribe((policyAppliesToActiveUser) => { - if ((this.disableHideEmail = policyAppliesToActiveUser)) { + if ( + (this.disableHideEmail = policyAppliesToActiveUser) && + !this.formGroup.controls.hideEmail.value + ) { this.formGroup.controls.hideEmail.disable(); + } else { + this.formGroup.controls.hideEmail.enable(); } }); From d8b76c1f514984de882287684bbf7766eb0aa4f6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:43:36 -0400 Subject: [PATCH 02/28] Update chromaui/action digest to a45a922 (#6120) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/chromatic.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }} From 2e76bc40b9d12b31339e0ce1e2a4190644a15b9a Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:29:07 -0400 Subject: [PATCH 03/28] Update Version Bump workflow (#6300) --- .github/workflows/version-bump.yml | 85 +++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) 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 }} From 4a8741e7b69e434a84b0b5292b95438b8d3c4204 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:21:30 -0400 Subject: [PATCH 04/28] PM-3444 - TDE - Admin Acct Recovery should prompt users to change MP for non MP decryption flows (#6109) * PM-3444 - SSO Login Strategy - Should setForcePasswordResetReason if server sends it down so that the auth.guard can direct the user accordingly after decryption * PM-3444 - (1) Sso Comp - Adjust force password reset logic to handle the only scenario that can occur here - admin acct recovery - not weak mp (can't evaluate as user won't have entered it yet) (2) Add comments explaining the scenarios + update tests. * PM-3444 - Update SSO Login strategy to only check for ForceResetPasswordReason.AdminForcePasswordReset as that's the only scenario that can happen here. * PM-3444 - Finish updating tests to pass * PM-3444 - Resolve PR feedback by updating ForceResetPasswordReason comments --- libs/angular/src/auth/components/sso.component.spec.ts | 4 ++-- libs/angular/src/auth/components/sso.component.ts | 6 +++--- libs/angular/src/auth/components/two-factor.component.ts | 6 ++++-- libs/common/src/auth/login-strategies/login.strategy.ts | 1 + libs/common/src/auth/login-strategies/sso-login.strategy.ts | 6 ++++++ .../src/auth/models/domain/force-reset-password-reason.ts | 6 ++++++ 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/libs/angular/src/auth/components/sso.component.spec.ts b/libs/angular/src/auth/components/sso.component.spec.ts index 894232af443..6d81b3d61ec 100644 --- a/libs/angular/src/auth/components/sso.component.spec.ts +++ b/libs/angular/src/auth/components/sso.component.spec.ts @@ -352,7 +352,7 @@ describe("SsoComponent", () => { describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is required", () => { [ ForceResetPasswordReason.AdminForcePasswordReset, - ForceResetPasswordReason.WeakMasterPassword, + // ForceResetPasswordReason.WeakMasterPassword, -- not possible in SSO flow as set client side ].forEach((forceResetPasswordReason) => { const reasonString = ForceResetPasswordReason[forceResetPasswordReason]; let authResult; @@ -449,7 +449,7 @@ describe("SsoComponent", () => { describe("Force Master Password Reset scenarios", () => { [ ForceResetPasswordReason.AdminForcePasswordReset, - ForceResetPasswordReason.WeakMasterPassword, + // ForceResetPasswordReason.WeakMasterPassword, -- not possible in SSO flow as set client side ].forEach((forceResetPasswordReason) => { const reasonString = ForceResetPasswordReason[forceResetPasswordReason]; diff --git a/libs/angular/src/auth/components/sso.component.ts b/libs/angular/src/auth/components/sso.component.ts index 7e6aca7ec07..c52902d49a0 100644 --- a/libs/angular/src/auth/components/sso.component.ts +++ b/libs/angular/src/auth/components/sso.component.ts @@ -226,9 +226,9 @@ export class SsoComponent { return await this.handleChangePasswordRequired(orgIdentifier); } - // Users can be forced to reset their password via an admin or org policy - // disallowing weak passwords - if (authResult.forcePasswordReset !== ForceResetPasswordReason.None) { + // Users enrolled in admin acct recovery can be forced to set a new password after + // having the admin set a temp password for them + if (authResult.forcePasswordReset == ForceResetPasswordReason.AdminForcePasswordReset) { return await this.handleForcePasswordReset(orgIdentifier); } diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index 9a6352283a0..b87f79a8c44 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -282,8 +282,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI return await this.handleChangePasswordRequired(orgIdentifier); } - // Users can be forced to reset their password via an admin or org policy - // disallowing weak passwords + // Users can be forced to reset their password via an admin or org policy disallowing weak passwords + // Note: this is different from SSO component login flow as a user can + // login with MP and then have to pass 2FA to finish login and we can actually + // evaluate if they have a weak password at this time. if (authResult.forcePasswordReset !== ForceResetPasswordReason.None) { return await this.handleForcePasswordReset(orgIdentifier); } diff --git a/libs/common/src/auth/login-strategies/login.strategy.ts b/libs/common/src/auth/login-strategies/login.strategy.ts index 6e51f215012..96855a34106 100644 --- a/libs/common/src/auth/login-strategies/login.strategy.ts +++ b/libs/common/src/auth/login-strategies/login.strategy.ts @@ -153,6 +153,7 @@ export abstract class LogInStrategy { const result = new AuthResult(); result.resetMasterPassword = response.resetMasterPassword; + // Convert boolean to enum if (response.forcePasswordReset) { result.forcePasswordReset = ForceResetPasswordReason.AdminForcePasswordReset; } 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 09dbca72fea..328dda527ae 100644 --- a/libs/common/src/auth/login-strategies/sso-login.strategy.ts +++ b/libs/common/src/auth/login-strategies/sso-login.strategy.ts @@ -14,6 +14,7 @@ import { DeviceTrustCryptoServiceAbstraction } from "../abstractions/device-trus import { KeyConnectorService } from "../abstractions/key-connector.service"; import { TokenService } from "../abstractions/token.service"; import { TwoFactorService } from "../abstractions/two-factor.service"; +import { ForceResetPasswordReason } from "../models/domain/force-reset-password-reason"; import { SsoLogInCredentials } from "../models/domain/log-in-credentials"; import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request"; import { IdentityTokenResponse } from "../models/response/identity-token.response"; @@ -73,6 +74,11 @@ export class SsoLogInStrategy extends LogInStrategy { this.email = ssoAuthResult.email; this.ssoEmail2FaSessionToken = ssoAuthResult.ssoEmail2FaSessionToken; + // Auth guard currently handles redirects for this. + if (ssoAuthResult.forcePasswordReset == ForceResetPasswordReason.AdminForcePasswordReset) { + await this.stateService.setForcePasswordResetReason(ssoAuthResult.forcePasswordReset); + } + return ssoAuthResult; } diff --git a/libs/common/src/auth/models/domain/force-reset-password-reason.ts b/libs/common/src/auth/models/domain/force-reset-password-reason.ts index c70a4cb33cb..99e461c2ea1 100644 --- a/libs/common/src/auth/models/domain/force-reset-password-reason.ts +++ b/libs/common/src/auth/models/domain/force-reset-password-reason.ts @@ -1,3 +1,7 @@ +/* + * This enum is used to determine if a user should be forced to reset their password + * on login (server flag) or unlock via MP (client evaluation). + */ export enum ForceResetPasswordReason { /** * A password reset should not be forced. @@ -6,12 +10,14 @@ export enum ForceResetPasswordReason { /** * Occurs when an organization admin forces a user to reset their password. + * Communicated via server flag. */ AdminForcePasswordReset, /** * Occurs when a user logs in / unlocks their vault with a master password that does not meet an organization's * master password policy that is enforced on login/unlock. + * Only set client side b/c server can't evaluate MP. */ WeakMasterPassword, } From 2f6af9c192d927b5c312ff2cd399614f4dffdf89 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:23:49 +0000 Subject: [PATCH 05/28] Autosync the updated translations (#6325) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/az/messages.json | 2 +- apps/browser/src/_locales/ca/messages.json | 2 +- apps/browser/src/_locales/el/messages.json | 4 ++-- apps/browser/src/_locales/hi/messages.json | 2 +- apps/browser/src/_locales/sr/messages.json | 2 +- apps/browser/src/_locales/zh_CN/messages.json | 20 +++++++++---------- 6 files changed, 16 insertions(+), 16 deletions(-) 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/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": "正登录为" From a39a3fbf8d0783097cdd46e8370f4cff353cb008 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 16:24:59 +0000 Subject: [PATCH 06/28] Autosync the updated translations (#6327) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/az/messages.json | 2 +- apps/desktop/src/locales/ca/messages.json | 2 +- apps/desktop/src/locales/de/messages.json | 2 +- apps/desktop/src/locales/el/messages.json | 12 ++++----- apps/desktop/src/locales/sr/messages.json | 2 +- apps/desktop/src/locales/zh_CN/messages.json | 26 ++++++++++---------- apps/desktop/src/locales/zh_TW/messages.json | 18 +++++++------- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 3d842ff3843..d14a5e369d9 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1985,7 +1985,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/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 365ca0dda4d..3cb44b3ecbb 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1985,7 +1985,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/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index b946a3f5f37..ddb23c5aa33 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -1985,7 +1985,7 @@ "message": "Persönlichen Tresor exportieren" }, "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": "Es werden nur persönliche Tresoreinträge exportiert, die mit $EMAIL$ verbunden sind. Tresoreinträge der Organisation werden nicht berücksichtigt. Es werden nur Informationen der Tresoreinträge exportiert. Diese enthalten nicht die zugehörigen Anhänge.", "placeholders": { "email": { "content": "$1", diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 8bab72d6219..4bb7f624193 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -929,7 +929,7 @@ "message": "Όταν ελαχιστοποιείται το παράθυρο, εμφανίζεται ένα εικονίδιο στη γραμμή μενού." }, "enableCloseToTray": { - "message": "Κλείσιμο στο Εικονίδιο Δίσκου" + "message": "Κλείσιμο σε εικονίδιο περιοχής ειδοποιήσεων" }, "enableCloseToTrayDesc": { "message": "Κατά το κλείσιμο του παραθύρου, να εμφανίζεται ένα εικονίδιο στην περιοχή ειδοποιήσεων." @@ -941,13 +941,13 @@ "message": "Κατά το κλείσιμο του παραθύρου, εμφανίζεται ένα εικονίδιο στη γραμμή μενού." }, "enableTray": { - "message": "Ενεργοποίηση Εικονιδίου Δίσκου" + "message": "Ενεργοποίηση εικονιδίου περιοχής ειδοποιήσεων" }, "enableTrayDesc": { "message": "Να εμφανίζεται πάντα ένα εικονίδιο στην περιοχή ειδοποιήσεων." }, "startToTray": { - "message": "Έναρξη στο εικονίδιο δίσκου" + "message": "Έναρξη σε εικονίδιο περιοχής ειδοποιήσεων" }, "startToTrayDesc": { "message": "Όταν ξεκινάει για πρώτη φορά η εφαρμογή, να εμφανίζεται μόνο ένα εικονίδιο στην περιοχή ειδοποιήσεων." @@ -1408,7 +1408,7 @@ "message": "Ξεκλειδώστε το vault σας" }, "autoPromptWindowsHello": { - "message": "Εμφάνιση Windows Hello κατά την εκκίνηση της εφαρμογής" + "message": "Να ζητείται Windows Hello κατά την εκκίνηση της εφαρμογής" }, "autoPromptTouchId": { "message": "Ερώτηση για το Touch ID κατά την εκκίνηση" @@ -2250,7 +2250,7 @@ "message": "Ενημέρωση Προτεινόμενων Ρυθμίσεων" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Απαιτείται έγκριση συσκευής. Επιλέξτε μια επιλογή έγκρισης παρακάτω:" }, "rememberThisDevice": { "message": "Remember this device" @@ -2320,7 +2320,7 @@ "message": "Input is required." }, "required": { - "message": "required" + "message": "απαιτείται" }, "search": { "message": "Search" diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 0e0a7c397e1..29b9ebd2a49 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1985,7 +1985,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/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index a0c1e85b730..81b08101eb9 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -317,7 +317,7 @@ "message": "文件夹" }, "newCustomField": { - "message": "新建自定义字段" + "message": "新增自定义字段" }, "value": { "message": "值" @@ -555,7 +555,7 @@ "message": "两次填写的主密码不一致。" }, "newAccountCreated": { - "message": "已经为您建立了账户,您可以登录了。" + "message": "您的新账户已创建!您现在可以登录了。" }, "masterPassSent": { "message": "我们已经为您发送了包含主密码提示的邮件。" @@ -597,7 +597,7 @@ "message": "请输入您的身份验证器应用中的 6 位验证码。" }, "enterVerificationCodeEmail": { - "message": "请输入通过电子邮件发送给 $EMAIL$ 的 6 位验证码。", + "message": "请输入发送给电子邮件 $EMAIL$ 的 6 位数验证码。", "placeholders": { "email": { "content": "$1", @@ -618,7 +618,7 @@ "message": "记住我" }, "sendVerificationCodeEmailAgain": { - "message": "重发验证码电子邮件" + "message": "再次发送验证码电子邮件" }, "useAnotherTwoStepMethod": { "message": "使用其他两步登录方式" @@ -663,7 +663,7 @@ "message": "使用任何 WebAuthn 兼容的安全钥匙访问您的帐户。" }, "emailTitle": { - "message": "电子邮件地址" + "message": "电子邮件" }, "emailDesc": { "message": "验证码将会发送到您的电子邮箱。" @@ -744,13 +744,13 @@ "message": "注销" }, "addNewLogin": { - "message": "添加新登录" + "message": "新增登录" }, "addNewItem": { "message": "新增项目" }, "addNewFolder": { - "message": "添加文件夹" + "message": "新增文件夹" }, "view": { "message": "查看" @@ -1414,7 +1414,7 @@ "message": "应用程序启动时要求使用触控 ID" }, "requirePasswordOnStart": { - "message": "应用程序启动时要求输入密码或 PIN" + "message": "应用程序启动时要求输入密码或 PIN 码" }, "recommendedForSecurity": { "message": "安全起见,推荐设置。" @@ -1534,7 +1534,7 @@ "message": "设置主密码" }, "ssoCompleteRegistration": { - "message": "要完成 SSO 登陆配置,请设置一个主密码以访问和保护您的密码库。" + "message": "要完成 SSO 登录配置,请设置一个主密码以访问和保护您的密码库。" }, "currentMasterPass": { "message": "当前主密码" @@ -1979,13 +1979,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", @@ -2083,7 +2083,7 @@ "message": "密码库" }, "loginWithMasterPassword": { - "message": "使用主密码登录" + "message": "主密码登录" }, "loggingInAs": { "message": "正登录为" @@ -2244,7 +2244,7 @@ } }, "windowsBiometricUpdateWarning": { - "message": "Bitwarden 建议更新您的生物识别设置,以在首次解锁时要求输入您的主密码(或 PIN)。现在要更新您的设置吗?" + "message": "Bitwarden 建议更新您的生物识别设置,以在首次解锁时要求输入您的主密码(或 PIN 码)。现在要更新您的设置吗?" }, "windowsBiometricUpdateWarningTitle": { "message": "推荐的设置更新" diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 32a511beab1..50ab2e3a3d3 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -2196,10 +2196,10 @@ "message": "登入要求已逾期。" }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." + "message": "此請求已失效" }, "approveLoginRequestDesc": { - "message": "Use this device to approve login requests made from other devices." + "message": "使用此裝置準予來自其他裝置的登入要求。" }, "confirmLoginAtemptForMail": { "message": "確認 $EMAIL$ 的登入嘗試", @@ -2259,19 +2259,19 @@ "message": "Uncheck if using a public device" }, "approveFromYourOtherDevice": { - "message": "Approve from your other device" + "message": "從其他裝置批准" }, "requestAdminApproval": { - "message": "Request admin approval" + "message": "要求管理員核准" }, "approveWithMasterPassword": { - "message": "Approve with master password" + "message": "使用主密碼批准" }, "region": { "message": "區域" }, "ssoIdentifierRequired": { - "message": "Organization SSO identifier is required." + "message": "需要單一登入 (SSO) 組織識別碼。" }, "eu": { "message": "EU", @@ -2287,7 +2287,7 @@ "message": "bitwarden.eu" }, "selfHostedServer": { - "message": "self-hosted" + "message": "自建" }, "accessDenied": { "message": "拒絕存取。您沒有檢視此頁面的權限。" @@ -2314,10 +2314,10 @@ "message": "User email missing" }, "deviceTrusted": { - "message": "Device trusted" + "message": "裝置已信任" }, "inputRequired": { - "message": "Input is required." + "message": "必須輸入內容。" }, "required": { "message": "必填" From b4d91543241f9ca321fdaada16e32b3fcdb5027a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:06:54 -0400 Subject: [PATCH 07/28] Bumped desktop version to 2023.8.5 (#6329) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 6855485510a..92220c7551a 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.4", + "version": "2023.8.5", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index c838b242b50..3af376f862a 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.4", + "version": "2023.8.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2023.8.4", + "version": "2023.8.5", "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 bbef6bf36f5..0e4bff007f4 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.4", + "version": "2023.8.5", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index de0f8a7ee59..640a45b239c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,7 +230,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2023.8.4", + "version": "2023.8.5", "hasInstallScript": true, "license": "GPL-3.0" }, From b6ea1b1f4036fdfba0833c6fc2a9c43a5a945d6c Mon Sep 17 00:00:00 2001 From: Will Martin Date: Mon, 18 Sep 2023 18:57:43 -0400 Subject: [PATCH 08/28] [PM-2415] migrate AboutComponent to CL (#6301) * migrate AboutComponent to CL --- apps/browser/src/popup/app.module.ts | 2 - .../src/popup/settings/about.component.html | 86 +++++++++---------- .../src/popup/settings/about.component.ts | 22 +++-- .../src/popup/settings/settings.component.ts | 2 +- 4 files changed, 54 insertions(+), 58 deletions(-) diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 0115768a40f..32eb7670f3c 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -61,7 +61,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"; @@ -151,7 +150,6 @@ import "../platform/popup/locales"; ViewCustomFieldsComponent, RemovePasswordComponent, VaultSelectComponent, - AboutComponent, HelpAndFeedbackComponent, AutofillComponent, EnvironmentSelectorComponent, 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 @@ - - +
+
- -
diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-users.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-users.component.ts deleted file mode 100644 index af0c344a29e..00000000000 --- a/apps/web/src/app/admin-console/organizations/manage/entity-users.component.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; - -import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service"; -import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/abstractions/organization-user/responses"; -import { - OrganizationUserStatusType, - OrganizationUserType, -} from "@bitwarden/common/admin-console/enums"; -import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -@Component({ - selector: "app-entity-users", - templateUrl: "entity-users.component.html", - providers: [SearchPipe], -}) -export class EntityUsersComponent implements OnInit { - @Input() entity: "group" | "collection"; - @Input() entityId: string; - @Input() entityName: string; - @Input() organizationId: string; - @Output() onEditedUsers = new EventEmitter(); - - organizationUserType = OrganizationUserType; - organizationUserStatusType = OrganizationUserStatusType; - - showSelected = false; - loading = true; - formPromise: Promise; - selectedCount = 0; - searchText: string; - - private allUsers: OrganizationUserUserDetailsResponse[] = []; - - constructor( - private search: SearchPipe, - private apiService: ApiService, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private organizationUserService: OrganizationUserService, - private logService: LogService - ) {} - - async ngOnInit() { - await this.loadUsers(); - this.loading = false; - } - - get users() { - if (this.showSelected) { - return this.allUsers.filter((u) => (u as any).checked); - } else { - return this.allUsers; - } - } - - get searchedUsers() { - return this.search.transform(this.users, this.searchText, "name", "email", "id"); - } - - get scrollViewportStyle() { - return `min-height: 120px; height: ${120 + this.searchedUsers.length * 46}px`; - } - - async loadUsers() { - const users = await this.organizationUserService.getAllUsers(this.organizationId); - this.allUsers = users.data.map((r) => r).sort(Utils.getSortFunction(this.i18nService, "email")); - if (this.entity === "group") { - const response = await this.apiService.getGroupUsers(this.organizationId, this.entityId); - if (response != null && users.data.length > 0) { - response.forEach((s) => { - const user = users.data.filter((u) => u.id === s); - if (user != null && user.length > 0) { - (user[0] as any).checked = true; - } - }); - } - } else if (this.entity === "collection") { - const response = await this.apiService.getCollectionUsers(this.organizationId, this.entityId); - if (response != null && users.data.length > 0) { - response.forEach((s) => { - const user = users.data.filter((u) => !u.accessAll && u.id === s.id); - if (user != null && user.length > 0) { - (user[0] as any).checked = true; - (user[0] as any).readOnly = s.readOnly; - (user[0] as any).hidePasswords = s.hidePasswords; - } - }); - } - } - - this.allUsers.forEach((u) => { - if (this.entity === "collection" && u.accessAll) { - (u as any).checked = true; - } - if ((u as any).checked) { - this.selectedCount++; - } - }); - } - - check(u: OrganizationUserUserDetailsResponse) { - if (this.entity === "collection" && u.accessAll) { - return; - } - (u as any).checked = !(u as any).checked; - this.selectedChanged(u); - } - - selectedChanged(u: OrganizationUserUserDetailsResponse) { - if ((u as any).checked) { - this.selectedCount++; - } else { - if (this.entity === "collection") { - (u as any).readOnly = false; - (u as any).hidePasswords = false; - } - this.selectedCount--; - } - } - - filterSelected(showSelected: boolean) { - this.showSelected = showSelected; - } - - async submit() { - try { - if (this.entity === "group") { - const selections = this.users.filter((u) => (u as any).checked).map((u) => u.id); - this.formPromise = this.apiService.putGroupUsers( - this.organizationId, - this.entityId, - selections - ); - } else { - const selections = this.users - .filter((u) => (u as any).checked && !u.accessAll) - .map( - (u) => - new SelectionReadOnlyRequest(u.id, !!(u as any).readOnly, !!(u as any).hidePasswords) - ); - this.formPromise = this.apiService.putCollectionUsers( - this.organizationId, - this.entityId, - selections - ); - } - await this.formPromise; - this.platformUtilsService.showToast("success", null, this.i18nService.t("updatedUsers")); - this.onEditedUsers.emit(); - } catch (e) { - this.logService.error(e); - } - } -} diff --git a/apps/web/src/app/admin-console/organizations/manage/organization-manage.module.ts b/apps/web/src/app/admin-console/organizations/manage/organization-manage.module.ts deleted file mode 100644 index 9a7b166962d..00000000000 --- a/apps/web/src/app/admin-console/organizations/manage/organization-manage.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ScrollingModule } from "@angular/cdk/scrolling"; -import { NgModule } from "@angular/core"; - -import { EntityUsersComponent } from "../../../admin-console/organizations/manage/entity-users.component"; -import { SharedModule } from "../../../shared"; - -@NgModule({ - imports: [SharedModule, ScrollingModule], - declarations: [EntityUsersComponent], - exports: [EntityUsersComponent], -}) -export class OrganizationManageModule {} diff --git a/apps/web/src/app/oss.module.ts b/apps/web/src/app/oss.module.ts index 1428aaea193..d15addba3ef 100644 --- a/apps/web/src/app/oss.module.ts +++ b/apps/web/src/app/oss.module.ts @@ -1,7 +1,6 @@ import { NgModule } from "@angular/core"; import { OrganizationCreateModule } from "./admin-console/organizations/create/organization-create.module"; -import { OrganizationManageModule } from "./admin-console/organizations/manage/organization-manage.module"; import { OrganizationUserModule } from "./admin-console/organizations/users/organization-user.module"; import { LoginModule } from "./auth/login/login.module"; import { TrialInitiationModule } from "./auth/trial-initiation/trial-initiation.module"; @@ -16,7 +15,6 @@ import { VaultFilterModule } from "./vault/individual-vault/vault-filter/vault-f TrialInitiationModule, VaultFilterModule, OrganizationBadgeModule, - OrganizationManageModule, OrganizationUserModule, OrganizationCreateModule, LoginModule, diff --git a/apps/web/src/scss/styles.scss b/apps/web/src/scss/styles.scss index 2c0f9c21fd6..05cd9fef4eb 100644 --- a/apps/web/src/scss/styles.scss +++ b/apps/web/src/scss/styles.scss @@ -20,7 +20,7 @@ @import "~bootstrap/scss/_buttons"; @import "~bootstrap/scss/_transitions"; @import "~bootstrap/scss/_dropdown"; -@import "~bootstrap/scss/_button-group"; +// @import "~bootstrap/scss/_button-group"; @import "~bootstrap/scss/_input-group"; // @import "~bootstrap/scss/_custom-forms"; @import "~bootstrap/scss/_nav"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.html index 152253fe4d1..90160daeadb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/people.component.html @@ -1,48 +1,37 @@
-
-
- - {{ "updateKeyTitle" | i18n }} -
-
-

{{ "updateEncryptionKeyShortDesc" | i18n }}

- -
-
- ; deletePromises: { [id: string]: Promise } = {}; @@ -50,15 +49,6 @@ export class AttachmentsComponent implements OnInit { } async submit() { - if (!this.hasUpdatedKey) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("updateKey") - ); - return; - } - const fileEl = document.getElementById("file") as HTMLInputElement; const files = fileEl.files; if (files == null || files.length === 0) { @@ -191,7 +181,6 @@ export class AttachmentsComponent implements OnInit { this.cipherDomain = await this.loadCipher(); this.cipher = await this.cipherDomain.decrypt(); - this.hasUpdatedKey = await this.cryptoService.hasUserKey(); const canAccessPremium = await this.stateService.getCanAccessPremium(); this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null; @@ -206,19 +195,6 @@ export class AttachmentsComponent implements OnInit { if (confirmed) { this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase"); } - } else if (!this.hasUpdatedKey) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "featureUnavailable" }, - content: { key: "updateKey" }, - acceptButtonText: { key: "learnMore" }, - type: "warning", - }); - - if (confirmed) { - this.platformUtilsService.launchUri( - "https://bitwarden.com/help/account-encryption-key/#rotate-your-encryption-key" - ); - } } } diff --git a/libs/common/src/auth/login-strategies/login.strategy.ts b/libs/common/src/auth/login-strategies/login.strategy.ts index 96855a34106..d8b0f5ca89d 100644 --- a/libs/common/src/auth/login-strategies/login.strategy.ts +++ b/libs/common/src/auth/login-strategies/login.strategy.ts @@ -1,4 +1,5 @@ import { ApiService } from "../../abstractions/api.service"; +import { ClientType } from "../../enums"; import { KeysRequest } from "../../models/request/keys.request"; import { AppIdService } from "../../platform/abstractions/app-id.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; @@ -151,6 +152,16 @@ export abstract class LogInStrategy { protected async processTokenResponse(response: IdentityTokenResponse): Promise { const result = new AuthResult(); + + // Old encryption keys must be migrated, but is currently only available on web. + // Other clients shouldn't continue the login process. + if (this.encryptionKeyMigrationRequired(response)) { + result.requiresEncryptionKeyMigration = true; + if (this.platformUtilsService.getClientType() !== ClientType.Web) { + return result; + } + } + result.resetMasterPassword = response.resetMasterPassword; // Convert boolean to enum @@ -166,9 +177,7 @@ export abstract class LogInStrategy { } await this.setMasterKey(response); - await this.setUserKey(response); - await this.setPrivateKey(response); this.messagingService.send("loggedIn"); @@ -183,6 +192,12 @@ export abstract class LogInStrategy { protected abstract setPrivateKey(response: IdentityTokenResponse): Promise; + // Old accounts used master key for encryption. We are forcing migrations but only need to + // check on password logins + protected encryptionKeyMigrationRequired(response: IdentityTokenResponse): boolean { + return false; + } + protected async createKeyPairForOldAccount() { try { const [publicKey, privateKey] = await this.cryptoService.makeKeyPair(); diff --git a/libs/common/src/auth/login-strategies/password-login.strategy.ts b/libs/common/src/auth/login-strategies/password-login.strategy.ts index 7f7ec585699..0bcc679ae9a 100644 --- a/libs/common/src/auth/login-strategies/password-login.strategy.ts +++ b/libs/common/src/auth/login-strategies/password-login.strategy.ts @@ -147,6 +147,10 @@ export class PasswordLogInStrategy extends LogInStrategy { } protected override async setUserKey(response: IdentityTokenResponse): Promise { + // If migration is required, we won't have a user key to set yet. + if (this.encryptionKeyMigrationRequired(response)) { + return; + } await this.cryptoService.setMasterKeyEncryptedUserKey(response.key); const masterKey = await this.cryptoService.getMasterKey(); @@ -162,6 +166,10 @@ export class PasswordLogInStrategy extends LogInStrategy { ); } + protected override encryptionKeyMigrationRequired(response: IdentityTokenResponse): boolean { + return !response.key; + } + private getMasterPasswordPolicyOptionsFromResponse( response: IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse ): MasterPasswordPolicyOptions { diff --git a/libs/common/src/auth/models/domain/auth-result.ts b/libs/common/src/auth/models/domain/auth-result.ts index c0a6f034aef..6900cba1c48 100644 --- a/libs/common/src/auth/models/domain/auth-result.ts +++ b/libs/common/src/auth/models/domain/auth-result.ts @@ -17,6 +17,7 @@ export class AuthResult { twoFactorProviders: Map = null; ssoEmail2FaSessionToken?: string; email: string; + requiresEncryptionKeyMigration: boolean; get requiresCaptcha() { return !Utils.isNullOrWhitespace(this.captchaSiteKey); diff --git a/libs/common/src/platform/abstractions/crypto.service.ts b/libs/common/src/platform/abstractions/crypto.service.ts index 42f60bde845..a868484bd04 100644 --- a/libs/common/src/platform/abstractions/crypto.service.ts +++ b/libs/common/src/platform/abstractions/crypto.service.ts @@ -42,6 +42,12 @@ export abstract class CryptoService { * @returns The user key */ getUserKey: (userId?: string) => Promise; + + /** + * Checks if the user is using an old encryption scheme that used the master key + * for encryption of data instead of the user key. + */ + isLegacyUser: (masterKey?: MasterKey, userId?: string) => Promise; /** * Use for encryption/decryption of data in order to support legacy * encryption models. It will return the user key if available, diff --git a/libs/common/src/platform/services/crypto.service.ts b/libs/common/src/platform/services/crypto.service.ts index 1f5cd10edc4..3b4090ef344 100644 --- a/libs/common/src/platform/services/crypto.service.ts +++ b/libs/common/src/platform/services/crypto.service.ts @@ -77,6 +77,12 @@ export class CryptoService implements CryptoServiceAbstraction { } } + async isLegacyUser(masterKey?: MasterKey, userId?: string): Promise { + return await this.validateUserKey( + (masterKey ?? (await this.getMasterKey(userId))) as unknown as UserKey + ); + } + async getUserKeyWithLegacySupport(userId?: string): Promise { const userKey = await this.getUserKey(userId); if (userKey) { @@ -510,7 +516,8 @@ export class CryptoService implements CryptoServiceAbstraction { } async makeKeyPair(key?: SymmetricCryptoKey): Promise<[string, EncString]> { - key ||= await this.getUserKey(); + // Default to user key + key ||= await this.getUserKeyWithLegacySupport(); const keyPair = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); const publicB64 = Utils.fromBufferToB64(keyPair[0]); @@ -943,23 +950,30 @@ export class CryptoService implements CryptoServiceAbstraction { async migrateAutoKeyIfNeeded(userId?: string) { const oldAutoKey = await this.stateService.getCryptoMasterKeyAuto({ userId: userId }); - if (oldAutoKey) { - // decrypt - const masterKey = new SymmetricCryptoKey(Utils.fromB64ToArray(oldAutoKey)) as MasterKey; - const encryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({ - userId: userId, - }); - const userKey = await this.decryptUserKeyWithMasterKey( - masterKey, - new EncString(encryptedUserKey), - userId - ); - // migrate - await this.stateService.setUserKeyAutoUnlock(userKey.keyB64, { userId: userId }); - await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); - // set encrypted user key in case user immediately locks without syncing - await this.setMasterKeyEncryptedUserKey(encryptedUserKey); + if (!oldAutoKey) { + return; } + // Decrypt + const masterKey = new SymmetricCryptoKey(Utils.fromB64ToArray(oldAutoKey)) as MasterKey; + if (await this.isLegacyUser(masterKey, userId)) { + // Legacy users don't have a user key, so no need to migrate. + // Instead, set the master key for additional isLegacyUser checks that will log the user out. + await this.setMasterKey(masterKey, userId); + return; + } + const encryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({ + userId: userId, + }); + const userKey = await this.decryptUserKeyWithMasterKey( + masterKey, + new EncString(encryptedUserKey), + userId + ); + // Migrate + await this.stateService.setUserKeyAutoUnlock(userKey.keyB64, { userId: userId }); + await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); + // Set encrypted user key in case user immediately locks without syncing + await this.setMasterKeyEncryptedUserKey(encryptedUserKey); } async decryptAndMigrateOldPinKey( diff --git a/libs/common/src/services/vault-timeout/vault-timeout.service.ts b/libs/common/src/services/vault-timeout/vault-timeout.service.ts index 0fbbf51bd60..9e5a78834f7 100644 --- a/libs/common/src/services/vault-timeout/vault-timeout.service.ts +++ b/libs/common/src/services/vault-timeout/vault-timeout.service.ts @@ -5,6 +5,7 @@ import { VaultTimeoutSettingsService } from "../../abstractions/vault-timeout/va import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout.service"; import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; +import { ClientType } from "../../enums"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; @@ -141,10 +142,18 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { } private async migrateKeyForNeverLockIfNeeded(): Promise { + // Web can't set vault timeout to never + if (this.platformUtilsService.getClientType() == ClientType.Web) { + return; + } const accounts = await firstValueFrom(this.stateService.accounts$); for (const userId in accounts) { if (userId != null) { await this.cryptoService.migrateAutoKeyIfNeeded(userId); + // Legacy users should be logged out since we're not on the web vault and can't migrate. + if (await this.cryptoService.isLegacyUser(null, userId)) { + await this.logOut(userId); + } } } } diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 0bae9a607a2..f03cb88e6e4 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -329,11 +329,6 @@ export class CipherService implements CipherServiceAbstraction { return await this.getDecryptedCipherCache(); } - const hasKey = await this.cryptoService.hasUserKey(); - if (!hasKey) { - throw new Error("No user key found."); - } - const ciphers = await this.getAll(); const orgKeys = await this.cryptoService.getOrgKeys(); const userKey = await this.cryptoService.getUserKeyWithLegacySupport(); From 014b32b488dcab3cbba813961e014fff37a05dd6 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 20 Sep 2023 16:09:32 -0400 Subject: [PATCH 23/28] code cleanup (#6238) --- libs/common/src/vault/services/cipher.service.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index f03cb88e6e4..03c70cca84e 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -398,14 +398,21 @@ export class CipherService implements CipherServiceAbstraction { defaultMatch ??= await this.stateService.getDefaultUriMatch(); return ciphers.filter((cipher) => { - if (cipher.deletedDate != null) { + const cipherIsLogin = cipher.type === CipherType.Login && cipher.login !== null; + + if (cipher.deletedDate !== null) { return false; } - if (includeOtherTypes != null && includeOtherTypes.indexOf(cipher.type) > -1) { + + if ( + Array.isArray(includeOtherTypes) && + includeOtherTypes.includes(cipher.type) && + !cipherIsLogin + ) { return true; } - if (cipher.type === CipherType.Login && cipher.login !== null) { + if (cipherIsLogin) { return cipher.login.matchesUri(url, equivalentDomains, defaultMatch); } From 5346025c77e323caf52452e75f8c30c0ca924ddc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 18:36:17 -0400 Subject: [PATCH 24/28] Bumped desktop version to 2023.9.1 (#6356) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 8103f20311c..719e680dd22 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.9.0", + "version": "2023.9.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index b1b465d26a3..fc1f62db020 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2023.9.0", + "version": "2023.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2023.9.0", + "version": "2023.9.1", "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 77e29a988b1..eca9f715417 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.9.0", + "version": "2023.9.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index c1f654797c5..96e308fb6ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,7 +230,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2023.9.0", + "version": "2023.9.1", "hasInstallScript": true, "license": "GPL-3.0" }, From 5d14afb97f38cc892da2885f3d43232b21714807 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Wed, 20 Sep 2023 18:47:28 -0400 Subject: [PATCH 25/28] [CL-130] fix select styles on desktop & browser --- apps/browser/src/popup/scss/popup.scss | 1 + apps/desktop/src/scss/styles.scss | 1 + 2 files changed, 2 insertions(+) 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/desktop/src/scss/styles.scss b/apps/desktop/src/scss/styles.scss index 049cf10b974..033a0f8b674 100644 --- a/apps/desktop/src/scss/styles.scss +++ b/apps/desktop/src/scss/styles.scss @@ -17,3 +17,4 @@ @import "left-nav.scss"; @import "loading.scss"; @import "../../../../libs/angular/src/scss/icons.scss"; +@import "../../../../libs/components/src/multi-select/scss/bw.theme"; From 5b69d52f027ccc2f86d0ef33288194080df1fa43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 21 Sep 2023 07:04:17 -0600 Subject: [PATCH 26/28] Ensure chrome.storage listeners also get cleaned up in Safari to avoid memory leak (#6354) --- .../src/platform/browser/browser-api.ts | 40 ++++++++++++++----- .../services/browser-state.service.ts | 3 +- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index 5a5596a795a..b71b8b80b6c 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -199,7 +199,10 @@ export class BrowserApi { BrowserApi.removeTab(tabToClose.id); } + // 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, @@ -207,21 +210,38 @@ export class BrowserApi { ) { chrome.runtime.onMessage.addListener(callback); - // 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 sendMessage(subscriber: string, arg: any = {}) { const message = Object.assign({}, { command: subscriber }, arg); return chrome.runtime.sendMessage(message); 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]) { From 02af0fed4c23a0327a12b2d1c9441144d0eeb170 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Thu, 21 Sep 2023 10:23:20 -0400 Subject: [PATCH 27/28] [CL-113] add readonly styles to bitInput (#6220) * add readonly styles and story * only add style to input & textarea --- .../src/form-field/form-field.stories.ts | 18 ++++++++++++++++++ libs/components/src/input/input.directive.ts | 1 + 2 files changed, 19 insertions(+) diff --git a/libs/components/src/form-field/form-field.stories.ts b/libs/components/src/form-field/form-field.stories.ts index 4c85a18607f..305b514266e 100644 --- a/libs/components/src/form-field/form-field.stories.ts +++ b/libs/components/src/form-field/form-field.stories.ts @@ -157,6 +157,24 @@ export const Disabled: Story = { args: {}, }; +export const Readonly: Story = { + render: (args) => ({ + props: args, + template: ` + + Input + + + + + Textarea + + + `, + }), + args: {}, +}; + export const InputGroup: Story = { render: (args) => ({ props: args, diff --git a/libs/components/src/input/input.directive.ts b/libs/components/src/input/input.directive.ts index 60589208d52..b9f71ff8d59 100644 --- a/libs/components/src/input/input.directive.ts +++ b/libs/components/src/input/input.directive.ts @@ -44,6 +44,7 @@ export class BitInputDirective implements BitFormFieldControl { "focus:tw-ring-primary-700", "focus:tw-z-10", "disabled:tw-bg-secondary-100", + "[&:is(input,textarea):read-only]:tw-bg-secondary-100", ].filter((s) => s != ""); } From 217e081859d3b7a3f08b04ad9d36f8e7979ecad9 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Thu, 21 Sep 2023 08:37:52 -0700 Subject: [PATCH 28/28] Hide generator type radio options when the generator is opened from an add/edit page (#6240) --- .../browser/src/tools/popup/generator/generator.component.html | 3 +-- apps/desktop/src/app/tools/generator.component.html | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/tools/popup/generator/generator.component.html b/apps/browser/src/tools/popup/generator/generator.component.html index 5c9c7492014..13d35b6cd7a 100644 --- a/apps/browser/src/tools/popup/generator/generator.component.html +++ b/apps/browser/src/tools/popup/generator/generator.component.html @@ -75,7 +75,7 @@
-
+
-
+