From 0e3e3c16c4567cc08e065147e6b43512d3cee389 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Mon, 27 Jan 2025 09:35:04 -0500 Subject: [PATCH 01/13] Removed unnecessary CODECOV_TOKEN with updated codecov-action (#12892) --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72bc3594beb..6b297e9344f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,10 +85,7 @@ jobs: fail-on-error: true - name: Upload coverage to codecov.io - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 - if: ${{ needs.check-test-secrets.outputs.available == 'true' }} - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 - name: Upload results to codecov.io uses: codecov/test-results-action@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.1 From 9d987a2513730d3a21405c706fa1e0041c6d6f20 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:11:42 +0100 Subject: [PATCH 02/13] PM-16220: Account does not exist during login race condition (#12488) Wait for an account to become available from separate observable, instead of blindly accepting that the value is there using `firstValueFrom`, while it's sometimes not there immediately. --- libs/common/package.json | 3 +- .../src/auth/services/account.service.spec.ts | 31 ++++++++++++++++++ .../src/auth/services/account.service.ts | 32 ++++++++++++------- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/libs/common/package.json b/libs/common/package.json index 5e0f5ae20c6..ad2771e2fff 100644 --- a/libs/common/package.json +++ b/libs/common/package.json @@ -15,6 +15,7 @@ "scripts": { "clean": "rimraf dist", "build": "npm run clean && tsc", - "build:watch": "npm run clean && tsc -watch" + "build:watch": "npm run clean && tsc -watch", + "test": "jest" } } diff --git a/libs/common/src/auth/services/account.service.spec.ts b/libs/common/src/auth/services/account.service.spec.ts index 227949156ee..2028a7e1fc4 100644 --- a/libs/common/src/auth/services/account.service.spec.ts +++ b/libs/common/src/auth/services/account.service.spec.ts @@ -69,6 +69,7 @@ describe("accountService", () => { let sut: AccountServiceImplementation; let accountsState: FakeGlobalState>; let activeAccountIdState: FakeGlobalState; + let accountActivityState: FakeGlobalState>; const userId = Utils.newGuid() as UserId; const userInfo = { email: "email", name: "name", emailVerified: true }; @@ -81,6 +82,7 @@ describe("accountService", () => { accountsState = globalStateProvider.getFake(ACCOUNT_ACCOUNTS); activeAccountIdState = globalStateProvider.getFake(ACCOUNT_ACTIVE_ACCOUNT_ID); + accountActivityState = globalStateProvider.getFake(ACCOUNT_ACTIVITY); }); afterEach(() => { @@ -256,6 +258,7 @@ describe("accountService", () => { beforeEach(() => { accountsState.stateSubject.next({ [userId]: userInfo }); activeAccountIdState.stateSubject.next(userId); + accountActivityState.stateSubject.next({ [userId]: new Date(1) }); }); it("should emit null if no account is provided", async () => { @@ -269,6 +272,34 @@ describe("accountService", () => { // eslint-disable-next-line @typescript-eslint/no-floating-promises expect(sut.switchAccount("unknown" as UserId)).rejects.toThrowError("Account does not exist"); }); + + it("should change active account when switched to the new account", async () => { + const newUserId = Utils.newGuid() as UserId; + accountsState.stateSubject.next({ [newUserId]: userInfo }); + + await sut.switchAccount(newUserId); + + await expect(firstValueFrom(sut.activeAccount$)).resolves.toEqual({ + id: newUserId, + ...userInfo, + }); + await expect(firstValueFrom(sut.accountActivity$)).resolves.toEqual({ + [userId]: new Date(1), + [newUserId]: expect.toAlmostEqual(new Date(), 1000), + }); + }); + + it("should not change active account when already switched to the same account", async () => { + await sut.switchAccount(userId); + + await expect(firstValueFrom(sut.activeAccount$)).resolves.toEqual({ + id: userId, + ...userInfo, + }); + await expect(firstValueFrom(sut.accountActivity$)).resolves.toEqual({ + [userId]: new Date(1), + }); + }); }); describe("account activity", () => { diff --git a/libs/common/src/auth/services/account.service.ts b/libs/common/src/auth/services/account.service.ts index d4479815c5d..673d88382fb 100644 --- a/libs/common/src/auth/services/account.service.ts +++ b/libs/common/src/auth/services/account.service.ts @@ -7,6 +7,9 @@ import { shareReplay, combineLatest, Observable, + filter, + timeout, + of, } from "rxjs"; import { @@ -149,21 +152,28 @@ export class AccountServiceImplementation implements InternalAccountService { async switchAccount(userId: UserId | null): Promise { let updateActivity = false; await this.activeAccountIdState.update( - (_, accounts) => { - if (userId == null) { - // indicates no account is active - return null; - } - - if (accounts?.[userId] == null) { - throw new Error("Account does not exist"); - } + (_, __) => { updateActivity = true; return userId; }, { - combineLatestWith: this.accounts$, - shouldUpdate: (id) => { + combineLatestWith: this.accountsState.state$.pipe( + filter((accounts) => { + if (userId == null) { + // Don't worry about accounts when we are about to set active user to null + return true; + } + + return accounts?.[userId] != null; + }), + // If we don't get the desired account with enough time, just return empty as that will result in the same error + timeout({ first: 1000, with: () => of({} as Record) }), + ), + shouldUpdate: (id, accounts) => { + if (userId != null && accounts?.[userId] == null) { + throw new Error("Account does not exist"); + } + // update only if userId changes return id !== userId; }, From 682e62cb6bcc296cb19536bdd80c23172fda3a46 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:12:20 +0100 Subject: [PATCH 03/13] [PM-16485] Remove deprecated and unused PasswordGenerationService (#13053) * Remove deprecated and unused PasswordGenerationService * Remove unused state-service --------- Co-authored-by: Daniel James Smith --- apps/browser/src/auth/popup/register.component.ts | 6 ------ apps/desktop/src/auth/register.component.ts | 6 ------ .../src/app/auth/register-form/register-form.component.ts | 6 ------ libs/angular/src/auth/components/register.component.ts | 4 ---- 4 files changed, 22 deletions(-) diff --git a/apps/browser/src/auth/popup/register.component.ts b/apps/browser/src/auth/popup/register.component.ts index f8a332f0fd1..50475b2204d 100644 --- a/apps/browser/src/auth/popup/register.component.ts +++ b/apps/browser/src/auth/popup/register.component.ts @@ -13,9 +13,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi 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 { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KeyService } from "@bitwarden/key-management"; @Component({ @@ -34,9 +32,7 @@ export class RegisterComponent extends BaseRegisterComponent { i18nService: I18nService, keyService: KeyService, apiService: ApiService, - stateService: StateService, platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, environmentService: EnvironmentService, logService: LogService, auditService: AuditService, @@ -51,9 +47,7 @@ export class RegisterComponent extends BaseRegisterComponent { i18nService, keyService, apiService, - stateService, platformUtilsService, - passwordGenerationService, environmentService, logService, auditService, diff --git a/apps/desktop/src/auth/register.component.ts b/apps/desktop/src/auth/register.component.ts index f3df5b88476..ee3510b0f58 100644 --- a/apps/desktop/src/auth/register.component.ts +++ b/apps/desktop/src/auth/register.component.ts @@ -12,9 +12,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi 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 { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KeyService } from "@bitwarden/key-management"; const BroadcasterSubscriptionId = "RegisterComponent"; @@ -32,9 +30,7 @@ export class RegisterComponent extends BaseRegisterComponent implements OnInit, i18nService: I18nService, keyService: KeyService, apiService: ApiService, - stateService: StateService, platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, environmentService: EnvironmentService, private broadcasterService: BroadcasterService, private ngZone: NgZone, @@ -51,9 +47,7 @@ export class RegisterComponent extends BaseRegisterComponent implements OnInit, i18nService, keyService, apiService, - stateService, platformUtilsService, - passwordGenerationService, environmentService, logService, auditService, diff --git a/apps/web/src/app/auth/register-form/register-form.component.ts b/apps/web/src/app/auth/register-form/register-form.component.ts index 7d3e6dbd00e..e8c9f0291a5 100644 --- a/apps/web/src/app/auth/register-form/register-form.component.ts +++ b/apps/web/src/app/auth/register-form/register-form.component.ts @@ -17,9 +17,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi 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 { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KeyService } from "@bitwarden/key-management"; import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; @@ -45,9 +43,7 @@ export class RegisterFormComponent extends BaseRegisterComponent implements OnIn i18nService: I18nService, keyService: KeyService, apiService: ApiService, - stateService: StateService, platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, private policyService: PolicyService, environmentService: EnvironmentService, logService: LogService, @@ -64,9 +60,7 @@ export class RegisterFormComponent extends BaseRegisterComponent implements OnIn i18nService, keyService, apiService, - stateService, platformUtilsService, - passwordGenerationService, environmentService, logService, auditService, diff --git a/libs/angular/src/auth/components/register.component.ts b/libs/angular/src/auth/components/register.component.ts index 279294f4c06..e4787aa8c01 100644 --- a/libs/angular/src/auth/components/register.component.ts +++ b/libs/angular/src/auth/components/register.component.ts @@ -15,10 +15,8 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi 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 { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; import { @@ -91,9 +89,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn i18nService: I18nService, protected keyService: KeyService, protected apiService: ApiService, - protected stateService: StateService, platformUtilsService: PlatformUtilsService, - protected passwordGenerationService: PasswordGenerationServiceAbstraction, environmentService: EnvironmentService, protected logService: LogService, protected auditService: AuditService, From 8577aae879780bb103d3390f045d905f983ac7c8 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 27 Jan 2025 07:12:58 -0800 Subject: [PATCH 04/13] Test existing Login URI icon retrieval behavior (#12845) --- .../src/vault/icon/build-cipher-icon.spec.ts | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 libs/common/src/vault/icon/build-cipher-icon.spec.ts diff --git a/libs/common/src/vault/icon/build-cipher-icon.spec.ts b/libs/common/src/vault/icon/build-cipher-icon.spec.ts new file mode 100644 index 00000000000..8de65390bf7 --- /dev/null +++ b/libs/common/src/vault/icon/build-cipher-icon.spec.ts @@ -0,0 +1,141 @@ +import { CipherType } from "../enums"; +import { CipherView } from "../models/view/cipher.view"; + +import { buildCipherIcon } from "./build-cipher-icon"; + +describe("buildCipherIcon", () => { + const iconServerUrl = "https://icons.example"; + describe("Login cipher", () => { + const cipher = { + type: CipherType.Login, + login: { + uri: "https://test.example", + }, + } as any as CipherView; + + it.each([true, false])("handles android app URIs for showFavicon setting %s", (showFavicon) => { + setUri("androidapp://test.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, showFavicon); + + expect(iconDetails).toEqual({ + icon: "bwi-android", + image: null, + fallbackImage: "", + imageEnabled: showFavicon, + }); + }); + + it("does not mark as an android app if the protocol is not androidapp", () => { + // This weird URI points to test.androidapp with a default port and path of /.example + setUri("https://test.androidapp://.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: "https://icons.example/test.androidapp/icon.png", + fallbackImage: "images/bwi-globe.png", + imageEnabled: true, + }); + }); + + it.each([true, false])("handles ios app URIs for showFavicon setting %s", (showFavicon) => { + setUri("iosapp://test.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, showFavicon); + + expect(iconDetails).toEqual({ + icon: "bwi-apple", + image: null, + fallbackImage: "", + imageEnabled: showFavicon, + }); + }); + + it("does not mark as an ios app if the protocol is not iosapp", () => { + // This weird URI points to test.iosapp with a default port and path of /.example + setUri("https://test.iosapp://.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: "https://icons.example/test.iosapp/icon.png", + fallbackImage: "images/bwi-globe.png", + imageEnabled: true, + }); + }); + + const testUris = ["test.example", "https://test.example"]; + + it.each(testUris)("resolves favicon for %s", (uri) => { + setUri(uri); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: "https://icons.example/test.example/icon.png", + fallbackImage: "images/bwi-globe.png", + imageEnabled: true, + }); + }); + + it.each(testUris)("does not resolve favicon for %s if showFavicon is false", () => { + setUri("https://test.example"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, false); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: undefined, + fallbackImage: "", + imageEnabled: false, + }); + }); + + it("does not resolve a favicon if the URI is missing a `.`", () => { + setUri("test"); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: undefined, + fallbackImage: "", + imageEnabled: true, + }); + }); + + it.each(["test.onion", "test.i2p"])("does not resolve a favicon for %s", (uri) => { + setUri(`https://${uri}`); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: null, + fallbackImage: "images/bwi-globe.png", + imageEnabled: true, + }); + }); + + it.each([null, undefined])("does not resolve a favicon if there is no uri", (nullish) => { + setUri(nullish as any as string); + + const iconDetails = buildCipherIcon(iconServerUrl, cipher, true); + + expect(iconDetails).toEqual({ + icon: "bwi-globe", + image: null, + fallbackImage: "", + imageEnabled: true, + }); + }); + + function setUri(uri: string) { + (cipher.login as { uri: string }).uri = uri; + } + }); +}); From 9fe84c35d2c78e2885fbf58a9ab0873140c53a1e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 27 Jan 2025 16:19:38 +0100 Subject: [PATCH 05/13] Group linting dependencies (#13049) * Group linting dependencies * Update renovate.json --- .github/renovate.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/renovate.json b/.github/renovate.json index b5c43cc1d39..56b59a2af46 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -92,6 +92,28 @@ "commitMessagePrefix": "[deps] Architecture:", "reviewers": ["team:dept-architecture"] }, + { + "matchPackageNames": [ + "@angular-eslint/eslint-plugin-template", + "@angular-eslint/eslint-plugin", + "@angular-eslint/schematics", + "@angular-eslint/template-parser", + "@typescript-eslint/eslint-plugin", + "@typescript-eslint/parser", + "eslint-config-prettier", + "eslint-import-resolver-typescript", + "eslint-plugin-import", + "eslint-plugin-rxjs-angular", + "eslint-plugin-rxjs", + "eslint-plugin-storybook", + "eslint-plugin-tailwindcss", + "eslint", + "husky", + "lint-staged" + ], + "groupName": "Linting minor-patch", + "matchUpdateTypes": ["minor", "patch"] + }, { "matchPackageNames": [ "@emotion/css", From c3bb76bee061bdcc20b841a4c644f0e6d85f9e9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:12:12 -0500 Subject: [PATCH 06/13] [deps] Architecture: Update eslint-plugin-tailwindcss to v3.18.0 (#12966) * [deps] Architecture: Update eslint-plugin-tailwindcss to v3.18.0 * Fix linting --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- .../popup/fido2/fido2-use-browser-link.component.html | 2 +- .../src/platform/popup/layout/popup-page.component.html | 4 ++-- .../vault-v2/vault-header/vault-header-v2.component.html | 2 +- .../integration-card/integration-card.component.html | 4 ++-- .../settings/account/change-avatar-dialog.component.html | 4 ++-- .../settings/security/device-management.component.html | 4 ++-- .../trial-initiation/trial-billing-step.component.html | 4 ++-- libs/angular/src/vault/components/icon.component.html | 2 +- libs/components/src/dialog/dialog/dialog.component.html | 5 +---- libs/components/src/form-field/form-field.component.html | 4 ++-- libs/components/src/layout/layout.component.html | 2 +- .../totp-countdown/totp-countdown.component.html | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 14 files changed, 23 insertions(+), 26 deletions(-) diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.html b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.html index 9f6c0aca50d..45c0612af44 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.html +++ b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.html @@ -47,6 +47,6 @@
diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.html b/apps/browser/src/platform/popup/layout/popup-page.component.html index ba9a108a504..94f0846a852 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.html +++ b/apps/browser/src/platform/popup/layout/popup-page.component.html @@ -13,13 +13,13 @@
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html index 5f958433c6d..91feaa433a9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.html @@ -20,7 +20,7 @@

{{ numberOfAppliedFilters$ | async }} diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.html b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.html index 4801c392976..e96fbef270c 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.html @@ -7,7 +7,7 @@
-
+ diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html index 5bcde6a697a..361b2d9a3a3 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html @@ -19,7 +19,7 @@

{{ "billingPlanLabel" | i18n }