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 01/15] 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 5e0f5ae20c..ad2771e2ff 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 227949156e..2028a7e1fc 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 d4479815c5..673d88382f 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 02/15] [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 f8a332f0fd..50475b2204 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 f3df5b8847..ee3510b0f5 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 7d3e6dbd00..e8c9f0291a 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 279294f4c0..e4787aa8c0 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 03/15] 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 0000000000..8de65390bf --- /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 04/15] 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 b5c43cc1d3..56b59a2af4 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 05/15] [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 9f6c0aca50..45c0612af4 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 ba9a108a50..94f0846a85 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 5f958433c6..91feaa433a 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 4801c39297..e96fbef270 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 5bcde6a697..361b2d9a3a 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 @@
+
-
-
+
+
diff --git a/libs/vault/src/components/totp-countdown/totp-countdown.component.html b/libs/vault/src/components/totp-countdown/totp-countdown.component.html index 7de19ac3ed..5c535a9e27 100644 --- a/libs/vault/src/components/totp-countdown/totp-countdown.component.html +++ b/libs/vault/src/components/totp-countdown/totp-countdown.component.html @@ -6,7 +6,7 @@ bitTypography="helper" >{{ totpSec }} - + Date: Mon, 27 Jan 2025 17:34:27 +0100 Subject: [PATCH 06/15] Fix linting (#13088) --- .../carousel/carousel-button/carousel-button.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/vault/src/components/carousel/carousel-button/carousel-button.component.html b/libs/vault/src/components/carousel/carousel-button/carousel-button.component.html index 824b16bb3a..7af120cfd6 100644 --- a/libs/vault/src/components/carousel/carousel-button/carousel-button.component.html +++ b/libs/vault/src/components/carousel/carousel-button/carousel-button.component.html @@ -2,7 +2,7 @@ #btn type="button" role="tab" - class="tw-h-6 tw-w-6 tw-p-0 tw-flex tw-items-center tw-justify-center tw-border-2 tw-border-solid tw-rounded-full tw-transition tw-bg-transparent tw-border-transparent focus-visible:tw-outline-none focus-visible:tw-border-primary-600" + class="tw-size-6 tw-p-0 tw-flex tw-items-center tw-justify-center tw-border-2 tw-border-solid tw-rounded-full tw-transition tw-bg-transparent tw-border-transparent focus-visible:tw-outline-none focus-visible:tw-border-primary-600" [ngClass]="dynamicClasses" [attr.aria-selected]="isActive" [attr.tabindex]="isActive ? 0 : -1" From da422fd1bbd496ded74f1202586c576536f5c50a Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Mon, 27 Jan 2025 13:17:06 -0500 Subject: [PATCH 07/15] remove getUserId operator (#13091) --- libs/common/src/platform/sync/core-sync.service.ts | 3 +-- libs/common/src/platform/sync/default-sync.service.ts | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index 48f4b2b64f..cfa9030c9d 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -8,7 +8,6 @@ import { ApiService } from "../../abstractions/api.service"; import { AccountService } from "../../auth/abstractions/account.service"; import { AuthService } from "../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; -import { getUserId } from "../../auth/services/account.service"; import { SyncCipherNotification, SyncFolderNotification, @@ -59,7 +58,7 @@ export abstract class CoreSyncService implements SyncService { abstract fullSync(forceSync: boolean, allowThrowOnError?: boolean): Promise; async getLastSync(): Promise { - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); if (userId == null) { return null; } diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index 7acd7dd8c7..138c7c0331 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { CollectionService, @@ -34,7 +34,6 @@ import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstraction import { TokenService } from "../../auth/abstractions/token.service"; import { AuthenticationStatus } from "../../auth/enums/authentication-status"; import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason"; -import { getUserId } from "../../auth/services/account.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; import { BillingAccountProfileStateService } from "../../billing/abstractions"; import { DomainsResponse } from "../../models/response/domains.response"; @@ -108,7 +107,7 @@ export class DefaultSyncService extends CoreSyncService { @sequentialize(() => "fullSync") override async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); this.syncStarted(); const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); if (authStatus === AuthenticationStatus.LoggedOut) { From 582beaf70662a7708e3545d50b43876b02a884bc Mon Sep 17 00:00:00 2001 From: albertboyd5 Date: Mon, 27 Jan 2025 15:25:40 -0600 Subject: [PATCH 08/15] [PM-13404] Weak Passwords Report - Sort by password weakness (#12359) * [PM-13404] Weak Passwords Report - Sort by password weakness * [PM-13404] Weak Passwords Report lint error fix * Test signed commit --- .../weak-passwords-report.component.html | 8 ++++++- .../pages/weak-passwords-report.component.ts | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.html b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.html index 9c5b587e60..76d4398cc0 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.html +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.html @@ -39,7 +39,13 @@ {{ "owner" | i18n }} - + {{ "weakness" | i18n }} diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts index c374ecd0e4..8b781b7737 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts @@ -59,6 +59,7 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen this.weakPasswordCiphers = []; this.filterStatus = [0]; this.findWeakPasswords(allCiphers); + this.weakPasswordCiphers = this.sortCiphers(this.weakPasswordCiphers, "score", false); } protected findWeakPasswords(ciphers: CipherView[]): void { @@ -112,6 +113,29 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen this.filterCiphersByOrg(this.weakPasswordCiphers); } + onSortChange(field: string, event: Event) { + const target = event.target as HTMLInputElement; + const ascending = target.checked; + this.weakPasswordCiphers = this.sortCiphers(this.weakPasswordCiphers, field, ascending); + } + + protected sortCiphers( + ciphers: ReportResult[], + field: string, + ascending: boolean, + ): ReportResult[] { + return ciphers.sort((a, b) => { + const aValue = a[field as keyof ReportResult]; + const bValue = b[field as keyof ReportResult]; + + if (aValue === bValue) { + return 0; + } + const comparison = aValue > bValue ? 1 : -1; + return ascending ? comparison : -comparison; + }); + } + protected canManageCipher(c: CipherView): boolean { // this will only ever be false from the org view; return true; From 08c42a8a27262d1d1ebf06adc50831bb7cffae38 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:12:56 -0600 Subject: [PATCH 09/15] [PM-13388] Extension: Persist Scroll from Vault (#12325) * add service to track scroll position of the vault tab in the popup * add data attribute to individual vault items - Allows query selector to focus on the specific element * stop scroll service when a cipher is deleted * start scroll listener when the vault page is initialized * fix strict linting errors * remove focus reset when navigating back to the vault screen * skip recording the first scroll from the automatic scroll * combine filters into a single observable * do not start the scroll service until filters have loaded in * refactor allFilters to come from the vault popup list filters service * use assertion on scroll position * hide virtual scrolling element while scrolling is restored * update comments * fix failing tests to use different matcher * remove visibility trick for restoring scroll position after chatting with design --------- Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com> --- .../vault-list-filters.component.html | 7 +- .../vault-list-filters.component.ts | 16 ++ .../components/vault-v2/vault-v2.component.ts | 30 +++- .../view-v2/view-v2.component.spec.ts | 157 +++++++++++++++++- .../vault-v2/view-v2/view-v2.component.ts | 3 + .../vault-popup-list-filters.service.ts | 3 + ...ault-popup-scroll-position.service.spec.ts | 137 +++++++++++++++ .../vault-popup-scroll-position.service.ts | 81 +++++++++ 8 files changed, 421 insertions(+), 13 deletions(-) create mode 100644 apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.spec.ts create mode 100644 apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.ts diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html index 56f35c41f6..c61562f9f9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html @@ -2,8 +2,9 @@
- + - + - + { + return { + organizations, + collections, + folders, + }; + }), + ); + constructor(private vaultPopupListFiltersService: VaultPopupListFiltersService) {} } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 7c21c7e6a0..12952a69c7 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -1,10 +1,9 @@ -import { ScrollingModule } from "@angular/cdk/scrolling"; +import { CdkVirtualScrollableElement, ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { Component, DestroyRef, OnDestroy, OnInit } from "@angular/core"; +import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { RouterLink } from "@angular/router"; -import { combineLatest, Observable, shareReplay, switchMap } from "rxjs"; -import { filter, map, take } from "rxjs/operators"; +import { combineLatest, filter, map, Observable, shareReplay, switchMap, take } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; @@ -19,6 +18,7 @@ import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-he import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; import { VaultPopupItemsService } from "../../services/vault-popup-items.service"; import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service"; +import { VaultPopupScrollPositionService } from "../../services/vault-popup-scroll-position.service"; import { BlockedInjectionBanner } from "./blocked-injection-banner/blocked-injection-banner.component"; import { @@ -58,7 +58,9 @@ enum VaultState { DecryptionFailureDialogComponent, ], }) -export class VaultV2Component implements OnInit, OnDestroy { +export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { + @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; + cipherType = CipherType; protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$; @@ -88,9 +90,12 @@ export class VaultV2Component implements OnInit, OnDestroy { protected VaultStateEnum = VaultState; + private allFilters$ = this.vaultPopupListFiltersService.allFilters$; + constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupListFiltersService: VaultPopupListFiltersService, + private vaultScrollPositionService: VaultPopupScrollPositionService, private destroyRef: DestroyRef, private cipherService: CipherService, private dialogService: DialogService, @@ -119,6 +124,17 @@ export class VaultV2Component implements OnInit, OnDestroy { }); } + ngAfterViewInit(): void { + if (this.virtualScrollElement) { + // The filters component can cause the size of the virtual scroll element to change, + // which can cause the scroll position to be land in the wrong spot. To fix this, + // wait until all filters are populated before restoring the scroll position. + this.allFilters$.pipe(take(1), takeUntilDestroyed(this.destroyRef)).subscribe(() => { + this.vaultScrollPositionService.start(this.virtualScrollElement!); + }); + } + } + async ngOnInit() { this.cipherService.failedToDecryptCiphers$ .pipe( @@ -134,5 +150,7 @@ export class VaultV2Component implements OnInit, OnDestroy { }); } - ngOnDestroy(): void {} + ngOnDestroy(): void { + this.vaultScrollPositionService.stop(); + } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 526ab2e257..39feb86f4f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -21,12 +21,15 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { DialogService, ToastService } from "@bitwarden/components"; import { CopyCipherFieldService } from "@bitwarden/vault"; import { BrowserApi } from "../../../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; +import { VaultPopupScrollPositionService } from "../../../services/vault-popup-scroll-position.service"; import { VaultPopupAutofillService } from "./../../../services/vault-popup-autofill.service"; import { ViewV2Component } from "./view-v2.component"; @@ -44,6 +47,10 @@ describe("ViewV2Component", () => { const collect = jest.fn().mockResolvedValue(null); const doAutofill = jest.fn().mockResolvedValue(true); const copy = jest.fn().mockResolvedValue(true); + const back = jest.fn().mockResolvedValue(null); + const openSimpleDialog = jest.fn().mockResolvedValue(true); + const stop = jest.fn(); + const showToast = jest.fn(); const mockCipher = { id: "122-333-444", @@ -54,7 +61,7 @@ describe("ViewV2Component", () => { password: "test-password", totp: "123", }, - }; + } as unknown as CipherView; const mockVaultPopupAutofillService = { doAutofill, @@ -68,13 +75,21 @@ describe("ViewV2Component", () => { const mockCipherService = { get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }), getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), + deleteWithServer: jest.fn().mockResolvedValue(undefined), + softDeleteWithServer: jest.fn().mockResolvedValue(undefined), }; beforeEach(async () => { + mockCipherService.deleteWithServer.mockClear(); + mockCipherService.softDeleteWithServer.mockClear(); mockNavigate.mockClear(); collect.mockClear(); doAutofill.mockClear(); copy.mockClear(); + stop.mockClear(); + openSimpleDialog.mockClear(); + back.mockClear(); + showToast.mockClear(); await TestBed.configureTestingModule({ imports: [ViewV2Component], @@ -84,9 +99,12 @@ describe("ViewV2Component", () => { { provide: LogService, useValue: mock() }, { provide: PlatformUtilsService, useValue: mock() }, { provide: ConfigService, useValue: mock() }, - { provide: PopupRouterCacheService, useValue: mock() }, + { provide: PopupRouterCacheService, useValue: mock({ back }) }, { provide: ActivatedRoute, useValue: { queryParams: params$ } }, { provide: EventCollectionService, useValue: { collect } }, + { provide: VaultPopupScrollPositionService, useValue: { stop } }, + { provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService }, + { provide: ToastService, useValue: { showToast } }, { provide: I18nService, useValue: { @@ -98,7 +116,6 @@ describe("ViewV2Component", () => { }, }, }, - { provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService }, { provide: AccountService, useValue: accountService, @@ -114,7 +131,13 @@ describe("ViewV2Component", () => { useValue: mockCopyCipherFieldService, }, ], - }).compileComponents(); + }) + .overrideProvider(DialogService, { + useValue: { + openSimpleDialog, + }, + }) + .compileComponents(); fixture = TestBed.createComponent(ViewV2Component); component = fixture.componentInstance; @@ -223,4 +246,130 @@ describe("ViewV2Component", () => { expect(closeSpy).toHaveBeenCalledTimes(1); })); }); + + describe("delete", () => { + beforeEach(() => { + component.cipher = mockCipher; + }); + + it("opens confirmation modal", async () => { + await component.delete(); + + expect(openSimpleDialog).toHaveBeenCalledTimes(1); + }); + + it("navigates back", async () => { + await component.delete(); + + expect(back).toHaveBeenCalledTimes(1); + }); + + it("stops scroll position service", async () => { + await component.delete(); + + expect(stop).toHaveBeenCalledTimes(1); + expect(stop).toHaveBeenCalledWith(true); + }); + + describe("deny confirmation", () => { + beforeEach(() => { + openSimpleDialog.mockResolvedValue(false); + }); + + it("does not delete the cipher", async () => { + await component.delete(); + + expect(mockCipherService.deleteWithServer).not.toHaveBeenCalled(); + expect(mockCipherService.softDeleteWithServer).not.toHaveBeenCalled(); + }); + + it("does not interact with side effects", () => { + expect(back).not.toHaveBeenCalled(); + expect(stop).not.toHaveBeenCalled(); + expect(showToast).not.toHaveBeenCalled(); + }); + }); + + describe("accept confirmation", () => { + beforeEach(() => { + openSimpleDialog.mockResolvedValue(true); + }); + + describe("soft delete", () => { + beforeEach(() => { + (mockCipher as any).isDeleted = null; + }); + + it("opens confirmation dialog", async () => { + await component.delete(); + + expect(openSimpleDialog).toHaveBeenCalledTimes(1); + expect(openSimpleDialog).toHaveBeenCalledWith({ + content: { + key: "deleteItemConfirmation", + }, + title: { + key: "deleteItem", + }, + type: "warning", + }); + }); + + it("calls soft delete", async () => { + await component.delete(); + + expect(mockCipherService.softDeleteWithServer).toHaveBeenCalled(); + expect(mockCipherService.deleteWithServer).not.toHaveBeenCalled(); + }); + + it("shows toast", async () => { + await component.delete(); + + expect(showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: "deletedItem", + }); + }); + }); + + describe("hard delete", () => { + beforeEach(() => { + (mockCipher as any).isDeleted = true; + }); + + it("opens confirmation dialog", async () => { + await component.delete(); + + expect(openSimpleDialog).toHaveBeenCalledTimes(1); + expect(openSimpleDialog).toHaveBeenCalledWith({ + content: { + key: "permanentlyDeleteItemConfirmation", + }, + title: { + key: "deleteItem", + }, + type: "warning", + }); + }); + + it("calls soft delete", async () => { + await component.delete(); + + expect(mockCipherService.deleteWithServer).toHaveBeenCalled(); + expect(mockCipherService.softDeleteWithServer).not.toHaveBeenCalled(); + }); + + it("shows toast", async () => { + await component.delete(); + + expect(showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: "permanentlyDeletedItem", + }); + }); + }); + }); + }); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index f3cd713dd5..378d3251e1 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -49,6 +49,7 @@ import { PopOutComponent } from "../../../../../platform/popup/components/pop-ou import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service"; import { BrowserViewPasswordHistoryService } from "../../../services/browser-view-password-history.service"; +import { VaultPopupScrollPositionService } from "../../../services/vault-popup-scroll-position.service"; import { closeViewVaultItemPopout, VaultPopoutType } from "../../../utils/vault-popout-window"; import { PopupFooterComponent } from "./../../../../../platform/popup/layout/popup-footer.component"; @@ -113,6 +114,7 @@ export class ViewV2Component { private popupRouterCacheService: PopupRouterCacheService, protected cipherAuthorizationService: CipherAuthorizationService, private copyCipherFieldService: CopyCipherFieldService, + private popupScrollPositionService: VaultPopupScrollPositionService, ) { this.subscribeToParams(); } @@ -202,6 +204,7 @@ export class ViewV2Component { return false; } + this.popupScrollPositionService.stop(true); await this.popupRouterCacheService.back(); this.toastService.showToast({ diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index 6190d14a6a..579319c92a 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -370,6 +370,9 @@ export class VaultPopupListFiltersService { ), ); + /** Organizations, collection, folders filters. */ + allFilters$ = combineLatest([this.organizations$, this.collections$, this.folders$]); + /** Updates the stored state for filter visibility. */ async updateFilterVisibility(isVisible: boolean): Promise { await this.filterVisibilityState.update(() => isVisible); diff --git a/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.spec.ts new file mode 100644 index 0000000000..562375f8f8 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.spec.ts @@ -0,0 +1,137 @@ +import { CdkVirtualScrollableElement } from "@angular/cdk/scrolling"; +import { fakeAsync, TestBed, tick } from "@angular/core/testing"; +import { NavigationEnd, Router } from "@angular/router"; +import { Subject, Subscription } from "rxjs"; + +import { VaultPopupScrollPositionService } from "./vault-popup-scroll-position.service"; + +describe("VaultPopupScrollPositionService", () => { + let service: VaultPopupScrollPositionService; + const events$ = new Subject(); + const unsubscribe = jest.fn(); + + beforeEach(async () => { + unsubscribe.mockClear(); + + await TestBed.configureTestingModule({ + providers: [ + VaultPopupScrollPositionService, + { provide: Router, useValue: { events: events$ } }, + ], + }); + + service = TestBed.inject(VaultPopupScrollPositionService); + + // set up dummy values + service["scrollPosition"] = 234; + service["scrollSubscription"] = { unsubscribe } as unknown as Subscription; + }); + + describe("router events", () => { + it("does not reset service when navigating to `/tabs/vault`", fakeAsync(() => { + const event = new NavigationEnd(22, "/tabs/vault", ""); + events$.next(event); + + tick(); + + expect(service["scrollPosition"]).toBe(234); + expect(service["scrollSubscription"]).not.toBeNull(); + })); + + it("resets values when navigating to other tab pages", fakeAsync(() => { + const event = new NavigationEnd(23, "/tabs/generator", ""); + events$.next(event); + + tick(); + + expect(service["scrollPosition"]).toBeNull(); + expect(unsubscribe).toHaveBeenCalled(); + expect(service["scrollSubscription"]).toBeNull(); + })); + }); + + describe("stop", () => { + it("removes scroll listener", () => { + service.stop(); + + expect(unsubscribe).toHaveBeenCalledTimes(1); + expect(service["scrollSubscription"]).toBeNull(); + }); + + it("resets stored values", () => { + service.stop(true); + + expect(service["scrollPosition"]).toBeNull(); + }); + }); + + describe("start", () => { + const elementScrolled$ = new Subject(); + const focus = jest.fn(); + const nativeElement = { + scrollTop: 0, + querySelector: jest.fn(() => ({ focus })), + addEventListener: jest.fn(), + style: { + visibility: "", + }, + }; + const virtualElement = { + elementScrolled: () => elementScrolled$, + getElementRef: () => ({ nativeElement }), + scrollTo: jest.fn(), + } as unknown as CdkVirtualScrollableElement; + + afterEach(() => { + // remove the actual subscription created by `.subscribe` + service["scrollSubscription"]?.unsubscribe(); + }); + + describe("initial scroll position", () => { + beforeEach(() => { + (virtualElement.scrollTo as jest.Mock).mockClear(); + nativeElement.querySelector.mockClear(); + }); + + it("does not scroll when `scrollPosition` is null", () => { + service["scrollPosition"] = null; + + service.start(virtualElement); + + expect(virtualElement.scrollTo).not.toHaveBeenCalled(); + }); + + it("scrolls the virtual element to `scrollPosition`", fakeAsync(() => { + service["scrollPosition"] = 500; + nativeElement.scrollTop = 500; + + service.start(virtualElement); + tick(); + + expect(virtualElement.scrollTo).toHaveBeenCalledWith({ behavior: "instant", top: 500 }); + })); + }); + + describe("scroll listener", () => { + it("unsubscribes from any existing subscription", () => { + service.start(virtualElement); + + expect(unsubscribe).toHaveBeenCalled(); + }); + + it("subscribes to `elementScrolled`", fakeAsync(() => { + virtualElement.measureScrollOffset = jest.fn(() => 455); + + service.start(virtualElement); + + elementScrolled$.next(null); // first subscription is skipped by `skip(1)` + elementScrolled$.next(null); + tick(); + + expect(virtualElement.measureScrollOffset).toHaveBeenCalledTimes(1); + expect(virtualElement.measureScrollOffset).toHaveBeenCalledWith("top"); + expect(service["scrollPosition"]).toBe(455); + })); + }); + }); +}); diff --git a/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.ts b/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.ts new file mode 100644 index 0000000000..5bfe0ec933 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-scroll-position.service.ts @@ -0,0 +1,81 @@ +import { CdkVirtualScrollableElement } from "@angular/cdk/scrolling"; +import { inject, Injectable } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { NavigationEnd, Router } from "@angular/router"; +import { filter, skip, Subscription } from "rxjs"; + +@Injectable({ + providedIn: "root", +}) +export class VaultPopupScrollPositionService { + private router = inject(Router); + + /** Path of the vault screen */ + private readonly vaultPath = "/tabs/vault"; + + /** Current scroll position relative to the top of the viewport. */ + private scrollPosition: number | null = null; + + /** Subscription associated with the virtual scroll element. */ + private scrollSubscription: Subscription | null = null; + + constructor() { + this.router.events + .pipe( + takeUntilDestroyed(), + filter((event): event is NavigationEnd => event instanceof NavigationEnd), + ) + .subscribe((event) => { + this.resetListenerForNavigation(event); + }); + } + + /** Scrolls the user to the stored scroll position and starts tracking scroll of the page. */ + start(virtualScrollElement: CdkVirtualScrollableElement) { + if (this.hasScrollPosition()) { + // Use `setTimeout` to scroll after rendering is complete + setTimeout(() => { + virtualScrollElement.scrollTo({ top: this.scrollPosition!, behavior: "instant" }); + }); + } + + this.scrollSubscription?.unsubscribe(); + + // Skip the first scroll event to avoid settings the scroll from the above `scrollTo` call + this.scrollSubscription = virtualScrollElement + ?.elementScrolled() + .pipe(skip(1)) + .subscribe(() => { + const offset = virtualScrollElement.measureScrollOffset("top"); + this.scrollPosition = offset; + }); + } + + /** Stops the scroll listener from updating the stored location. */ + stop(reset?: true) { + this.scrollSubscription?.unsubscribe(); + this.scrollSubscription = null; + + if (reset) { + this.scrollPosition = null; + } + } + + /** Returns true when a scroll position has been stored. */ + hasScrollPosition() { + return this.scrollPosition !== null; + } + + /** Conditionally resets the scroll listeners based on the ending path of the navigation */ + private resetListenerForNavigation(event: NavigationEnd): void { + // The vault page is the target of the scroll listener, return early + if (event.url === this.vaultPath) { + return; + } + + // For all other tab pages reset the scroll position + if (event.url.startsWith("/tabs/")) { + this.stop(true); + } + } +} From 70ea75d8f73883d9461afbcd30394af09385178e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 28 Jan 2025 16:40:52 +0100 Subject: [PATCH 10/15] [PM-17496] Migrate eslint to flat config (#12806) The legacy config is deprecated and will be removed in eslint 10. The flat config also allows us to write js functions which will assist in handling limitations with multiple identical rules. --- .eslintignore | 29 -- .eslintrc.json | 258 ------------ .github/renovate.json | 9 +- .storybook/main.ts | 5 +- .storybook/manager.js | 8 +- .vscode/settings.json | 3 +- apps/browser/.eslintrc.json | 26 -- .../content/components/.lit-storybook/main.ts | 6 +- apps/browser/tailwind.config.js | 2 +- apps/cli/.eslintrc.json | 5 - apps/cli/src/admin-console/.eslintrc.json | 3 - apps/desktop/.eslintrc.json | 6 - .../.eslintrc.json | 5 - .../src/ipc.service.ts | 1 + .../src/native-message.service.ts | 1 + apps/desktop/tailwind.config.js | 2 +- apps/web/.eslintrc.json | 22 - apps/web/src/app/admin-console/.eslintrc.json | 3 - ...console-cipher-form-config.service.spec.ts | 2 + bitwarden_license/bit-cli/.eslintrc.json | 5 - .../bit-cli/src/admin-console/.eslintrc.json | 3 - .../src/app/admin-console/.eslintrc.json | 3 - eslint.config.mjs | 378 ++++++++++++++++++ libs/admin-console/.eslintrc.json | 22 - .../src/state-migrations/.eslintrc.json | 24 -- package-lock.json | 68 +++- package.json | 7 +- scripts/.eslintrc.json | 5 - tsconfig.eslint.json | 5 + 29 files changed, 469 insertions(+), 447 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json delete mode 100644 apps/browser/.eslintrc.json delete mode 100644 apps/cli/.eslintrc.json delete mode 100644 apps/cli/src/admin-console/.eslintrc.json delete mode 100644 apps/desktop/.eslintrc.json delete mode 100644 apps/desktop/native-messaging-test-runner/.eslintrc.json delete mode 100644 apps/web/.eslintrc.json delete mode 100644 apps/web/src/app/admin-console/.eslintrc.json delete mode 100644 bitwarden_license/bit-cli/.eslintrc.json delete mode 100644 bitwarden_license/bit-cli/src/admin-console/.eslintrc.json delete mode 100644 bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json create mode 100644 eslint.config.mjs delete mode 100644 libs/admin-console/.eslintrc.json delete mode 100644 libs/common/src/state-migrations/.eslintrc.json delete mode 100644 scripts/.eslintrc.json diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c9a25670a9..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,29 +0,0 @@ -**/build -**/dist -**/coverage -.angular -storybook-static - -**/node_modules - -**/webpack.*.js -**/jest.config.js - -apps/browser/config/config.js -apps/browser/src/auth/scripts/duo.js -apps/browser/webpack/manifest.js - -apps/desktop/desktop_native -apps/desktop/src/auth/scripts/duo.js - -apps/web/config.js -apps/web/scripts/*.js -apps/web/tailwind.config.js - -apps/cli/config/config.js - -tailwind.config.js -libs/components/tailwind.config.base.js -libs/components/tailwind.config.js - -scripts/*.js diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 3fd6dec3d7..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,258 +0,0 @@ -{ - "root": true, - "env": { - "browser": true, - "webextensions": true - }, - "overrides": [ - { - "files": ["*.ts", "*.js"], - "plugins": ["@typescript-eslint", "rxjs", "rxjs-angular", "import"], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": ["./tsconfig.eslint.json"], - "sourceType": "module", - "ecmaVersion": 2020 - }, - "extends": [ - "eslint:recommended", - "plugin:@angular-eslint/recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/recommended", - "plugin:import/typescript", - "plugin:rxjs/recommended", - "prettier", - "plugin:storybook/recommended" - ], - "settings": { - "import/parsers": { - "@typescript-eslint/parser": [".ts"] - }, - "import/resolver": { - "typescript": { - "alwaysTryTypes": true - } - } - }, - "rules": { - "@angular-eslint/component-class-suffix": 0, - "@angular-eslint/contextual-lifecycle": 0, - "@angular-eslint/directive-class-suffix": 0, - "@angular-eslint/no-empty-lifecycle-method": 0, - "@angular-eslint/no-host-metadata-property": 0, - "@angular-eslint/no-input-rename": 0, - "@angular-eslint/no-inputs-metadata-property": 0, - "@angular-eslint/no-output-native": 0, - "@angular-eslint/no-output-on-prefix": 0, - "@angular-eslint/no-output-rename": 0, - "@angular-eslint/no-outputs-metadata-property": 0, - "@angular-eslint/use-lifecycle-interface": "error", - "@angular-eslint/use-pipe-transform-interface": 0, - "@typescript-eslint/explicit-member-accessibility": [ - "error", - { "accessibility": "no-public" } - ], - "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled - "@typescript-eslint/no-floating-promises": "error", - "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], - "@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }], - "@typescript-eslint/no-unused-expressions": ["error", { "allowTernary": true }], - "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], - "no-console": "error", - "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package. - "import/order": [ - "error", - { - "alphabetize": { - "order": "asc" - }, - "newlines-between": "always", - "pathGroups": [ - { - "pattern": "@bitwarden/**", - "group": "external", - "position": "after" - }, - { - "pattern": "src/**/*", - "group": "parent", - "position": "before" - } - ], - "pathGroupsExcludedImportTypes": ["builtin"] - } - ], - "rxjs-angular/prefer-takeuntil": ["error", { "alias": ["takeUntilDestroyed"] }], - "rxjs/no-exposed-subjects": ["error", { "allowProtected": true }], - "no-restricted-syntax": [ - "error", - { - "message": "Calling `svgIcon` directly is not allowed", - "selector": "CallExpression[callee.name='svgIcon']" - }, - { - "message": "Accessing FormGroup using `get` is not allowed, use `.value` instead", - "selector": "ChainExpression[expression.object.callee.property.name='get'][expression.property.name='value']" - } - ], - "curly": ["error", "all"], - "import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway - "import/no-restricted-paths": [ - "error", - { - "zones": [ - { - "target": ["libs/**/*"], - "from": ["apps/**/*"], - "message": "Libs should not import app-specific code." - }, - { - // avoid specific frameworks or large dependencies in common - "target": "./libs/common/**/*", - "from": [ - // Angular - "./libs/angular/**/*", - "./node_modules/@angular*/**/*", - - // Node - "./libs/node/**/*", - - //Generator - "./libs/tools/generator/components/**/*", - "./libs/tools/generator/core/**/*", - "./libs/tools/generator/extensions/**/*", - - // Import/export - "./libs/importer/**/*", - "./libs/tools/export/vault-export/vault-export-core/**/*" - ] - }, - { - // avoid import of unexported state objects - "target": [ - "!(libs)/**/*", - "libs/!(common)/**/*", - "libs/common/!(src)/**/*", - "libs/common/src/!(platform)/**/*", - "libs/common/src/platform/!(state)/**/*" - ], - "from": ["./libs/common/src/platform/state/**/*"], - // allow module index import - "except": ["**/state/index.ts"] - } - ] - } - ] - } - }, - { - "files": ["*.html"], - "parser": "@angular-eslint/template-parser", - "plugins": ["@angular-eslint/template", "tailwindcss"], - "rules": { - "@angular-eslint/template/button-has-type": "error", - "tailwindcss/no-custom-classname": [ - "error", - { - // uses negative lookahead to whitelist any class that doesn't start with "tw-" - // in other words: classnames that start with tw- must be valid TailwindCSS classes - "whitelist": ["(?!(tw)\\-).*"] - } - ], - "tailwindcss/enforces-negative-arbitrary-values": "error", - "tailwindcss/enforces-shorthand": "error", - "tailwindcss/no-contradicting-classname": "error" - } - }, - { - "files": ["apps/browser/src/**/*.ts", "libs/**/*.ts"], - "excludedFiles": [ - "apps/browser/src/autofill/{content,notification}/**/*.ts", - "apps/browser/src/**/background/**/*.ts", // It's okay to have long lived listeners in the background - "apps/browser/src/platform/background.ts" - ], - "rules": { - "no-restricted-syntax": [ - "error", - { - "message": "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", - // This selector covers events like chrome.storage.onChange & chrome.runtime.onMessage - "selector": "CallExpression > [object.object.object.name='chrome'][property.name='addListener']" - }, - { - "message": "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", - // This selector covers events like chrome.storage.local.onChange - "selector": "CallExpression > [object.object.object.object.name='chrome'][property.name='addListener']" - } - ] - } - }, - { - "files": ["**/*.ts"], - "excludedFiles": ["**/platform/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - "**/platform/**/internal", // General internal pattern - // All features that have been converted to barrel files - "**/platform/messaging/**" - ] - } - ] - } - }, - { - "files": ["**/src/**/*.ts"], - "excludedFiles": ["**/platform/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - "**/platform/**/internal", // General internal pattern - // All features that have been converted to barrel files - "**/platform/messaging/**", - "**/src/**/*" // Prevent relative imports across libs. - ] - } - ] - } - }, - { - "files": ["bitwarden_license/bit-common/src/**/*.ts"], - "rules": { - "no-restricted-imports": [ - "error", - { "patterns": ["@bitwarden/bit-common/*", "**/src/**/*"] } - ] - } - }, - { - "files": ["apps/**/*.ts"], - "rules": { - // Catches static imports - "no-restricted-imports": [ - "error", - { - "patterns": [ - "biwarden_license/**", - "@bitwarden/bit-common/*", - "@bitwarden/bit-web/*", - "**/src/**/*" - ] - } - ], - // Catches dynamic imports, e.g. in routing modules where modules are lazy-loaded - "no-restricted-syntax": [ - "error", - { - "message": "Don't import Bitwarden licensed code into OSS code.", - "selector": "ImportExpression > Literal.source[value=/.*(bitwarden_license|bit-common|bit-web).*/]" - } - ] - } - } - ] -} diff --git a/.github/renovate.json b/.github/renovate.json index 56b59a2af4..64b81cd9ee 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -71,12 +71,8 @@ }, { "matchPackageNames": [ - "@angular-eslint/eslint-plugin-template", - "@angular-eslint/eslint-plugin", "@angular-eslint/schematics", - "@angular-eslint/template-parser", - "@typescript-eslint/eslint-plugin", - "@typescript-eslint/parser", + "angular-eslint", "eslint-config-prettier", "eslint-import-resolver-typescript", "eslint-plugin-import", @@ -86,7 +82,8 @@ "eslint-plugin-tailwindcss", "eslint", "husky", - "lint-staged" + "lint-staged", + "typescript-eslint" ], "description": "Architecture owned dependencies", "commitMessagePrefix": "[deps] Architecture:", diff --git a/.storybook/main.ts b/.storybook/main.ts index b48a86ba2b..d98ca06ead 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,7 +1,8 @@ import { dirname, join } from "path"; + import { StorybookConfig } from "@storybook/angular"; -import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; import remarkGfm from "remark-gfm"; +import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; const config: StorybookConfig = { stories: [ @@ -29,6 +30,8 @@ const config: StorybookConfig = { getAbsolutePath("@storybook/addon-designs"), getAbsolutePath("@storybook/addon-interactions"), { + // @storybook/addon-docs is part of @storybook/addon-essentials + // eslint-disable-next-line storybook/no-uninstalled-addons name: "@storybook/addon-docs", options: { mdxPluginOptions: { diff --git a/.storybook/manager.js b/.storybook/manager.js index 409f93ec50..e0ec04fd37 100644 --- a/.storybook/manager.js +++ b/.storybook/manager.js @@ -50,10 +50,14 @@ const darkTheme = create({ }); export const getPreferredColorScheme = () => { - if (!globalThis || !globalThis.matchMedia) return "light"; + if (!globalThis || !globalThis.matchMedia) { + return "light"; + } const isDarkThemePreferred = globalThis.matchMedia("(prefers-color-scheme: dark)").matches; - if (isDarkThemePreferred) return "dark"; + if (isDarkThemePreferred) { + return "dark"; + } return "light"; }; diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b31121e17..295c290a37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "**/_locales/*[^n]/messages.json": true }, "rust-analyzer.linkedProjects": ["apps/desktop/desktop_native/Cargo.toml"], - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "eslint.useFlatConfig": true } diff --git a/apps/browser/.eslintrc.json b/apps/browser/.eslintrc.json deleted file mode 100644 index ba96051183..0000000000 --- a/apps/browser/.eslintrc.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "env": { - "browser": true, - "webextensions": true - }, - "overrides": [ - { - "files": ["src/**/*.ts"], - "excludedFiles": [ - "src/**/{content,popup,spec}/**/*.ts", - "src/**/autofill/{notification,overlay}/**/*.ts", - "src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts", - "src/**/*.spec.ts" - ], - "rules": { - "no-restricted-globals": [ - "error", - { - "name": "window", - "message": "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead." - } - ] - } - } - ] -} diff --git a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts index 9e2da59d99..157682160e 100644 --- a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts +++ b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts @@ -1,8 +1,8 @@ -import { dirname, join } from "path"; -import path from "path"; +import path, { dirname, join } from "path"; + import type { StorybookConfig } from "@storybook/web-components-webpack5"; -import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; import remarkGfm from "remark-gfm"; +import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; const getAbsolutePath = (value: string): string => dirname(require.resolve(join(value, "package.json"))); diff --git a/apps/browser/tailwind.config.js b/apps/browser/tailwind.config.js index d0ec8025c6..5e7c962b95 100644 --- a/apps/browser/tailwind.config.js +++ b/apps/browser/tailwind.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef, @typescript-eslint/no-var-requires */ +/* eslint-disable no-undef, @typescript-eslint/no-require-imports */ const config = require("../../libs/components/tailwind.config.base"); config.content = [ diff --git a/apps/cli/.eslintrc.json b/apps/cli/.eslintrc.json deleted file mode 100644 index 10d2238837..0000000000 --- a/apps/cli/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true - } -} diff --git a/apps/cli/src/admin-console/.eslintrc.json b/apps/cli/src/admin-console/.eslintrc.json deleted file mode 100644 index 3846718729..0000000000 --- a/apps/cli/src/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../libs/admin-console/.eslintrc.json" -} diff --git a/apps/desktop/.eslintrc.json b/apps/desktop/.eslintrc.json deleted file mode 100644 index 5d9ea457c3..0000000000 --- a/apps/desktop/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "env": { - "browser": true, - "node": true - } -} diff --git a/apps/desktop/native-messaging-test-runner/.eslintrc.json b/apps/desktop/native-messaging-test-runner/.eslintrc.json deleted file mode 100644 index d5ba8f9d9c..0000000000 --- a/apps/desktop/native-messaging-test-runner/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "no-console": "off" - } -} diff --git a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts index 8513363956..68c7ac73ab 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { homedir } from "os"; diff --git a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts index 94fdde026b..71c55a17d1 100644 --- a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import "module-alias/register"; import { v4 as uuidv4 } from "uuid"; diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index bf3b67c74a..9e43b21a59 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef, @typescript-eslint/no-var-requires */ +/* eslint-disable no-undef, @typescript-eslint/no-require-imports */ const config = require("../../libs/components/tailwind.config.base"); config.content = [ diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json deleted file mode 100644 index 6f606eb0c3..0000000000 --- a/apps/web/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "env": { - "browser": true - }, - "rules": { - "no-restricted-imports": [ - "error", - { - "patterns": [ - "**/app/core/*", - "**/reports/*", - "**/app/shared/*", - "**/organizations/settings/*", - "**/organizations/policies/*", - "@bitwarden/web-vault/*", - "src/**/*", - "bitwarden_license" - ] - } - ] - } -} diff --git a/apps/web/src/app/admin-console/.eslintrc.json b/apps/web/src/app/admin-console/.eslintrc.json deleted file mode 100644 index d55df3899e..0000000000 --- a/apps/web/src/app/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../../libs/admin-console/.eslintrc.json" -} diff --git a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts index c318e7389a..d10f83fd42 100644 --- a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts +++ b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts @@ -11,6 +11,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +// FIXME: remove `src` and fix import +// eslint-disable-next-line no-restricted-imports import { Account } from "../../../../../../../libs/importer/src/importers/lastpass/access/models"; import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; diff --git a/bitwarden_license/bit-cli/.eslintrc.json b/bitwarden_license/bit-cli/.eslintrc.json deleted file mode 100644 index 10d2238837..0000000000 --- a/bitwarden_license/bit-cli/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true - } -} diff --git a/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json b/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json deleted file mode 100644 index 3846718729..0000000000 --- a/bitwarden_license/bit-cli/src/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../libs/admin-console/.eslintrc.json" -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json b/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json deleted file mode 100644 index d55df3899e..0000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../../../libs/admin-console/.eslintrc.json" -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..514d1ccf0b --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,378 @@ +// @ts-check + +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import angular from "angular-eslint"; +// @ts-ignore +import importPlugin from "eslint-plugin-import"; +import eslintConfigPrettier from "eslint-config-prettier"; +import eslintPluginTailwindCSS from "eslint-plugin-tailwindcss"; +import rxjs from "eslint-plugin-rxjs"; +import angularRxjs from "eslint-plugin-rxjs-angular"; +import storybook from "eslint-plugin-storybook"; + +export default tseslint.config( + ...storybook.configs["flat/recommended"], + { + // Everything in this config object targets our TypeScript files (Components, Directives, Pipes etc) + files: ["**/*.ts", "**/*.js"], + extends: [ + eslint.configs.recommended, + ...tseslint.configs.recommended, + //...tseslint.configs.stylistic, + ...angular.configs.tsRecommended, + importPlugin.flatConfigs.recommended, + importPlugin.flatConfigs.typescript, + eslintConfigPrettier, // Disables rules that conflict with Prettier + ], + plugins: { + rxjs: rxjs, + "rxjs-angular": angularRxjs, + }, + languageOptions: { + parserOptions: { + project: ["./tsconfig.eslint.json"], + sourceType: "module", + ecmaVersion: 2020, + }, + }, + settings: { + "import/parsers": { + "@typescript-eslint/parser": [".ts"], + }, + "import/resolver": { + typescript: { + alwaysTryTypes: true, + }, + }, + }, + processor: angular.processInlineTemplates, + rules: { + ...rxjs.configs.recommended.rules, + "rxjs-angular/prefer-takeuntil": ["error", { alias: ["takeUntilDestroyed"] }], + "rxjs/no-exposed-subjects": ["error", { allowProtected: true }], + + // TODO: Enable these. + "@angular-eslint/component-class-suffix": 0, + "@angular-eslint/contextual-lifecycle": 0, + "@angular-eslint/directive-class-suffix": 0, + "@angular-eslint/no-empty-lifecycle-method": 0, + "@angular-eslint/no-host-metadata-property": 0, + "@angular-eslint/no-input-rename": 0, + "@angular-eslint/no-inputs-metadata-property": 0, + "@angular-eslint/no-output-native": 0, + "@angular-eslint/no-output-on-prefix": 0, + "@angular-eslint/no-output-rename": 0, + "@angular-eslint/no-outputs-metadata-property": 0, + "@angular-eslint/use-lifecycle-interface": "error", + "@angular-eslint/use-pipe-transform-interface": 0, + + "@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }], + "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }], + "@typescript-eslint/no-this-alias": ["error", { allowedNames: ["self"] }], + "@typescript-eslint/no-unused-expressions": ["error", { allowTernary: true }], + "@typescript-eslint/no-unused-vars": ["error", { args: "none" }], + + curly: ["error", "all"], + "no-console": "error", + + "import/order": [ + "error", + { + alphabetize: { + order: "asc", + }, + "newlines-between": "always", + pathGroups: [ + { + pattern: "@bitwarden/**", + group: "external", + position: "after", + }, + { + pattern: "src/**/*", + group: "parent", + position: "before", + }, + ], + pathGroupsExcludedImportTypes: ["builtin"], + }, + ], + "import/namespace": ["off"], // This doesn't resolve namespace imports correctly, but TS will throw for this anyway + "import/no-restricted-paths": [ + "error", + { + zones: [ + { + target: ["libs/**/*"], + from: ["apps/**/*"], + message: "Libs should not import app-specific code.", + }, + { + // avoid specific frameworks or large dependencies in common + target: "./libs/common/**/*", + from: [ + // Angular + "./libs/angular/**/*", + "./node_modules/@angular*/**/*", + + // Node + "./libs/node/**/*", + + //Generator + "./libs/tools/generator/components/**/*", + "./libs/tools/generator/core/**/*", + "./libs/tools/generator/extensions/**/*", + + // Import/export + "./libs/importer/**/*", + "./libs/tools/export/vault-export/vault-export-core/**/*", + ], + }, + { + // avoid import of unexported state objects + target: [ + "!(libs)/**/*", + "libs/!(common)/**/*", + "libs/common/!(src)/**/*", + "libs/common/src/!(platform)/**/*", + "libs/common/src/platform/!(state)/**/*", + ], + from: ["./libs/common/src/platform/state/**/*"], + // allow module index import + except: ["**/state/index.ts"], + }, + ], + }, + ], + "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package., + }, + }, + { + // Everything in this config object targets our HTML files (external templates, + // and inline templates as long as we have the `processor` set on our TypeScript config above) + files: ["**/*.html"], + extends: [ + // Apply the recommended Angular template rules + // ...angular.configs.templateRecommended, + // Apply the Angular template rules which focus on accessibility of our apps + // ...angular.configs.templateAccessibility, + ], + languageOptions: { + parser: angular.templateParser, + }, + plugins: { + "@angular-eslint/template": angular.templatePlugin, + tailwindcss: eslintPluginTailwindCSS, + }, + rules: { + "@angular-eslint/template/button-has-type": "error", + "tailwindcss/no-custom-classname": [ + "error", + { + // uses negative lookahead to whitelist any class that doesn't start with "tw-" + // in other words: classnames that start with tw- must be valid TailwindCSS classes + whitelist: ["(?!(tw)\\-).*"], + }, + ], + "tailwindcss/enforces-negative-arbitrary-values": "error", + "tailwindcss/enforces-shorthand": "error", + "tailwindcss/no-contradicting-classname": "error", + }, + }, + + // Global quirks + { + files: ["apps/browser/src/**/*.ts", "libs/**/*.ts"], + ignores: [ + "apps/browser/src/autofill/{deprecated/content,content,notification}/**/*.ts", + "apps/browser/src/**/background/**/*.ts", // It's okay to have long lived listeners in the background + "apps/browser/src/platform/background.ts", + ], + rules: { + "no-restricted-syntax": [ + "error", + { + message: + "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", + // This selector covers events like chrome.storage.onChange & chrome.runtime.onMessage + selector: + "CallExpression > [object.object.object.name='chrome'][property.name='addListener']", + }, + { + message: + "Using addListener in the browser popup produces a memory leak in Safari, use `BrowserApi.addListener` instead", + // This selector covers events like chrome.storage.local.onChange + selector: + "CallExpression > [object.object.object.object.name='chrome'][property.name='addListener']", + }, + ], + }, + }, + { + files: ["**/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports(), + }, + }, + + // App overrides. Be considerate if you override these. + { + files: ["apps/browser/src/**/*.ts"], + ignores: [ + "apps/browser/src/**/{content,popup,spec}/**/*.ts", + "apps/browser/src/**/autofill/{notification,overlay}/**/*.ts", + "apps/browser/src/**/autofill/**/{autofill-overlay-content,collect-autofill-content,dom-element-visibility,insert-autofill-content}.service.ts", + "apps/browser/src/**/*.spec.ts", + ], + rules: { + "no-restricted-globals": [ + "error", + { + name: "window", + message: + "The `window` object is not available in service workers and may not be available within the background script. Consider using `self`, `globalThis`, or another global property instead.", + }, + ], + }, + }, + { + files: ["bitwarden_license/bit-common/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports(["@bitwarden/bit-common/*"]), + }, + }, + { + files: ["apps/**/*.ts"], + rules: { + // Catches static imports + "no-restricted-imports": buildNoRestrictedImports([ + "bitwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + ]), + }, + }, + { + files: ["apps/web/src/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([ + "bitwarden_license/**", + "@bitwarden/bit-common/*", + "@bitwarden/bit-web/*", + + "**/app/core/*", + "**/reports/*", + "**/app/shared/*", + "**/organizations/settings/*", + "**/organizations/policies/*", + ]), + }, + }, + + /// Team overrides + { + files: ["**/src/platform/**/*.ts"], + rules: { + "no-restricted-imports": buildNoRestrictedImports([], true), + }, + }, + { + files: [ + "apps/cli/src/admin-console/**/*.ts", + "apps/web/src/app/admin-console/**/*.ts", + "bitwarden_license/bit-cli/src/admin-console/**/*.ts", + "bitwarden_license/bit-web/src/app/admin-console/**/*.ts", + "libs/admin-console/src/**/*.ts", + ], + rules: { + "@angular-eslint/component-class-suffix": "error", + "@angular-eslint/contextual-lifecycle": "error", + "@angular-eslint/directive-class-suffix": "error", + "@angular-eslint/no-empty-lifecycle-method": "error", + "@angular-eslint/no-input-rename": "error", + "@angular-eslint/no-inputs-metadata-property": "error", + "@angular-eslint/no-output-native": "error", + "@angular-eslint/no-output-on-prefix": "error", + "@angular-eslint/no-output-rename": "error", + "@angular-eslint/no-outputs-metadata-property": "error", + "@angular-eslint/use-lifecycle-interface": "error", + "@angular-eslint/use-pipe-transform-interface": "error", + }, + }, + { + files: ["libs/common/src/state-migrations/**/*.ts"], + rules: { + "import/no-restricted-paths": [ + "error", + { + basePath: "libs/common/src/state-migrations", + zones: [ + { + target: "./", + from: "../", + // Relative to from, not basePath + except: ["state-migrations"], + message: + "State migrations should rarely import from the greater codebase. If you need to import from another location, take into account the likelihood of change in that code and consider copying to the migration instead.", + }, + ], + }, + ], + }, + }, + + // Keep ignores at the end + { + ignores: [ + "**/build/", + "**/dist/", + "**/coverage/", + ".angular/", + "storybook-static/", + + "**/node_modules/", + + "**/webpack.*.js", + "**/jest.config.js", + + "apps/browser/config/config.js", + "apps/browser/src/auth/scripts/duo.js", + "apps/browser/webpack/manifest.js", + + "apps/desktop/desktop_native", + "apps/desktop/src/auth/scripts/duo.js", + + "apps/web/config.js", + "apps/web/scripts/*.js", + "apps/web/tailwind.config.js", + + "apps/cli/config/config.js", + + "tailwind.config.js", + "libs/components/tailwind.config.base.js", + "libs/components/tailwind.config.js", + + "scripts/*.js", + ], + }, +); + +/** + * // Helper function for building no-restricted-imports rule + * @param {string[]} additionalForbiddenPatterns + * @returns {any} + */ +function buildNoRestrictedImports(additionalForbiddenPatterns = [], skipPlatform = false) { + return [ + "error", + { + patterns: [ + ...(skipPlatform ? [] : ["**/platform/**/internal", "**/platform/messaging/**"]), + "**/src/**/*", // Prevent relative imports across libs. + ].concat(additionalForbiddenPatterns), + }, + ]; +} diff --git a/libs/admin-console/.eslintrc.json b/libs/admin-console/.eslintrc.json deleted file mode 100644 index d8aa8f64a8..0000000000 --- a/libs/admin-console/.eslintrc.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "overrides": [ - { - "files": ["*.ts"], - "extends": ["plugin:@angular-eslint/recommended"], - "rules": { - "@angular-eslint/component-class-suffix": "error", - "@angular-eslint/contextual-lifecycle": "error", - "@angular-eslint/directive-class-suffix": "error", - "@angular-eslint/no-empty-lifecycle-method": "error", - "@angular-eslint/no-input-rename": "error", - "@angular-eslint/no-inputs-metadata-property": "error", - "@angular-eslint/no-output-native": "error", - "@angular-eslint/no-output-on-prefix": "error", - "@angular-eslint/no-output-rename": "error", - "@angular-eslint/no-outputs-metadata-property": "error", - "@angular-eslint/use-lifecycle-interface": "error", - "@angular-eslint/use-pipe-transform-interface": "error" - } - } - ] -} diff --git a/libs/common/src/state-migrations/.eslintrc.json b/libs/common/src/state-migrations/.eslintrc.json deleted file mode 100644 index 4b66f0a32f..0000000000 --- a/libs/common/src/state-migrations/.eslintrc.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "overrides": [ - { - "files": ["*"], - "rules": { - "import/no-restricted-paths": [ - "error", - { - "basePath": "libs/common/src/state-migrations", - "zones": [ - { - "target": "./", - "from": "../", - // Relative to from, not basePath - "except": ["state-migrations"], - "message": "State migrations should rarely import from the greater codebase. If you need to import from another location, take into account the likelihood of change in that code and consider copying to the migration instead." - } - ] - } - ] - } - } - ] -} diff --git a/package-lock.json b/package-lock.json index e274e82254..005fd25d86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,10 +77,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "18.2.12", - "@angular-eslint/eslint-plugin": "18.4.3", - "@angular-eslint/eslint-plugin-template": "18.4.3", "@angular-eslint/schematics": "18.4.3", - "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", "@babel/core": "7.24.9", @@ -121,10 +118,9 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "8.20.0", - "@typescript-eslint/parser": "8.20.0", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", + "angular-eslint": "18.4.3", "autoprefixer": "10.4.20", "babel-loader": "9.2.1", "base64-loader": "1.0.0", @@ -176,6 +172,7 @@ "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "2.19.0", "typescript": "5.4.2", + "typescript-eslint": "8.20.0", "typescript-strict-plugin": "^2.4.4", "url": "0.11.4", "util": "0.12.5", @@ -1240,6 +1237,20 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-eslint/builder": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-18.4.3.tgz", + "integrity": "sha512-NzmrXlr7GFE+cjwipY/CxBscZXNqnuK0us1mO6Z2T6MeH6m+rRcdlY/rZyKoRniyNNvuzl6vpEsfMIMmnfebrA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": ">= 0.1800.0 < 0.1900.0", + "@angular-devkit/core": ">= 18.0.0 < 19.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, "node_modules/@angular-eslint/bundled-angular-compiler": { "version": "18.4.3", "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-18.4.3.tgz", @@ -10080,7 +10091,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz", "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.20.0", @@ -10259,7 +10269,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz", "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==", "dev": true, - "license": "MIT", "dependencies": { "@typescript-eslint/scope-manager": "8.20.0", "@typescript-eslint/types": "8.20.0", @@ -10302,7 +10311,6 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz", "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==", "dev": true, - "license": "MIT", "dependencies": { "@typescript-eslint/typescript-estree": "8.20.0", "@typescript-eslint/utils": "8.20.0", @@ -11106,6 +11114,28 @@ "ajv": "^8.8.2" } }, + "node_modules/angular-eslint": { + "version": "18.4.3", + "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-18.4.3.tgz", + "integrity": "sha512-0ZjLzzADGRLUhZC8ZpwSo6CE/m6QhQB/oljMJ0mEfP+lB1sy1v8PBKNsJboIcfEEgGW669Z/efVQ3df88yJLYg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": ">= 18.0.0 < 19.0.0", + "@angular-devkit/schematics": ">= 18.0.0 < 19.0.0", + "@angular-eslint/builder": "18.4.3", + "@angular-eslint/eslint-plugin": "18.4.3", + "@angular-eslint/eslint-plugin-template": "18.4.3", + "@angular-eslint/schematics": "18.4.3", + "@angular-eslint/template-parser": "18.4.3", + "@typescript-eslint/types": "^8.0.0", + "@typescript-eslint/utils": "^8.0.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*", + "typescript-eslint": "^8.0.0" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -31151,6 +31181,28 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.20.0.tgz", + "integrity": "sha512-Kxz2QRFsgbWj6Xcftlw3Dd154b3cEPFqQC+qMZrMypSijPd4UanKKvoKDrJ4o8AIfZFKAF+7sMaEIR8mTElozA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.20.0", + "@typescript-eslint/parser": "8.20.0", + "@typescript-eslint/utils": "8.20.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/typescript-strict-plugin": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/typescript-strict-plugin/-/typescript-strict-plugin-2.4.4.tgz", diff --git a/package.json b/package.json index adc02d6e4f..fc4f1a49fb 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,7 @@ ], "devDependencies": { "@angular-devkit/build-angular": "18.2.12", - "@angular-eslint/eslint-plugin": "18.4.3", - "@angular-eslint/eslint-plugin-template": "18.4.3", "@angular-eslint/schematics": "18.4.3", - "@angular-eslint/template-parser": "18.4.3", "@angular/cli": "18.2.12", "@angular/compiler-cli": "18.2.13", "@babel/core": "7.24.9", @@ -81,10 +78,9 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "8.20.0", - "@typescript-eslint/parser": "8.20.0", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", + "angular-eslint": "18.4.3", "autoprefixer": "10.4.20", "babel-loader": "9.2.1", "base64-loader": "1.0.0", @@ -136,6 +132,7 @@ "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "2.19.0", "typescript": "5.4.2", + "typescript-eslint": "8.20.0", "typescript-strict-plugin": "^2.4.4", "url": "0.11.4", "util": "0.12.5", diff --git a/scripts/.eslintrc.json b/scripts/.eslintrc.json deleted file mode 100644 index 10d2238837..0000000000 --- a/scripts/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "node": true - } -} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 980d7832ac..83f62d5f68 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -47,6 +47,11 @@ } ] }, + "files": [ + ".storybook/main.ts", + ".storybook/manager.js", + "apps/browser/src/autofill/content/components/.lit-storybook/main.ts" + ], "include": ["apps/**/*", "libs/**/*", "bitwarden_license/**/*", "scripts/**/*"], "exclude": ["**/build", "**/dist"] } From 7c2bf504a3b185ec747c58dec168d6094ad10fcd Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:01:23 -0600 Subject: [PATCH 11/15] [PM-11249] Sync attachment updates across platforms (#11758) * update extension refresh form when an attachment is added or removed - This is needed because the revision date was updated on the server and the locally stored cipher needs to match. * receive updated cipher from delete attachment endpoint - deleting an attachment will now alter the revision timestamp on a cipher. * patch the cipher when an attachment is added or deleted * migrate vault component to use the `cipherViews$` observable * reference `cipherViews$` on desktop for vault-items - This avoid race conditions where ciphers are cleared out in the background. `cipherViews` should always emit the latest views * return CipherData from cipher service so that consumers have the updated cipher right away * use the updated cipher from attachment endpoints to refresh the details within the add/edit components on desktop --- .../src/vault/app/vault/add-edit.component.ts | 11 ++++++++++ .../src/vault/app/vault/vault.component.ts | 19 ++++++++++------- .../vault-item-dialog.component.ts | 19 +++++++++++++++++ .../vault/components/attachments.component.ts | 20 +++++++++++++----- .../vault/components/vault-items.component.ts | 12 ++++++++--- .../src/vault/components/view.component.ts | 12 +++++++---- libs/common/src/services/api.service.ts | 2 +- .../src/vault/abstractions/cipher.service.ts | 4 ++-- .../src/vault/services/cipher.service.ts | 21 +++++++++++++++---- libs/vault/src/cipher-form/index.ts | 1 + 10 files changed, 95 insertions(+), 26 deletions(-) diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index 02fa807608..f2ca05c933 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -22,6 +22,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogService, ToastService } from "@bitwarden/components"; import { SshKeyPasswordPromptComponent } from "@bitwarden/importer/ui"; @@ -148,6 +149,16 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On ); } + /** + * Updates the cipher when an attachment is altered. + * Note: This only updates the `attachments` and `revisionDate` + * properties to ensure any in-progress edits are not lost. + */ + patchCipherAttachments(cipher: CipherView) { + this.cipher.attachments = cipher.attachments; + this.cipher.revisionDate = cipher.revisionDate; + } + async importSshKeyFromClipboard(password: string = "") { const key = await this.platformUtilsService.readFromClipboard(); const parsedKey = await ipc.platform.sshAgent.importKey(key, password); diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index da1f7bb316..987c269159 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -159,11 +159,6 @@ export class VaultComponent implements OnInit, OnDestroy { await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); await this.vaultFilterComponent.reloadOrganizations(); break; - case "refreshCiphers": - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.vaultItemsComponent.refresh(); - break; case "modalShown": this.showingModal = true; break; @@ -535,9 +530,19 @@ export class VaultComponent implements OnInit, OnDestroy { let madeAttachmentChanges = false; // eslint-disable-next-line rxjs-angular/prefer-takeuntil - childComponent.onUploadedAttachment.subscribe(() => (madeAttachmentChanges = true)); + childComponent.onUploadedAttachment.subscribe((cipher) => { + madeAttachmentChanges = true; + // Update the edit component cipher with the updated cipher, + // which is needed because the revision date is updated when an attachment is altered + this.addEditComponent.patchCipherAttachments(cipher); + }); // eslint-disable-next-line rxjs-angular/prefer-takeuntil - childComponent.onDeletedAttachment.subscribe(() => (madeAttachmentChanges = true)); + childComponent.onDeletedAttachment.subscribe((cipher) => { + madeAttachmentChanges = true; + // Update the edit component cipher with the updated cipher, + // which is needed because the revision date is updated when an attachment is altered + this.addEditComponent.patchCipherAttachments(cipher); + }); // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.modal.onClosed.subscribe(async () => { diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index a530fd0cc8..f4d9a9a73e 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -43,6 +43,7 @@ import { DecryptionFailureDialogComponent, } from "@bitwarden/vault"; +import { CipherFormComponent } from "../../../../../../../libs/vault/src/cipher-form/components/cipher-form.component"; import { SharedModule } from "../../../shared/shared.module"; import { AttachmentDialogCloseResult, @@ -144,6 +145,8 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { @ViewChild("dialogContent") protected dialogContent: ElementRef; + @ViewChild(CipherFormComponent) cipherFormComponent!: CipherFormComponent; + /** * Tracks if the cipher was ever modified while the dialog was open. Used to ensure the dialog emits the correct result * in case of closing with the X button or ESC key. @@ -432,6 +435,22 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { result.action === AttachmentDialogResult.Removed || result.action === AttachmentDialogResult.Uploaded ) { + const updatedCipher = await this.cipherService.get(this.formConfig.originalCipher?.id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const updatedCipherView = await updatedCipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), + ); + + this.cipherFormComponent.patchCipher((currentCipher) => { + currentCipher.attachments = updatedCipherView.attachments; + currentCipher.revisionDate = updatedCipherView.revisionDate; + + return currentCipher; + }); + this._cipherModified = true; } }; diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index a3b635f151..ec3dc43b44 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -16,6 +16,7 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -26,7 +27,7 @@ import { KeyService } from "@bitwarden/key-management"; export class AttachmentsComponent implements OnInit { @Input() cipherId: string; @Input() viewOnly: boolean; - @Output() onUploadedAttachment = new EventEmitter(); + @Output() onUploadedAttachment = new EventEmitter(); @Output() onDeletedAttachment = new EventEmitter(); @Output() onReuploadedAttachment = new EventEmitter(); @@ -34,7 +35,7 @@ export class AttachmentsComponent implements OnInit { cipherDomain: Cipher; canAccessAttachments: boolean; formPromise: Promise; - deletePromises: { [id: string]: Promise } = {}; + deletePromises: { [id: string]: Promise } = {}; reuploadPromises: { [id: string]: Promise } = {}; emergencyAccessId?: string = null; protected componentName = ""; @@ -96,7 +97,7 @@ export class AttachmentsComponent implements OnInit { title: null, message: this.i18nService.t("attachmentSaved"), }); - this.onUploadedAttachment.emit(); + this.onUploadedAttachment.emit(this.cipher); } catch (e) { this.logService.error(e); } @@ -125,7 +126,16 @@ export class AttachmentsComponent implements OnInit { try { this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); - await this.deletePromises[attachment.id]; + const updatedCipher = await this.deletePromises[attachment.id]; + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const cipher = new Cipher(updatedCipher); + this.cipher = await cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); + this.toastService.showToast({ variant: "success", title: null, @@ -140,7 +150,7 @@ export class AttachmentsComponent implements OnInit { } this.deletePromises[attachment.id] = null; - this.onDeletedAttachment.emit(); + this.onDeletedAttachment.emit(this.cipher); } async download(attachment: AttachmentView) { diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts index 4ef00e9006..f093aeb133 100644 --- a/libs/angular/src/vault/components/vault-items.component.ts +++ b/libs/angular/src/vault/components/vault-items.component.ts @@ -1,7 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { BehaviorSubject, firstValueFrom, from, Subject, switchMap, takeUntil } from "rxjs"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { BehaviorSubject, Subject, firstValueFrom, from, switchMap, takeUntil } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -40,7 +41,12 @@ export class VaultItemsComponent implements OnInit, OnDestroy { constructor( protected searchService: SearchService, protected cipherService: CipherService, - ) {} + ) { + this.cipherService.cipherViews$.pipe(takeUntilDestroyed()).subscribe((ciphers) => { + void this.doSearch(ciphers); + this.loaded = true; + }); + } ngOnInit(): void { this._searchText$ @@ -117,7 +123,7 @@ export class VaultItemsComponent implements OnInit, OnDestroy { protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted; protected async doSearch(indexedCiphers?: CipherView[]) { - indexedCiphers = indexedCiphers ?? (await this.cipherService.getAllDecrypted()); + indexedCiphers = indexedCiphers ?? (await firstValueFrom(this.cipherService.cipherViews$)); const failedCiphers = await firstValueFrom(this.cipherService.failedToDecryptCiphers$); if (failedCiphers != null && failedCiphers.length > 0) { diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 18caa875e0..abbedf1307 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -11,7 +11,7 @@ import { OnInit, Output, } from "@angular/core"; -import { firstValueFrom, map, Observable } from "rxjs"; +import { filter, firstValueFrom, map, Observable } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -144,11 +144,15 @@ export class ViewComponent implements OnDestroy, OnInit { async load() { this.cleanUp(); - const cipher = await this.cipherService.get(this.cipherId); const activeUserId = await firstValueFrom(this.activeUserId$); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + // Grab individual cipher from `cipherViews$` for the most up-to-date information + this.cipher = await firstValueFrom( + this.cipherService.cipherViews$.pipe( + map((ciphers) => ciphers.find((c) => c.id === this.cipherId)), + filter((cipher) => !!cipher), + ), ); + this.canAccessPremium = await firstValueFrom( this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), ); diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 03ea969c7b..ad59ad0837 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -702,7 +702,7 @@ export class ApiService implements ApiServiceAbstraction { } deleteCipherAttachment(id: string, attachmentId: string): Promise { - return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, false); + return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, true); } deleteCipherAttachmentAdmin(id: string, attachmentId: string): Promise { diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 2e34f0ac60..0672ae29e9 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -154,8 +154,8 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; deleteWithServer: (id: string, asAdmin?: boolean) => Promise; deleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise; - deleteAttachment: (id: string, attachmentId: string) => Promise; - deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise; + deleteAttachment: (id: string, revisionDate: string, attachmentId: string) => Promise; + deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise; sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number; sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number; getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number; diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index b1cdf72e08..18295453d9 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1078,7 +1078,11 @@ export class CipherService implements CipherServiceAbstraction { await this.delete(ids); } - async deleteAttachment(id: string, attachmentId: string): Promise { + async deleteAttachment( + id: string, + revisionDate: string, + attachmentId: string, + ): Promise { let ciphers = await firstValueFrom(this.ciphers$); const cipherId = id as CipherId; // eslint-disable-next-line @@ -1092,6 +1096,10 @@ export class CipherService implements CipherServiceAbstraction { } } + // Deleting the cipher updates the revision date on the server, + // Update the stored `revisionDate` to match + ciphers[cipherId].revisionDate = revisionDate; + await this.clearCache(); await this.encryptedCiphersState.update(() => { if (ciphers == null) { @@ -1099,15 +1107,20 @@ export class CipherService implements CipherServiceAbstraction { } return ciphers; }); + + return ciphers[cipherId]; } - async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { + async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { + let cipherResponse = null; try { - await this.apiService.deleteCipherAttachment(id, attachmentId); + cipherResponse = await this.apiService.deleteCipherAttachment(id, attachmentId); } catch (e) { return Promise.reject((e as ErrorResponse).getSingleMessage()); } - await this.deleteAttachment(id, attachmentId); + const cipherData = CipherData.fromJSON(cipherResponse?.cipher); + + return await this.deleteAttachment(id, cipherData.revisionDate, attachmentId); } sortCiphersByLastUsed(a: CipherView, b: CipherView): number { diff --git a/libs/vault/src/cipher-form/index.ts b/libs/vault/src/cipher-form/index.ts index 8cb779a8ec..75e805b3be 100644 --- a/libs/vault/src/cipher-form/index.ts +++ b/libs/vault/src/cipher-form/index.ts @@ -9,3 +9,4 @@ export { TotpCaptureService } from "./abstractions/totp-capture.service"; export { CipherFormGenerationService } from "./abstractions/cipher-form-generation.service"; export { DefaultCipherFormConfigService } from "./services/default-cipher-form-config.service"; export { CipherFormGeneratorComponent } from "./components/cipher-generator/cipher-form-generator.component"; +export { CipherFormContainer } from "../cipher-form/cipher-form-container"; From 331c04a0fa12c6da69e5d208dda4ba4be67a9f67 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:49:48 -0600 Subject: [PATCH 12/15] fix deep import for `CipherFormComponent` (#13107) --- .../components/vault-item-dialog/vault-item-dialog.component.ts | 2 +- libs/vault/src/cipher-form/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index f4d9a9a73e..af7f8e1e8f 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -36,6 +36,7 @@ import { } from "@bitwarden/components"; import { CipherAttachmentsComponent, + CipherFormComponent, CipherFormConfig, CipherFormGenerationService, CipherFormModule, @@ -43,7 +44,6 @@ import { DecryptionFailureDialogComponent, } from "@bitwarden/vault"; -import { CipherFormComponent } from "../../../../../../../libs/vault/src/cipher-form/components/cipher-form.component"; import { SharedModule } from "../../../shared/shared.module"; import { AttachmentDialogCloseResult, diff --git a/libs/vault/src/cipher-form/index.ts b/libs/vault/src/cipher-form/index.ts index 75e805b3be..0172733b68 100644 --- a/libs/vault/src/cipher-form/index.ts +++ b/libs/vault/src/cipher-form/index.ts @@ -10,3 +10,4 @@ export { CipherFormGenerationService } from "./abstractions/cipher-form-generati export { DefaultCipherFormConfigService } from "./services/default-cipher-form-config.service"; export { CipherFormGeneratorComponent } from "./components/cipher-generator/cipher-form-generator.component"; export { CipherFormContainer } from "../cipher-form/cipher-form-container"; +export { CipherFormComponent } from "./components/cipher-form.component"; From 26a0594056134ae86bca4dbb70a7f5b929415ce1 Mon Sep 17 00:00:00 2001 From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:17:00 -0500 Subject: [PATCH 13/15] [PM-17655] Billing Code Ownership Updates (#13105) * Moved has-premium.guard under billing * Moved free-trial.ts to billing * Moved premium directives to billing * Moved families-policy.service.ts to billing * Moved trial initiation from auth to billing --- .../{ => billing}/services/families-policy.service.spec.ts | 0 .../src/{ => billing}/services/families-policy.service.ts | 0 .../about-page/more-from-bitwarden-page-v2.component.ts | 2 +- .../src/app/{core => billing}/guards/has-premium.guard.ts | 0 .../payment-method/organization-payment-method.component.ts | 2 +- apps/web/src/app/billing/services/trial-flow.service.ts | 2 +- apps/web/src/app/billing/shared/payment-method.component.ts | 2 +- .../complete-trial-initiation.component.html | 0 .../complete-trial-initiation.component.ts | 2 +- .../resolver/free-trial-text.resolver.spec.ts | 0 .../resolver/free-trial-text.resolver.ts | 0 .../trial-initiation/confirmation-details.component.html | 0 .../trial-initiation/confirmation-details.component.ts | 0 .../content/abm-enterprise-content.component.html | 0 .../content/abm-enterprise-content.component.ts | 0 .../content/abm-teams-content.component.html | 0 .../trial-initiation/content/abm-teams-content.component.ts | 0 .../content/cnet-enterprise-content.component.html | 0 .../content/cnet-enterprise-content.component.ts | 0 .../content/cnet-individual-content.component.html | 0 .../content/cnet-individual-content.component.ts | 0 .../content/cnet-teams-content.component.html | 0 .../content/cnet-teams-content.component.ts | 0 .../trial-initiation/content/default-content.component.html | 0 .../trial-initiation/content/default-content.component.ts | 0 .../content/enterprise-content.component.html | 0 .../content/enterprise-content.component.ts | 0 .../content/enterprise1-content.component.html | 0 .../content/enterprise1-content.component.ts | 0 .../content/enterprise2-content.component.html | 0 .../content/enterprise2-content.component.ts | 0 .../trial-initiation/content/logo-badges.component.html | 0 .../trial-initiation/content/logo-badges.component.ts | 0 .../content/logo-cnet-5-stars.component.html | 0 .../trial-initiation/content/logo-cnet-5-stars.component.ts | 0 .../trial-initiation/content/logo-cnet.component.html | 0 .../trial-initiation/content/logo-cnet.component.ts | 0 .../content/logo-company-testimonial.component.html | 0 .../content/logo-company-testimonial.component.ts | 0 .../trial-initiation/content/logo-forbes.component.html | 0 .../trial-initiation/content/logo-forbes.component.ts | 0 .../trial-initiation/content/logo-us-news.component.html | 0 .../trial-initiation/content/logo-us-news.component.ts | 0 .../trial-initiation/content/review-blurb.component.html | 0 .../trial-initiation/content/review-blurb.component.ts | 0 .../trial-initiation/content/review-logo.component.html | 0 .../trial-initiation/content/review-logo.component.ts | 0 .../content/secrets-manager-content.component.html | 0 .../content/secrets-manager-content.component.ts | 0 .../trial-initiation/content/teams-content.component.html | 0 .../trial-initiation/content/teams-content.component.ts | 0 .../trial-initiation/content/teams1-content.component.html | 0 .../trial-initiation/content/teams1-content.component.ts | 0 .../trial-initiation/content/teams2-content.component.html | 0 .../trial-initiation/content/teams2-content.component.ts | 0 .../trial-initiation/content/teams3-content.component.html | 0 .../trial-initiation/content/teams3-content.component.ts | 0 .../secrets-manager-trial-free-stepper.component.html | 0 .../secrets-manager-trial-free-stepper.component.ts | 0 .../secrets-manager-trial-paid-stepper.component.html | 0 .../secrets-manager-trial-paid-stepper.component.ts | 0 .../secrets-manager/secrets-manager-trial.component.html | 0 .../secrets-manager/secrets-manager-trial.component.ts | 0 .../trial-initiation/trial-initiation.component.html | 0 .../trial-initiation/trial-initiation.component.spec.ts | 4 ++-- .../trial-initiation/trial-initiation.component.ts | 4 ++-- .../trial-initiation/trial-initiation.module.ts | 6 +++--- .../vertical-stepper/vertical-step-content.component.html | 0 .../vertical-stepper/vertical-step-content.component.ts | 0 .../vertical-stepper/vertical-step.component.html | 0 .../vertical-stepper/vertical-step.component.ts | 0 .../vertical-stepper/vertical-stepper.component.html | 0 .../vertical-stepper/vertical-stepper.component.ts | 0 .../vertical-stepper/vertical-stepper.module.ts | 0 apps/web/src/app/{core => billing}/types/free-trial.ts | 0 apps/web/src/app/oss-routing.module.ts | 4 ++-- apps/web/src/app/oss.module.ts | 2 +- apps/web/src/app/tools/reports/reports-routing.module.ts | 2 +- .../vault-banners/vault-banners.component.ts | 2 +- apps/web/src/app/vault/individual-vault/vault.component.ts | 2 +- apps/web/src/app/vault/org-vault/vault.component.ts | 2 +- .../src/app/secrets-manager/overview/overview.component.ts | 2 +- .../src/{ => billing}/directives/not-premium.directive.ts | 0 .../src/{ => billing}/directives/premium.directive.ts | 0 libs/angular/src/jslib.module.ts | 2 +- 85 files changed, 21 insertions(+), 21 deletions(-) rename apps/browser/src/{ => billing}/services/families-policy.service.spec.ts (100%) rename apps/browser/src/{ => billing}/services/families-policy.service.ts (100%) rename apps/web/src/app/{core => billing}/guards/has-premium.guard.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts (99%) rename apps/web/src/app/{auth => billing}/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/confirmation-details.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/confirmation-details.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/abm-enterprise-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/abm-enterprise-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/abm-teams-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/abm-teams-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/cnet-enterprise-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/cnet-enterprise-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/cnet-individual-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/cnet-individual-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/cnet-teams-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/cnet-teams-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/default-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/default-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/enterprise-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/enterprise-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/enterprise1-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/enterprise1-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/enterprise2-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/enterprise2-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-badges.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-badges.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-cnet-5-stars.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-cnet-5-stars.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-cnet.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-cnet.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-company-testimonial.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-company-testimonial.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-forbes.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-forbes.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-us-news.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/logo-us-news.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/review-blurb.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/review-blurb.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/review-logo.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/review-logo.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/secrets-manager-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/secrets-manager-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/teams-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/teams-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/teams1-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/teams1-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/teams2-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/teams2-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/teams3-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/content/teams3-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/secrets-manager/secrets-manager-trial.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/secrets-manager/secrets-manager-trial.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/trial-initiation.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/trial-initiation.component.spec.ts (98%) rename apps/web/src/app/{auth => billing}/trial-initiation/trial-initiation.component.ts (98%) rename apps/web/src/app/{auth => billing}/trial-initiation/trial-initiation.module.ts (90%) rename apps/web/src/app/{auth => billing}/trial-initiation/vertical-stepper/vertical-step-content.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/vertical-stepper/vertical-step-content.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/vertical-stepper/vertical-step.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/vertical-stepper/vertical-step.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/vertical-stepper/vertical-stepper.component.html (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/vertical-stepper/vertical-stepper.component.ts (100%) rename apps/web/src/app/{auth => billing}/trial-initiation/vertical-stepper/vertical-stepper.module.ts (100%) rename apps/web/src/app/{core => billing}/types/free-trial.ts (100%) rename libs/angular/src/{ => billing}/directives/not-premium.directive.ts (100%) rename libs/angular/src/{ => billing}/directives/premium.directive.ts (100%) diff --git a/apps/browser/src/services/families-policy.service.spec.ts b/apps/browser/src/billing/services/families-policy.service.spec.ts similarity index 100% rename from apps/browser/src/services/families-policy.service.spec.ts rename to apps/browser/src/billing/services/families-policy.service.spec.ts diff --git a/apps/browser/src/services/families-policy.service.ts b/apps/browser/src/billing/services/families-policy.service.ts similarity index 100% rename from apps/browser/src/services/families-policy.service.ts rename to apps/browser/src/billing/services/families-policy.service.ts diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts index 20e5548f99..a3d1c55397 100644 --- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts @@ -11,11 +11,11 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { DialogService, ItemModule } from "@bitwarden/components"; +import { FamiliesPolicyService } from "../../../../billing/services/families-policy.service"; import { BrowserApi } from "../../../../platform/browser/browser-api"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; -import { FamiliesPolicyService } from "../../../../services/families-policy.service"; @Component({ templateUrl: "more-from-bitwarden-page-v2.component.html", diff --git a/apps/web/src/app/core/guards/has-premium.guard.ts b/apps/web/src/app/billing/guards/has-premium.guard.ts similarity index 100% rename from apps/web/src/app/core/guards/has-premium.guard.ts rename to apps/web/src/app/billing/guards/has-premium.guard.ts diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index 3fb2121b03..a8b2c7a46f 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -23,7 +23,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService, ToastService } from "@bitwarden/components"; -import { FreeTrial } from "../../../core/types/free-trial"; import { TrialFlowService } from "../../services/trial-flow.service"; import { AddCreditDialogResult, @@ -33,6 +32,7 @@ import { AdjustPaymentDialogComponent, AdjustPaymentDialogResultType, } from "../../shared/adjust-payment-dialog/adjust-payment-dialog.component"; +import { FreeTrial } from "../../types/free-trial"; @Component({ templateUrl: "./organization-payment-method.component.html", diff --git a/apps/web/src/app/billing/services/trial-flow.service.ts b/apps/web/src/app/billing/services/trial-flow.service.ts index a3a4ba6bba..eb08e5bd7a 100644 --- a/apps/web/src/app/billing/services/trial-flow.service.ts +++ b/apps/web/src/app/billing/services/trial-flow.service.ts @@ -16,11 +16,11 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService } from "@bitwarden/components"; -import { FreeTrial } from "../../core/types/free-trial"; import { ChangePlanDialogResultType, openChangePlanDialog, } from "../organizations/change-plan-dialog.component"; +import { FreeTrial } from "../types/free-trial"; @Injectable({ providedIn: "root" }) export class TrialFlowService { diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index c5ec942f8b..dc031ade42 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -24,8 +24,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService, ToastService } from "@bitwarden/components"; -import { FreeTrial } from "../../core/types/free-trial"; import { TrialFlowService } from "../services/trial-flow.service"; +import { FreeTrial } from "../types/free-trial"; import { AddCreditDialogResult, openAddCreditDialog } from "./add-credit-dialog.component"; import { diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html rename to apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts similarity index 99% rename from apps/web/src/app/auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts rename to apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 96c50a8131..873ceea2ad 100644 --- a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -25,13 +25,13 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { ToastService } from "@bitwarden/components"; +import { AcceptOrganizationInviteService } from "../../../auth/organization-invite/accept-organization.service"; import { OrganizationCreatedEvent, SubscriptionProduct, TrialOrganizationType, } from "../../../billing/accounts/trial-initiation/trial-billing-step.component"; import { RouterService } from "../../../core/router.service"; -import { AcceptOrganizationInviteService } from "../../organization-invite/accept-organization.service"; import { VerticalStepperComponent } from "../vertical-stepper/vertical-stepper.component"; export type InitiationPath = diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts rename to apps/web/src/app/billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.spec.ts diff --git a/apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts rename to apps/web/src/app/billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver.ts diff --git a/apps/web/src/app/auth/trial-initiation/confirmation-details.component.html b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/confirmation-details.component.html rename to apps/web/src/app/billing/trial-initiation/confirmation-details.component.html diff --git a/apps/web/src/app/auth/trial-initiation/confirmation-details.component.ts b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/confirmation-details.component.ts rename to apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/abm-enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/abm-enterprise-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/abm-enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/abm-enterprise-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/abm-teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/abm-teams-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/abm-teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/abm-teams-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-enterprise-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-enterprise-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-individual-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-individual-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-individual-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-individual-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-teams-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/cnet-teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/cnet-teams-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/default-content.component.html b/apps/web/src/app/billing/trial-initiation/content/default-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/default-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/default-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/default-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/default-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/default-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/default-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-badges.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-badges.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-cnet-5-stars.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-cnet-5-stars.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-cnet-5-stars.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-cnet-5-stars.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-cnet.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-cnet.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-cnet.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-cnet.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-company-testimonial.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-company-testimonial.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-company-testimonial.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-company-testimonial.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-forbes.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-forbes.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-forbes.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-forbes.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-us-news.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-us-news.component.html rename to apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-us-news.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/logo-us-news.component.ts rename to apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/review-blurb.component.html b/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/review-blurb.component.html rename to apps/web/src/app/billing/trial-initiation/content/review-blurb.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/review-blurb.component.ts b/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/review-blurb.component.ts rename to apps/web/src/app/billing/trial-initiation/content/review-blurb.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/review-logo.component.html b/apps/web/src/app/billing/trial-initiation/content/review-logo.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/review-logo.component.html rename to apps/web/src/app/billing/trial-initiation/content/review-logo.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/review-logo.component.ts b/apps/web/src/app/billing/trial-initiation/content/review-logo.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/review-logo.component.ts rename to apps/web/src/app/billing/trial-initiation/content/review-logo.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.html b/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/secrets-manager-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/teams-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/teams-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/teams1-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams1-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/teams1-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/teams2-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams2-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/teams2-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/teams2-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams2-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/teams2-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/content/teams3-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams3-content.component.html rename to apps/web/src/app/billing/trial-initiation/content/teams3-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/content/teams3-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/content/teams3-content.component.ts rename to apps/web/src/app/billing/trial-initiation/content/teams3-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html rename to apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts rename to apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.html b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.html rename to apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.html diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts rename to apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial.component.html b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial.component.html rename to apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.html diff --git a/apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial.component.ts b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/secrets-manager/secrets-manager-trial.component.ts rename to apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.html b/apps/web/src/app/billing/trial-initiation/trial-initiation.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/trial-initiation.component.html rename to apps/web/src/app/billing/trial-initiation/trial-initiation.component.html diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts b/apps/web/src/app/billing/trial-initiation/trial-initiation.component.spec.ts similarity index 98% rename from apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts rename to apps/web/src/app/billing/trial-initiation/trial-initiation.component.spec.ts index 61fc7a6003..c8d4d35fb7 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts +++ b/apps/web/src/app/billing/trial-initiation/trial-initiation.component.spec.ts @@ -22,10 +22,10 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { AcceptOrganizationInviteService } from "../../auth/organization-invite/accept-organization.service"; +import { OrganizationInvite } from "../../auth/organization-invite/organization-invite"; import { RouterService } from "../../core"; import { SharedModule } from "../../shared"; -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; import { TrialInitiationComponent } from "./trial-initiation.component"; import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/trial-initiation.component.ts similarity index 98% rename from apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts rename to apps/web/src/app/billing/trial-initiation/trial-initiation.component.ts index fbe3eb7aa6..2403c28d26 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/trial-initiation.component.ts @@ -23,13 +23,13 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { AcceptOrganizationInviteService } from "../../auth/organization-invite/accept-organization.service"; +import { OrganizationInvite } from "../../auth/organization-invite/organization-invite"; import { OrganizationCreatedEvent, SubscriptionProduct, TrialOrganizationType, } from "../../billing/accounts/trial-initiation/trial-billing-step.component"; -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; import { RouterService } from "./../../core/router.service"; import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts b/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts similarity index 90% rename from apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts rename to apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts index d49621222f..7b81f57e33 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts +++ b/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts @@ -7,11 +7,11 @@ import { FormFieldModule } from "@bitwarden/components"; import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module"; import { RegisterFormModule } from "../../auth/register-form/register-form.module"; -import { SecretsManagerTrialFreeStepperComponent } from "../../auth/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component"; -import { SecretsManagerTrialPaidStepperComponent } from "../../auth/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component"; -import { SecretsManagerTrialComponent } from "../../auth/trial-initiation/secrets-manager/secrets-manager-trial.component"; import { TaxInfoComponent } from "../../billing"; import { TrialBillingStepComponent } from "../../billing/accounts/trial-initiation/trial-billing-step.component"; +import { SecretsManagerTrialFreeStepperComponent } from "../../billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component"; +import { SecretsManagerTrialPaidStepperComponent } from "../../billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component"; +import { SecretsManagerTrialComponent } from "../../billing/trial-initiation/secrets-manager/secrets-manager-trial.component"; import { EnvironmentSelectorModule } from "../../components/environment-selector/environment-selector.module"; import { SharedModule } from "../../shared"; diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step-content.component.html b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step-content.component.html rename to apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.html diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step-content.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step-content.component.ts rename to apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step.component.html b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step.component.html rename to apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.html diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-step.component.ts rename to apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.component.html b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.html similarity index 100% rename from apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.component.html rename to apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.html diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.component.ts rename to apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts diff --git a/apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.module.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.module.ts similarity index 100% rename from apps/web/src/app/auth/trial-initiation/vertical-stepper/vertical-stepper.module.ts rename to apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.module.ts diff --git a/apps/web/src/app/core/types/free-trial.ts b/apps/web/src/app/billing/types/free-trial.ts similarity index 100% rename from apps/web/src/app/core/types/free-trial.ts rename to apps/web/src/app/billing/types/free-trial.ts diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index d03548faf9..064e6f415a 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -72,8 +72,6 @@ import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emerg import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component"; import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module"; import { SsoComponentV1 } from "./auth/sso-v1.component"; -import { CompleteTrialInitiationComponent } from "./auth/trial-initiation/complete-trial-initiation/complete-trial-initiation.component"; -import { freeTrialTextResolver } from "./auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; import { TwoFactorAuthComponent } from "./auth/two-factor-auth.component"; import { TwoFactorComponent } from "./auth/two-factor.component"; import { UpdatePasswordComponent } from "./auth/update-password.component"; @@ -81,6 +79,8 @@ import { UpdateTempPasswordComponent } from "./auth/update-temp-password.compone import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component"; import { SponsoredFamiliesComponent } from "./billing/settings/sponsored-families.component"; +import { CompleteTrialInitiationComponent } from "./billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component"; +import { freeTrialTextResolver } from "./billing/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component"; import { RouteDataProperties } from "./core"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; diff --git a/apps/web/src/app/oss.module.ts b/apps/web/src/app/oss.module.ts index 3f18440d23..0810a138de 100644 --- a/apps/web/src/app/oss.module.ts +++ b/apps/web/src/app/oss.module.ts @@ -2,7 +2,7 @@ import { NgModule } from "@angular/core"; import { AuthModule } from "./auth"; import { LoginModule } from "./auth/login/login.module"; -import { TrialInitiationModule } from "./auth/trial-initiation/trial-initiation.module"; +import { TrialInitiationModule } from "./billing/trial-initiation/trial-initiation.module"; import { LooseComponentsModule, SharedModule } from "./shared"; import { AccessComponent } from "./tools/send/access.component"; import { OrganizationBadgeModule } from "./vault/individual-vault/organization-badge/organization-badge.module"; diff --git a/apps/web/src/app/tools/reports/reports-routing.module.ts b/apps/web/src/app/tools/reports/reports-routing.module.ts index cad6586bb8..941e6eb7d3 100644 --- a/apps/web/src/app/tools/reports/reports-routing.module.ts +++ b/apps/web/src/app/tools/reports/reports-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule, Routes } from "@angular/router"; import { authGuard } from "@bitwarden/angular/auth/guards"; -import { hasPremiumGuard } from "../../core/guards/has-premium.guard"; +import { hasPremiumGuard } from "../../billing/guards/has-premium.guard"; import { BreachReportComponent } from "./pages/breach-report.component"; import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component"; diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts index 161b2ccb7e..5a0c0a535b 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/vault-banners.component.ts @@ -9,7 +9,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { BannerModule } from "@bitwarden/components"; import { VerifyEmailComponent } from "../../../auth/settings/verify-email.component"; -import { FreeTrial } from "../../../core/types/free-trial"; +import { FreeTrial } from "../../../billing/types/free-trial"; import { SharedModule } from "../../../shared"; import { VaultBannersService, VisibleVaultBanner } from "./services/vault-banners.service"; diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 464d074d9d..7215c98020 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -85,7 +85,7 @@ import { } from "@bitwarden/vault"; import { TrialFlowService } from "../../billing/services/trial-flow.service"; -import { FreeTrial } from "../../core/types/free-trial"; +import { FreeTrial } from "../../billing/types/free-trial"; import { SharedModule } from "../../shared/shared.module"; import { AssignCollectionsWebComponent } from "../components/assign-collections"; import { diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index ca1d330ecf..fe76f9842e 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -92,7 +92,7 @@ import { ResellerWarningService, } from "../../billing/services/reseller-warning.service"; import { TrialFlowService } from "../../billing/services/trial-flow.service"; -import { FreeTrial } from "../../core/types/free-trial"; +import { FreeTrial } from "../../billing/types/free-trial"; import { SharedModule } from "../../shared"; import { VaultFilterService } from "../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; import { VaultFilter } from "../../vault/individual-vault/vault-filter/shared/models/vault-filter.model"; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index cb3f601676..7eb28b2bc2 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -33,7 +33,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; import { TrialFlowService } from "@bitwarden/web-vault/app/billing/services/trial-flow.service"; -import { FreeTrial } from "@bitwarden/web-vault/app/core/types/free-trial"; +import { FreeTrial } from "@bitwarden/web-vault/app/billing/types/free-trial"; import { OrganizationCounts } from "../models/view/counts.view"; import { ProjectListView } from "../models/view/project-list.view"; diff --git a/libs/angular/src/directives/not-premium.directive.ts b/libs/angular/src/billing/directives/not-premium.directive.ts similarity index 100% rename from libs/angular/src/directives/not-premium.directive.ts rename to libs/angular/src/billing/directives/not-premium.directive.ts diff --git a/libs/angular/src/directives/premium.directive.ts b/libs/angular/src/billing/directives/premium.directive.ts similarity index 100% rename from libs/angular/src/directives/premium.directive.ts rename to libs/angular/src/billing/directives/premium.directive.ts diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts index b06faacef1..6ef2cf1d4d 100644 --- a/libs/angular/src/jslib.module.ts +++ b/libs/angular/src/jslib.module.ts @@ -30,6 +30,7 @@ import { } from "@bitwarden/components"; import { TwoFactorIconComponent } from "./auth/components/two-factor-icon.component"; +import { NotPremiumDirective } from "./billing/directives/not-premium.directive"; import { DeprecatedCalloutComponent } from "./components/callout.component"; import { A11yInvalidDirective } from "./directives/a11y-invalid.directive"; import { ApiActionDirective } from "./directives/api-action.directive"; @@ -40,7 +41,6 @@ import { IfFeatureDirective } from "./directives/if-feature.directive"; import { InputStripSpacesDirective } from "./directives/input-strip-spaces.directive"; import { InputVerbatimDirective } from "./directives/input-verbatim.directive"; import { LaunchClickDirective } from "./directives/launch-click.directive"; -import { NotPremiumDirective } from "./directives/not-premium.directive"; import { StopClickDirective } from "./directives/stop-click.directive"; import { StopPropDirective } from "./directives/stop-prop.directive"; import { TextDragDirective } from "./directives/text-drag.directive"; From d0018548ed4cdc092f469f4486b4ff6956f046b2 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 28 Jan 2025 12:27:02 -0600 Subject: [PATCH 14/15] PM-17392 Slide out drawer (#13096) --- .../risk-insights/models/password-health.ts | 12 ++ .../services/risk-insights-data.service.ts | 57 +++++- .../all-applications.component.ts | 20 +- .../app-at-risk-members-dialog.component.html | 19 -- .../app-at-risk-members-dialog.component.ts | 35 ---- .../critical-applications.component.html | 2 +- .../critical-applications.component.ts | 20 +- .../org-at-risk-apps-dialog.component.html | 25 --- .../org-at-risk-apps-dialog.component.ts | 24 --- .../org-at-risk-members-dialog.component.html | 25 --- .../org-at-risk-members-dialog.component.ts | 24 --- .../risk-insights.component.html | 177 ++++++++++++------ .../risk-insights.component.ts | 25 ++- 13 files changed, 228 insertions(+), 237 deletions(-) delete mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html delete mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html delete mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts delete mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html delete mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index 7d5e5e255f..2260f9257a 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -117,6 +117,11 @@ export type AtRiskApplicationDetail = { atRiskPasswordCount: number; }; +export type AppAtRiskMembersDialogParams = { + members: MemberDetailsFlat[]; + applicationName: string; +}; + /** * Request to drop a password health report application * Model is expected by the API endpoint @@ -143,4 +148,11 @@ export interface PasswordHealthReportApplicationsRequest { url: string; } +export enum DrawerType { + None = 0, + AppAtRiskMembers = 1, + OrgAtRiskMembers = 2, + OrgAtRiskApps = 3, +} + export type PasswordHealthReportApplicationId = Opaque; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts index 42bab69fca..668fb18725 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-data.service.ts @@ -1,10 +1,15 @@ import { BehaviorSubject } from "rxjs"; import { finalize } from "rxjs/operators"; -import { ApplicationHealthReportDetail } from "../models/password-health"; +import { + AppAtRiskMembersDialogParams, + ApplicationHealthReportDetail, + AtRiskApplicationDetail, + AtRiskMemberDetail, + DrawerType, +} from "../models/password-health"; import { RiskInsightsReportService } from "./risk-insights-report.service"; - export class RiskInsightsDataService { private applicationsSubject = new BehaviorSubject(null); @@ -22,6 +27,12 @@ export class RiskInsightsDataService { private dataLastUpdatedSubject = new BehaviorSubject(null); dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable(); + openDrawer = false; + activeDrawerType: DrawerType = DrawerType.None; + atRiskMemberDetails: AtRiskMemberDetail[] = []; + appAtRiskMembers: AppAtRiskMembersDialogParams | null = null; + atRiskAppDetails: AtRiskApplicationDetail[] | null = null; + constructor(private reportService: RiskInsightsReportService) {} /** @@ -57,4 +68,46 @@ export class RiskInsightsDataService { refreshApplicationsReport(organizationId: string): void { this.fetchApplicationsReport(organizationId, true); } + + isActiveDrawerType = (drawerType: DrawerType): boolean => { + return this.activeDrawerType === drawerType; + }; + + setDrawerForOrgAtRiskMembers = (atRiskMemberDetails: AtRiskMemberDetail[]): void => { + this.resetDrawer(DrawerType.OrgAtRiskMembers); + this.activeDrawerType = DrawerType.OrgAtRiskMembers; + this.atRiskMemberDetails = atRiskMemberDetails; + this.openDrawer = !this.openDrawer; + }; + + setDrawerForAppAtRiskMembers = ( + atRiskMembersDialogParams: AppAtRiskMembersDialogParams, + ): void => { + this.resetDrawer(DrawerType.None); + this.activeDrawerType = DrawerType.AppAtRiskMembers; + this.appAtRiskMembers = atRiskMembersDialogParams; + this.openDrawer = !this.openDrawer; + }; + + setDrawerForOrgAtRiskApps = (atRiskApps: AtRiskApplicationDetail[]): void => { + this.resetDrawer(DrawerType.OrgAtRiskApps); + this.activeDrawerType = DrawerType.OrgAtRiskApps; + this.atRiskAppDetails = atRiskApps; + this.openDrawer = !this.openDrawer; + }; + + closeDrawer = (): void => { + this.resetDrawer(DrawerType.None); + }; + + private resetDrawer = (drawerType: DrawerType): void => { + if (this.activeDrawerType !== drawerType) { + this.openDrawer = false; + } + + this.activeDrawerType = DrawerType.None; + this.atRiskMemberDetails = []; + this.appAtRiskMembers = null; + this.atRiskAppDetails = null; + }; } diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index 8d8112587c..64dc5a21fc 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -26,7 +26,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { - DialogService, Icons, NoItemsModule, SearchModule, @@ -38,9 +37,6 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; -import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component"; -import { OrgAtRiskAppsDialogComponent } from "./org-at-risk-apps-dialog.component"; -import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @Component({ @@ -131,7 +127,6 @@ export class AllApplicationsComponent implements OnInit { protected reportService: RiskInsightsReportService, private accountService: AccountService, protected criticalAppsService: CriticalAppsService, - protected dialogService: DialogService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) @@ -176,24 +171,23 @@ export class AllApplicationsComponent implements OnInit { } showAppAtRiskMembers = async (applicationName: string) => { - openAppAtRiskMembersDialog(this.dialogService, { + const info = { members: this.dataSource.data.find((app) => app.applicationName === applicationName) ?.atRiskMemberDetails ?? [], applicationName, - }); + }; + this.dataService.setDrawerForAppAtRiskMembers(info); }; showOrgAtRiskMembers = async () => { - this.dialogService.open(OrgAtRiskMembersDialogComponent, { - data: this.reportService.generateAtRiskMemberList(this.dataSource.data), - }); + const dialogData = this.reportService.generateAtRiskMemberList(this.dataSource.data); + this.dataService.setDrawerForOrgAtRiskMembers(dialogData); }; showOrgAtRiskApps = async () => { - this.dialogService.open(OrgAtRiskAppsDialogComponent, { - data: this.reportService.generateAtRiskApplicationList(this.dataSource.data), - }); + const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data); + this.dataService.setDrawerForOrgAtRiskApps(data); }; onCheckboxChange(applicationName: string, event: Event) { diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html deleted file mode 100644 index fa58678be0..0000000000 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.html +++ /dev/null @@ -1,19 +0,0 @@ - -
{{ applicationName }} - -
- {{ "atRiskMembersWithCount" | i18n: members.length }} - {{ "atRiskMembersDescriptionWithApp" | i18n: applicationName }} -
- -
{{ member.email }}
-
-
-
-
- - - - diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts deleted file mode 100644 index d6a757fe89..0000000000 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/app-at-risk-members-dialog.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { DIALOG_DATA } from "@angular/cdk/dialog"; -import { CommonModule } from "@angular/common"; -import { Component, Inject } from "@angular/core"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { MemberDetailsFlat } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; -import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; - -type AppAtRiskMembersDialogParams = { - members: MemberDetailsFlat[]; - applicationName: string; -}; - -export const openAppAtRiskMembersDialog = ( - dialogService: DialogService, - dialogConfig: AppAtRiskMembersDialogParams, -) => - dialogService.open(AppAtRiskMembersDialogComponent, { - data: dialogConfig, - }); - -@Component({ - standalone: true, - templateUrl: "./app-at-risk-members-dialog.component.html", - imports: [ButtonModule, CommonModule, JslibModule, DialogModule], -}) -export class AppAtRiskMembersDialogComponent { - protected members: MemberDetailsFlat[]; - protected applicationName: string; - - constructor(@Inject(DIALOG_DATA) private params: AppAtRiskMembersDialogParams) { - this.members = params.members; - this.applicationName = params.applicationName; - } -} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html index 41d256c073..38e017fd5c 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/critical-applications.component.html @@ -43,7 +43,7 @@ > { - openAppAtRiskMembersDialog(this.dialogService, { + const data = { members: this.dataSource.data.find((app) => app.applicationName === applicationName) ?.atRiskMemberDetails ?? [], applicationName, - }); + }; + this.dataService.setDrawerForAppAtRiskMembers(data); }; showOrgAtRiskMembers = async () => { - this.dialogService.open(OrgAtRiskMembersDialogComponent, { - data: this.reportService.generateAtRiskMemberList(this.dataSource.data), - }); + const data = this.reportService.generateAtRiskMemberList(this.dataSource.data); + this.dataService.setDrawerForOrgAtRiskMembers(data); }; showOrgAtRiskApps = async () => { - this.dialogService.open(OrgAtRiskAppsDialogComponent, { - data: this.reportService.generateAtRiskApplicationList(this.dataSource.data), - }); + const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data); + this.dataService.setDrawerForOrgAtRiskApps(data); }; trackByFunction(_: number, item: ApplicationHealthReportDetailWithCriticalFlag) { diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html deleted file mode 100644 index 298011b215..0000000000 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html +++ /dev/null @@ -1,25 +0,0 @@ - - - {{ "atRiskApplicationsWithCount" | i18n: atRiskApps.length }} - - -
- {{ "atRiskApplicationsDescription" | i18n }} -
-
{{ "application" | i18n }}
-
{{ "atRiskPasswords" | i18n }}
-
- -
-
{{ app.applicationName }}
-
{{ app.atRiskPasswordCount }}
-
-
-
-
- - - -
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts deleted file mode 100644 index 0ae00f6087..0000000000 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { DIALOG_DATA } from "@angular/cdk/dialog"; -import { CommonModule } from "@angular/common"; -import { Component, Inject } from "@angular/core"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { AtRiskApplicationDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; -import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; - -export const openOrgAtRiskMembersDialog = ( - dialogService: DialogService, - dialogConfig: AtRiskApplicationDetail[], -) => - dialogService.open(OrgAtRiskAppsDialogComponent, { - data: dialogConfig, - }); - -@Component({ - standalone: true, - templateUrl: "./org-at-risk-apps-dialog.component.html", - imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], -}) -export class OrgAtRiskAppsDialogComponent { - constructor(@Inject(DIALOG_DATA) protected atRiskApps: AtRiskApplicationDetail[]) {} -} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html deleted file mode 100644 index 1f1de10366..0000000000 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.html +++ /dev/null @@ -1,25 +0,0 @@ - - - {{ "atRiskMembersWithCount" | i18n: atRiskMembers.length }} - - -
- {{ "atRiskMembersDescription" | i18n }} -
-
{{ "email" | i18n }}
-
{{ "atRiskPasswords" | i18n }}
-
- -
-
{{ member.email }}
-
{{ member.atRiskPasswordCount }}
-
-
-
-
- - - -
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts deleted file mode 100644 index 72518843d9..0000000000 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-members-dialog.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { DIALOG_DATA } from "@angular/cdk/dialog"; -import { CommonModule } from "@angular/common"; -import { Component, Inject } from "@angular/core"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { AtRiskMemberDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; -import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; - -export const openOrgAtRiskMembersDialog = ( - dialogService: DialogService, - dialogConfig: AtRiskMemberDetail[], -) => - dialogService.open(OrgAtRiskMembersDialogComponent, { - data: dialogConfig, - }); - -@Component({ - standalone: true, - templateUrl: "./org-at-risk-members-dialog.component.html", - imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], -}) -export class OrgAtRiskMembersDialogComponent { - constructor(@Inject(DIALOG_DATA) protected atRiskMembers: AtRiskMemberDetail[]) {} -} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html index ae8bd94e5f..a368f5c0c1 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html @@ -1,57 +1,126 @@ -
{{ "accessIntelligence" | i18n }}
-

{{ "riskInsights" | i18n }}

-
- {{ "reviewAtRiskPasswords" | i18n }} -
-
+ + + + + + + + {{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }} + + + + + + + + + + + + + + + + + + + + {{ + "atRiskMembersDescription" | i18n + }} +
+
{{ "email" | i18n }}
+
{{ "atRiskPasswords" | i18n }}
+
+ +
+
{{ member.email }}
+
{{ member.atRiskPasswordCount }}
+
+
+
+
+ + + + + +
+ {{ "atRiskMembersWithCount" | i18n: dataService.appAtRiskMembers.members.length }} +
+
+ {{ + "atRiskMembersDescriptionWithApp" | i18n: dataService.appAtRiskMembers.applicationName + }} +
+
+ +
{{ member.email }}
+
+
+
+
+ + + + + + + {{ + "atRiskApplicationsDescription" | i18n + }} +
+
{{ "application" | i18n }}
+
{{ "atRiskPasswords" | i18n }}
+
+ +
+
{{ app.applicationName }}
+
{{ app.atRiskPasswordCount }}
+
+
+
+
+
+ diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts index 6d39a710e2..20dc320de2 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts @@ -12,6 +12,7 @@ import { } from "@bitwarden/bit-common/tools/reports/risk-insights"; import { ApplicationHealthReportDetail, + DrawerType, PasswordHealthReportApplicationsResponse, } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; // eslint-disable-next-line no-restricted-imports -- used for dependency injection @@ -19,7 +20,15 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components"; +import { + AsyncActionsModule, + ButtonModule, + DrawerBodyComponent, + DrawerComponent, + DrawerHeaderComponent, + LayoutComponent, + TabsModule, +} from "@bitwarden/components"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { AllApplicationsComponent } from "./all-applications.component"; @@ -51,6 +60,10 @@ export enum RiskInsightsTabType { PasswordHealthMembersURIComponent, NotifiedMembersTableComponent, TabsModule, + DrawerComponent, + DrawerBodyComponent, + DrawerHeaderComponent, + LayoutComponent, ], }) export class RiskInsightsComponent implements OnInit { @@ -77,7 +90,7 @@ export class RiskInsightsComponent implements OnInit { private route: ActivatedRoute, private router: Router, private configService: ConfigService, - private dataService: RiskInsightsDataService, + protected dataService: RiskInsightsDataService, private criticalAppsService: CriticalAppsService, ) { this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => { @@ -137,5 +150,13 @@ export class RiskInsightsComponent implements OnInit { queryParams: { tabIndex: newIndex }, queryParamsHandling: "merge", }); + + // close drawer when tabs are changed + this.dataService.closeDrawer(); + } + + // Get a list of drawer types + get drawerTypes(): typeof DrawerType { + return DrawerType; } } From 222392d1fa2a1c6e3198742ff19d4349f98e522a Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Tue, 28 Jan 2025 16:01:07 -0500 Subject: [PATCH 15/15] [PM-12444] remove ngx infinite scroll dependency (#13056) * replace provider clients components with vNext implementation * remove ngx-infinite-scroll dependency * fix ts strict errors --- apps/web/src/app/app.module.ts | 2 -- apps/web/src/app/shared/shared.module.ts | 3 --- .../providers/clients/clients.component.ts | 2 +- bitwarden_license/bit-web/src/app/app.module.ts | 2 -- package-lock.json | 13 ------------- package.json | 1 - 6 files changed, 1 insertion(+), 22 deletions(-) diff --git a/apps/web/src/app/app.module.ts b/apps/web/src/app/app.module.ts index 22fd745eab..ed48e4a734 100644 --- a/apps/web/src/app/app.module.ts +++ b/apps/web/src/app/app.module.ts @@ -3,7 +3,6 @@ import { LayoutModule } from "@angular/cdk/layout"; import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; -import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { AppComponent } from "./app.component"; import { CoreModule } from "./core"; @@ -23,7 +22,6 @@ import { WildcardRoutingModule } from "./wildcard-routing.module"; BrowserAnimationsModule, FormsModule, CoreModule, - InfiniteScrollDirective, DragDropModule, LayoutModule, OssRoutingModule, diff --git a/apps/web/src/app/shared/shared.module.ts b/apps/web/src/app/shared/shared.module.ts index 8f44d8a4bf..1ad17139db 100644 --- a/apps/web/src/app/shared/shared.module.ts +++ b/apps/web/src/app/shared/shared.module.ts @@ -3,7 +3,6 @@ import { CommonModule, DatePipe } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { RouterModule } from "@angular/router"; -import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -49,7 +48,6 @@ import "./locales"; DragDropModule, FormsModule, ReactiveFormsModule, - InfiniteScrollDirective, RouterModule, JslibModule, @@ -86,7 +84,6 @@ import "./locales"; DragDropModule, FormsModule, ReactiveFormsModule, - InfiniteScrollDirective, RouterModule, JslibModule, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts index 09195cd22f..f830b149db 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/clients.component.ts @@ -139,7 +139,7 @@ export class ClientsComponent { async load() { const response = await this.apiService.getProviderClients(this.providerId); - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const clients = response.data != null && response.data.length > 0 ? response.data : []; this.dataSource.data = clients; this.manageOrganizations = diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index 3a78ae0ed0..833a67e151 100644 --- a/bitwarden_license/bit-web/src/app/app.module.ts +++ b/bitwarden_license/bit-web/src/app/app.module.ts @@ -4,7 +4,6 @@ import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { RouterModule } from "@angular/router"; -import { InfiniteScrollDirective } from "ngx-infinite-scroll"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CoreModule } from "@bitwarden/web-vault/app/core"; @@ -37,7 +36,6 @@ import { AccessIntelligenceModule } from "./tools/access-intelligence/access-int FormsModule, ReactiveFormsModule, CoreModule, - InfiniteScrollDirective, DragDropModule, AppRoutingModule, OssRoutingModule, diff --git a/package-lock.json b/package-lock.json index 005fd25d86..8f15b9aab7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,6 @@ "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "1.4.5-lts.1", - "ngx-infinite-scroll": "18.0.0", "ngx-toastr": "19.0.0", "node-fetch": "2.6.12", "node-forge": "1.3.1", @@ -24605,18 +24604,6 @@ "node": ">= 10" } }, - "node_modules/ngx-infinite-scroll": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-18.0.0.tgz", - "integrity": "sha512-D183TDwpsd9Zl56UmItsl3RzHdN25srAISfg6lc3A8mEKkEgOq0s7ZzRAYcx8DHsAkMgtZqjIPEvMifD3DOB/g==", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": ">=18.0.0 <19.0.0", - "@angular/core": ">=18.0.0 <19.0.0" - } - }, "node_modules/ngx-toastr": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-19.0.0.tgz", diff --git a/package.json b/package.json index fc4f1a49fb..1d570ac4e6 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,6 @@ "lowdb": "1.0.0", "lunr": "2.3.9", "multer": "1.4.5-lts.1", - "ngx-infinite-scroll": "18.0.0", "ngx-toastr": "19.0.0", "node-fetch": "2.6.12", "node-forge": "1.3.1",