From 7e86c0afd4ffeb6e0a91b4ed942f8b923904ea23 Mon Sep 17 00:00:00 2001 From: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Date: Wed, 5 Jun 2024 13:45:08 +0530 Subject: [PATCH 01/12] [PM-5014] Download license component migration (#8443) * Download license component migration * download license dialog component migration * download license dialog component migration --- .../download-license.component.html | 68 ++++++++-------- .../download-license.component.ts | 79 +++++++++++-------- .../organization-billing.module.ts | 4 +- ...nization-subscription-cloud.component.html | 7 -- ...ganization-subscription-cloud.component.ts | 9 ++- 5 files changed, 86 insertions(+), 81 deletions(-) diff --git a/apps/web/src/app/billing/organizations/download-license.component.html b/apps/web/src/app/billing/organizations/download-license.component.html index 0997462ce92..33a534bacf7 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.html +++ b/apps/web/src/app/billing/organizations/download-license.component.html @@ -1,39 +1,35 @@ -
-
- -

{{ "downloadLicense" | i18n }}

-
-
-
- - - - + + + {{ "downloadLicense" | i18n }} + +
+
+ + {{ "enterInstallationId" | i18n }} + + + + + +
-
-
- - -
+ + + + + + diff --git a/apps/web/src/app/billing/organizations/download-license.component.ts b/apps/web/src/app/billing/organizations/download-license.component.ts index 88a37a28aab..6b3a93548b4 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.ts +++ b/apps/web/src/app/billing/organizations/download-license.component.ts @@ -1,50 +1,61 @@ -import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { DialogConfig, DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { DialogService } from "@bitwarden/components"; + +export enum DownloadLicenseDialogResult { + Cancelled = "cancelled", + Downloaded = "downloaded", +} +type DownloadLicenseDialogData = { + /** current organization id */ + organizationId: string; +}; @Component({ - selector: "app-download-license", templateUrl: "download-license.component.html", }) -export class DownloadLicenseComponent { - @Input() organizationId: string; - @Output() onDownloaded = new EventEmitter(); - @Output() onCanceled = new EventEmitter(); - - installationId: string; - formPromise: Promise; - +export class DownloadLicenceDialogComponent { + licenseForm = this.formBuilder.group({ + installationId: ["", [Validators.required]], + }); constructor( + @Inject(DIALOG_DATA) protected data: DownloadLicenseDialogData, + private dialogRef: DialogRef, private fileDownloadService: FileDownloadService, - private logService: LogService, private organizationApiService: OrganizationApiServiceAbstraction, + protected formBuilder: FormBuilder, ) {} - async submit() { - if (this.installationId == null || this.installationId === "") { + submit = async () => { + this.licenseForm.markAllAsTouched(); + const installationId = this.licenseForm.get("installationId").value; + if (installationId == null || installationId === "") { return; } - - try { - this.formPromise = this.organizationApiService.getLicense( - this.organizationId, - this.installationId, - ); - const license = await this.formPromise; - const licenseString = JSON.stringify(license, null, 2); - this.fileDownloadService.download({ - fileName: "bitwarden_organization_license.json", - blobData: licenseString, - }); - this.onDownloaded.emit(); - } catch (e) { - this.logService.error(e); - } - } - - cancel() { - this.onCanceled.emit(); + const license = await this.organizationApiService.getLicense( + this.data.organizationId, + installationId, + ); + const licenseString = JSON.stringify(license, null, 2); + this.fileDownloadService.download({ + fileName: "bitwarden_organization_license.json", + blobData: licenseString, + }); + this.dialogRef.close(DownloadLicenseDialogResult.Downloaded); + }; + /** + * Strongly typed helper to open a DownloadLicenceDialogComponent + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ + static open(dialogService: DialogService, config: DialogConfig) { + return dialogService.open(DownloadLicenceDialogComponent, config); } + cancel = () => { + this.dialogRef.close(DownloadLicenseDialogResult.Cancelled); + }; } diff --git a/apps/web/src/app/billing/organizations/organization-billing.module.ts b/apps/web/src/app/billing/organizations/organization-billing.module.ts index 490ebafbff0..a95efe32e47 100644 --- a/apps/web/src/app/billing/organizations/organization-billing.module.ts +++ b/apps/web/src/app/billing/organizations/organization-billing.module.ts @@ -8,7 +8,7 @@ import { AdjustSubscription } from "./adjust-subscription.component"; import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; import { BillingSyncKeyComponent } from "./billing-sync-key.component"; import { ChangePlanComponent } from "./change-plan.component"; -import { DownloadLicenseComponent } from "./download-license.component"; +import { DownloadLicenceDialogComponent } from "./download-license.component"; import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component"; import { OrganizationBillingRoutingModule } from "./organization-billing-routing.module"; import { OrganizationPlansComponent } from "./organization-plans.component"; @@ -32,7 +32,7 @@ import { SubscriptionStatusComponent } from "./subscription-status.component"; BillingSyncApiKeyComponent, BillingSyncKeyComponent, ChangePlanComponent, - DownloadLicenseComponent, + DownloadLicenceDialogComponent, OrganizationSubscriptionCloudComponent, OrganizationSubscriptionSelfhostComponent, OrgBillingHistoryViewComponent, diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index 25ac3a7a155..e11cf602ad2 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -246,13 +246,6 @@ {{ (hasBillingSyncToken ? "manageBillingSync" : "setUpBillingSync") | i18n }}
-
- -

{{ "additionalOptions" | i18n }}

diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index a0db7b5a200..b6282f1e7b1 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -29,6 +29,7 @@ import { } from "../shared/offboarding-survey.component"; import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; +import { DownloadLicenceDialogComponent } from "./download-license.component"; import { ManageBilling } from "./icons/manage-billing.icon"; import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.component"; @@ -354,8 +355,12 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy this.showChangePlan = false; } - downloadLicense() { - this.showDownloadLicense = !this.showDownloadLicense; + async downloadLicense() { + DownloadLicenceDialogComponent.open(this.dialogService, { + data: { + organizationId: this.organizationId, + }, + }); } async manageBillingSync() { From cb0927ac5d9e338cf0282dbb4ea01211698f3b3a Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Wed, 5 Jun 2024 10:06:52 -0400 Subject: [PATCH 02/12] [PM-8553] browser v2 search bar defects (#9506) * update no results icon and no results scrolling * update v2 search so the term persist when the user clicks into an item and exits the item --- .../vault-search/vault-v2-search.component.ts | 35 ++++++++++++++----- .../components/vault/vault-v2.component.html | 13 +++---- .../components/vault/vault-v2.component.ts | 5 +-- .../services/vault-popup-items.service.ts | 9 ++--- libs/components/src/icon/icons/index.ts | 1 + libs/components/src/icon/icons/no-results.ts | 18 ++++++++++ 6 files changed, 56 insertions(+), 25 deletions(-) create mode 100644 libs/components/src/icon/icons/no-results.ts diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts index 321717285a9..e6100321607 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -1,12 +1,14 @@ import { CommonModule } from "@angular/common"; -import { Component, Output, EventEmitter } from "@angular/core"; +import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; -import { Subject, debounceTime } from "rxjs"; +import { Subject, Subscription, debounceTime, filter } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; +import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; + const SearchTextDebounceInterval = 200; @Component({ @@ -17,19 +19,34 @@ const SearchTextDebounceInterval = 200; }) export class VaultV2SearchComponent { searchText: string; - @Output() searchTextChanged = new EventEmitter(); private searchText$ = new Subject(); - constructor() { - this.searchText$ - .pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed()) - .subscribe((data) => { - this.searchTextChanged.emit(data); - }); + constructor(private vaultPopupItemsService: VaultPopupItemsService) { + this.subscribeToLatestSearchText(); + this.subscribeToApplyFilter(); } onSearchTextChanged() { this.searchText$.next(this.searchText); } + + subscribeToLatestSearchText(): Subscription { + return this.vaultPopupItemsService.latestSearchText$ + .pipe( + takeUntilDestroyed(), + filter((data) => !!data), + ) + .subscribe((text) => { + this.searchText = text; + }); + } + + subscribeToApplyFilter(): Subscription { + return this.searchText$ + .pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed()) + .subscribe((data) => { + this.vaultPopupItemsService.applyFilter(data); + }); + } } diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html index f99d3cbb303..9f38fd61fab 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html @@ -22,18 +22,15 @@

-
- - + - -
+
- + {{ "noItemsMatchSearch" | i18n }} {{ "clearFiltersOrTryAnother" | i18n }} @@ -41,7 +38,7 @@
{{ "organizationIsDeactivated" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts index 5e9487ac880..5e91f196a30 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.ts @@ -44,6 +44,7 @@ export class VaultV2Component implements OnInit, OnDestroy { protected vaultIcon = Icons.Vault; protected deactivatedIcon = Icons.DeactivatedOrg; + protected noResultsIcon = Icons.NoResults; constructor( private vaultPopupItemsService: VaultPopupItemsService, @@ -54,10 +55,6 @@ export class VaultV2Component implements OnInit, OnDestroy { ngOnDestroy(): void {} - handleSearchTextChange(searchText: string) { - this.vaultPopupItemsService.applyFilter(searchText); - } - addCipher() { // TODO: Add currently filtered organization to query params if available void this.router.navigate(["/add-cipher"], {}); diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index eacb8e013ef..ab98722d060 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -38,7 +38,8 @@ import { MY_VAULT_ID, VaultPopupListFiltersService } from "./vault-popup-list-fi }) export class VaultPopupItemsService { private _refreshCurrentTab$ = new Subject(); - private searchText$ = new BehaviorSubject(""); + private _searchText$ = new BehaviorSubject(""); + latestSearchText$: Observable = this._searchText$.asObservable(); /** * Observable that contains the list of other cipher types that should be shown @@ -105,7 +106,7 @@ export class VaultPopupItemsService { private _filteredCipherList$: Observable = combineLatest([ this._cipherList$, - this.searchText$, + this._searchText$, this.vaultPopupListFiltersService.filterFunction$, ]).pipe( map(([ciphers, searchText, filterFunction]): [CipherView[], string] => [ @@ -179,7 +180,7 @@ export class VaultPopupItemsService { * Observable that indicates whether a filter is currently applied to the ciphers. */ hasFilterApplied$ = combineLatest([ - this.searchText$, + this._searchText$, this.vaultPopupListFiltersService.filters$, ]).pipe( switchMap(([searchText, filters]) => { @@ -242,7 +243,7 @@ export class VaultPopupItemsService { } applyFilter(newSearchText: string) { - this.searchText$.next(newSearchText); + this._searchText$.next(newSearchText); } /** diff --git a/libs/components/src/icon/icons/index.ts b/libs/components/src/icon/icons/index.ts index 9de81f19913..ea583031f61 100644 --- a/libs/components/src/icon/icons/index.ts +++ b/libs/components/src/icon/icons/index.ts @@ -2,3 +2,4 @@ export * from "./deactivated-org"; export * from "./search"; export * from "./no-access"; export * from "./vault"; +export * from "./no-results"; diff --git a/libs/components/src/icon/icons/no-results.ts b/libs/components/src/icon/icons/no-results.ts new file mode 100644 index 00000000000..f68e67f88ce --- /dev/null +++ b/libs/components/src/icon/icons/no-results.ts @@ -0,0 +1,18 @@ +import { svgIcon } from "../icon"; + +export const NoResults = svgIcon` + + + + + + + + + + + + + + +`; From 1aaa88a64d4d48aa75868980eb4c280b672057c3 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:48:12 -0400 Subject: [PATCH 03/12] [PM-7837] Move `SyncService` to Platform Ownership (#9055) * Move * Update References In Unowned Files * Update References In Vault Files * Update Web AppComponent * Add Import --- .../browser/src/background/main.background.ts | 13 ++- .../src/popup/services/services.module.ts | 2 +- .../components/vault/current-tab.component.ts | 2 +- .../vault/vault-filter.component.ts | 2 +- .../vault/popup/settings/sync.component.ts | 2 +- apps/cli/src/service-container.ts | 10 +-- apps/cli/src/vault/sync.command.ts | 2 +- apps/desktop/src/app/app.component.ts | 2 +- apps/desktop/src/app/services/init.service.ts | 2 +- .../src/vault/app/vault/vault.component.ts | 2 +- apps/web/src/app/app.component.ts | 2 +- .../src/app/layouts/user-layout.component.ts | 2 +- .../organization-options.component.ts | 2 +- .../vault/individual-vault/vault.component.ts | 2 +- .../app/vault/org-vault/vault.component.ts | 2 +- .../vault/settings/purge-vault.component.ts | 2 +- .../src/services/jslib-services.module.ts | 22 ++--- libs/common/src/abstractions/api.service.ts | 2 +- .../sync/default-sync.service.ts} | 89 ++++++++++--------- libs/common/src/platform/sync/index.ts | 2 + libs/common/src/platform/sync/internal.ts | 1 + .../sync}/sync-event-args.ts | 2 +- .../sync}/sync.response.ts | 17 ++-- libs/common/src/platform/sync/sync.service.ts | 58 ++++++++++++ libs/common/src/services/api.service.ts | 2 +- .../src/services/notifications.service.ts | 2 +- .../sync/sync-notifier.service.abstraction.ts | 8 -- .../sync/sync.service.abstraction.ts | 21 +---- .../services/sync/sync-notifier.service.ts | 18 ---- 29 files changed, 152 insertions(+), 143 deletions(-) rename libs/common/src/{vault/services/sync/sync.service.ts => platform/sync/default-sync.service.ts} (74%) create mode 100644 libs/common/src/platform/sync/index.ts rename libs/common/src/{vault/types => platform/sync}/sync-event-args.ts (94%) rename libs/common/src/{vault/models/response => platform/sync}/sync.response.ts (69%) create mode 100644 libs/common/src/platform/sync/sync.service.ts delete mode 100644 libs/common/src/vault/abstractions/sync/sync-notifier.service.abstraction.ts delete mode 100644 libs/common/src/vault/services/sync/sync-notifier.service.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 63721466f6f..a8654c92f03 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -136,6 +136,9 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; /* eslint-enable import/no-restricted-paths */ +import { SyncService } from "@bitwarden/common/platform/sync"; +// eslint-disable-next-line no-restricted-imports -- Needed for service creation +import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal"; import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { ApiService } from "@bitwarden/common/services/api.service"; import { AuditService } from "@bitwarden/common/services/audit.service"; @@ -166,8 +169,6 @@ import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/co import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction"; -import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -176,8 +177,6 @@ import { CollectionService } from "@bitwarden/common/vault/services/collection.s import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; -import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; -import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service"; import { @@ -268,7 +267,7 @@ export default class MainBackground { collectionService: CollectionServiceAbstraction; vaultTimeoutService: VaultTimeoutService; vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction; - syncService: SyncServiceAbstraction; + syncService: SyncService; passwordGenerationService: PasswordGenerationServiceAbstraction; passwordStrengthService: PasswordStrengthServiceAbstraction; totpService: TotpServiceAbstraction; @@ -306,7 +305,6 @@ export default class MainBackground { policyApiService: PolicyApiServiceAbstraction; sendApiService: SendApiServiceAbstraction; userVerificationApiService: UserVerificationApiServiceAbstraction; - syncNotifierService: SyncNotifierServiceAbstraction; fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction; fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction; fido2ClientService: Fido2ClientServiceAbstraction; @@ -638,7 +636,6 @@ export default class MainBackground { this.i18nService, this.stateProvider, ); - this.syncNotifierService = new SyncNotifierService(); this.autofillSettingsService = new AutofillSettingsService( this.stateProvider, @@ -827,7 +824,7 @@ export default class MainBackground { messageListener, ); } else { - this.syncService = new SyncService( + this.syncService = new DefaultSyncService( this.masterPasswordService, this.accountService, this.apiService, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index ace9af3dfa8..d61fa3b19c0 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -80,11 +80,11 @@ import { } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- Used for dependency injection import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { DialogService, ToastService } from "@bitwarden/components"; diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts index 4d2674fd703..24ca030284f 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts @@ -14,8 +14,8 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts index deb4434df47..b46b4cf9ff2 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts @@ -9,8 +9,8 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; diff --git a/apps/browser/src/vault/popup/settings/sync.component.ts b/apps/browser/src/vault/popup/settings/sync.component.ts index 3fe4de9eb51..16f388804bb 100644 --- a/apps/browser/src/vault/popup/settings/sync.component.ts +++ b/apps/browser/src/vault/popup/settings/sync.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; @Component({ selector: "app-sync", diff --git a/apps/cli/src/service-container.ts b/apps/cli/src/service-container.ts index 53039e91473..ff4eb52b84e 100644 --- a/apps/cli/src/service-container.ts +++ b/apps/cli/src/service-container.ts @@ -97,6 +97,9 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service"; /* eslint-enable import/no-restricted-paths */ +import { SyncService } from "@bitwarden/common/platform/sync"; +// eslint-disable-next-line no-restricted-imports -- Needed for service construction +import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal"; import { AuditService } from "@bitwarden/common/services/audit.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; @@ -120,8 +123,6 @@ import { CollectionService } from "@bitwarden/common/vault/services/collection.s import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; -import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; -import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { ImportApiService, @@ -216,7 +217,6 @@ export class ServiceContainer { folderApiService: FolderApiService; userVerificationApiService: UserVerificationApiService; organizationApiService: OrganizationApiServiceAbstraction; - syncNotifierService: SyncNotifierService; sendApiService: SendApiService; devicesApiService: DevicesApiServiceAbstraction; deviceTrustService: DeviceTrustServiceAbstraction; @@ -440,8 +440,6 @@ export class ServiceContainer { customUserAgent, ); - this.syncNotifierService = new SyncNotifierService(); - this.organizationApiService = new OrganizationApiService(this.apiService, this.syncService); this.containerService = new ContainerService(this.cryptoService, this.encryptService); @@ -648,7 +646,7 @@ export class ServiceContainer { this.avatarService = new AvatarService(this.apiService, this.stateProvider); - this.syncService = new SyncService( + this.syncService = new DefaultSyncService( this.masterPasswordService, this.accountService, this.apiService, diff --git a/apps/cli/src/vault/sync.command.ts b/apps/cli/src/vault/sync.command.ts index 073b9b5df48..c3c6f637538 100644 --- a/apps/cli/src/vault/sync.command.ts +++ b/apps/cli/src/vault/sync.command.ts @@ -1,4 +1,4 @@ -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { Response } from "../models/response"; import { MessageResponse } from "../models/response/message.response"; diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 561e9b2df9c..e4fdd17dc15 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -42,13 +42,13 @@ import { SystemService } from "@bitwarden/common/platform/abstractions/system.se import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UserId } from "@bitwarden/common/types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { DialogService, ToastOptions, ToastService } from "@bitwarden/components"; diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 0452e9be837..8793587300f 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -15,10 +15,10 @@ import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwar import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; +import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/platform/sync"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { I18nRendererService } from "../../platform/services/i18n.renderer.service"; import { NativeMessagingService } from "../../services/native-messaging.service"; diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index 208bbc70f03..37992ecea0e 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -23,7 +23,7 @@ import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broa import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 254f23eeb23..c9fbf359f0f 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -35,12 +35,12 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastOptions, ToastService } from "@bitwarden/components"; import { PolicyListService } from "./admin-console/core/policy-list.service"; diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index 1ce8d4d2278..757b8220f3a 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -10,7 +10,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components"; import { PaymentMethodWarningsModule } from "../billing/shared"; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index 8dd63e62ddb..5a138c3147b 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -13,7 +13,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; 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 { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService } from "@bitwarden/components"; import { OrganizationUserResetPasswordService } from "../../../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.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 ca04b3aa51f..ae3a0657788 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -45,9 +45,9 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data"; 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 514cb8150d1..dfdce5c818e 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -48,10 +48,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; diff --git a/apps/web/src/app/vault/settings/purge-vault.component.ts b/apps/web/src/app/vault/settings/purge-vault.component.ts index 869cbaab1b4..9a677af7b5d 100644 --- a/apps/web/src/app/vault/settings/purge-vault.component.ts +++ b/apps/web/src/app/vault/settings/purge-vault.component.ts @@ -8,7 +8,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { Verification } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService } from "@bitwarden/components"; export interface PurgeVaultDialogData { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 048c1829001..8c676bdb9d9 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -187,6 +187,9 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; import { StateEventRunnerService } from "@bitwarden/common/platform/state/state-event-runner.service"; /* eslint-enable import/no-restricted-paths */ +import { SyncService } from "@bitwarden/common/platform/sync"; +// eslint-disable-next-line no-restricted-imports -- Needed for DI +import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal"; import { DefaultThemeStateService, ThemeStateService, @@ -226,8 +229,6 @@ import { FolderService as FolderServiceAbstraction, InternalFolderService, } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction"; -import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; @@ -235,8 +236,6 @@ import { CollectionService } from "@bitwarden/common/vault/services/collection.s import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; -import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; -import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service"; import { ToastService } from "@bitwarden/components"; @@ -644,8 +643,8 @@ const safeProviders: SafeProvider[] = [ deps: [ApiServiceAbstraction, FileUploadServiceAbstraction, InternalSendService], }), safeProvider({ - provide: SyncServiceAbstraction, - useClass: SyncService, + provide: SyncService, + useClass: DefaultSyncService, deps: [ InternalMasterPasswordServiceAbstraction, AccountServiceAbstraction, @@ -796,7 +795,7 @@ const safeProviders: SafeProvider[] = [ useClass: devFlagEnabled("noopNotifications") ? NoopNotificationsService : NotificationsService, deps: [ LogService, - SyncServiceAbstraction, + SyncService, AppIdServiceAbstraction, ApiServiceAbstraction, EnvironmentService, @@ -942,12 +941,7 @@ const safeProviders: SafeProvider[] = [ // it depends on SyncService so that new data can be retrieved through the sync // rather than updating the OrganizationService directly. Instead OrganizationService // subscribes to sync notifications and will update itself based on that. - deps: [ApiServiceAbstraction, SyncServiceAbstraction], - }), - safeProvider({ - provide: SyncNotifierServiceAbstraction, - useClass: SyncNotifierService, - deps: [], + deps: [ApiServiceAbstraction, SyncService], }), safeProvider({ provide: DefaultConfigService, @@ -1122,7 +1116,7 @@ const safeProviders: SafeProvider[] = [ EncryptService, I18nServiceAbstraction, OrganizationApiServiceAbstraction, - SyncServiceAbstraction, + SyncService, ], }), safeProvider({ diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 73e4f74e63f..ed43849d62b 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -103,6 +103,7 @@ import { EventResponse } from "../models/response/event.response"; import { ListResponse } from "../models/response/list.response"; import { ProfileResponse } from "../models/response/profile.response"; import { UserKeyResponse } from "../models/response/user-key.response"; +import { SyncResponse } from "../platform/sync"; import { UserId } from "../types/guid"; import { AttachmentRequest } from "../vault/models/request/attachment.request"; import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request"; @@ -124,7 +125,6 @@ import { CollectionResponse, } from "../vault/models/response/collection.response"; import { OptionalCipherResponse } from "../vault/models/response/optional-cipher.response"; -import { SyncResponse } from "../vault/models/response/sync.response"; /** * @deprecated The `ApiService` class is deprecated and calls should be extracted into individual diff --git a/libs/common/src/vault/services/sync/sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts similarity index 74% rename from libs/common/src/vault/services/sync/sync.service.ts rename to libs/common/src/platform/sync/default-sync.service.ts index 109ecea0358..5058288487f 100644 --- a/libs/common/src/vault/services/sync/sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -1,50 +1,53 @@ import { firstValueFrom } from "rxjs"; -import { LogoutReason, UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { UserDecryptionOptionsServiceAbstraction } from "../../../../auth/src/common/abstractions"; +import { LogoutReason } from "../../../../auth/src/common/types"; +import { ApiService } from "../../abstractions/api.service"; +import { InternalOrganizationServiceAbstraction } from "../../admin-console/abstractions/organization/organization.service.abstraction"; +import { InternalPolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; +import { ProviderService } from "../../admin-console/abstractions/provider.service"; +import { OrganizationUserType } from "../../admin-console/enums"; +import { OrganizationData } from "../../admin-console/models/data/organization.data"; +import { PolicyData } from "../../admin-console/models/data/policy.data"; +import { ProviderData } from "../../admin-console/models/data/provider.data"; +import { PolicyResponse } from "../../admin-console/models/response/policy.response"; +import { AccountService } from "../../auth/abstractions/account.service"; +import { AuthService } from "../../auth/abstractions/auth.service"; +import { AvatarService } from "../../auth/abstractions/avatar.service"; +import { KeyConnectorService } from "../../auth/abstractions/key-connector.service"; +import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction"; +import { TokenService } from "../../auth/abstractions/token.service"; +import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason"; +import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; +import { BillingAccountProfileStateService } from "../../billing/abstractions"; +import { DomainsResponse } from "../../models/response/domains.response"; +import { ProfileResponse } from "../../models/response/profile.response"; +import { SendData } from "../../tools/send/models/data/send.data"; +import { SendResponse } from "../../tools/send/models/response/send.response"; +import { SendApiService } from "../../tools/send/services/send-api.service.abstraction"; +import { InternalSendService } from "../../tools/send/services/send.service.abstraction"; +import { UserId } from "../../types/guid"; +import { CipherService } from "../../vault/abstractions/cipher.service"; +import { CollectionService } from "../../vault/abstractions/collection.service"; +import { FolderApiServiceAbstraction } from "../../vault/abstractions/folder/folder-api.service.abstraction"; +import { InternalFolderService } from "../../vault/abstractions/folder/folder.service.abstraction"; +import { CipherData } from "../../vault/models/data/cipher.data"; +import { CollectionData } from "../../vault/models/data/collection.data"; +import { FolderData } from "../../vault/models/data/folder.data"; +import { CipherResponse } from "../../vault/models/response/cipher.response"; +import { CollectionDetailsResponse } from "../../vault/models/response/collection.response"; +import { FolderResponse } from "../../vault/models/response/folder.response"; +import { CryptoService } from "../abstractions/crypto.service"; +import { LogService } from "../abstractions/log.service"; +import { StateService } from "../abstractions/state.service"; +import { MessageSender } from "../messaging"; +import { sequentialize } from "../misc/sequentialize"; -import { ApiService } from "../../../abstractions/api.service"; -import { InternalOrganizationServiceAbstraction } from "../../../admin-console/abstractions/organization/organization.service.abstraction"; -import { InternalPolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction"; -import { ProviderService } from "../../../admin-console/abstractions/provider.service"; -import { OrganizationUserType } from "../../../admin-console/enums"; -import { OrganizationData } from "../../../admin-console/models/data/organization.data"; -import { PolicyData } from "../../../admin-console/models/data/policy.data"; -import { ProviderData } from "../../../admin-console/models/data/provider.data"; -import { PolicyResponse } from "../../../admin-console/models/response/policy.response"; -import { AccountService } from "../../../auth/abstractions/account.service"; -import { AuthService } from "../../../auth/abstractions/auth.service"; -import { AvatarService } from "../../../auth/abstractions/avatar.service"; -import { KeyConnectorService } from "../../../auth/abstractions/key-connector.service"; -import { InternalMasterPasswordServiceAbstraction } from "../../../auth/abstractions/master-password.service.abstraction"; -import { TokenService } from "../../../auth/abstractions/token.service"; -import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; -import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; -import { BillingAccountProfileStateService } from "../../../billing/abstractions/account/billing-account-profile-state.service"; -import { DomainsResponse } from "../../../models/response/domains.response"; -import { ProfileResponse } from "../../../models/response/profile.response"; -import { CryptoService } from "../../../platform/abstractions/crypto.service"; -import { LogService } from "../../../platform/abstractions/log.service"; -import { StateService } from "../../../platform/abstractions/state.service"; -import { MessageSender } from "../../../platform/messaging"; -import { sequentialize } from "../../../platform/misc/sequentialize"; -import { CoreSyncService } from "../../../platform/sync/core-sync.service"; -import { SendData } from "../../../tools/send/models/data/send.data"; -import { SendResponse } from "../../../tools/send/models/response/send.response"; -import { SendApiService } from "../../../tools/send/services/send-api.service.abstraction"; -import { InternalSendService } from "../../../tools/send/services/send.service.abstraction"; -import { UserId } from "../../../types/guid"; -import { CipherService } from "../../../vault/abstractions/cipher.service"; -import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction"; -import { InternalFolderService } from "../../../vault/abstractions/folder/folder.service.abstraction"; -import { CipherData } from "../../../vault/models/data/cipher.data"; -import { FolderData } from "../../../vault/models/data/folder.data"; -import { CipherResponse } from "../../../vault/models/response/cipher.response"; -import { FolderResponse } from "../../../vault/models/response/folder.response"; -import { CollectionService } from "../../abstractions/collection.service"; -import { CollectionData } from "../../models/data/collection.data"; -import { CollectionDetailsResponse } from "../../models/response/collection.response"; +import { CoreSyncService } from "./core-sync.service"; + +export class DefaultSyncService extends CoreSyncService { + syncInProgress = false; -export class SyncService extends CoreSyncService { constructor( private masterPasswordService: InternalMasterPasswordServiceAbstraction, accountService: AccountService, diff --git a/libs/common/src/platform/sync/index.ts b/libs/common/src/platform/sync/index.ts new file mode 100644 index 00000000000..641d591ff09 --- /dev/null +++ b/libs/common/src/platform/sync/index.ts @@ -0,0 +1,2 @@ +export { SyncService } from "./sync.service"; +export { SyncResponse } from "./sync.response"; diff --git a/libs/common/src/platform/sync/internal.ts b/libs/common/src/platform/sync/internal.ts index f515e90a07e..d74f200e0da 100644 --- a/libs/common/src/platform/sync/internal.ts +++ b/libs/common/src/platform/sync/internal.ts @@ -1 +1,2 @@ +export { DefaultSyncService } from "./default-sync.service"; export { CoreSyncService } from "./core-sync.service"; diff --git a/libs/common/src/vault/types/sync-event-args.ts b/libs/common/src/platform/sync/sync-event-args.ts similarity index 94% rename from libs/common/src/vault/types/sync-event-args.ts rename to libs/common/src/platform/sync/sync-event-args.ts index 4f7d870a58f..10b7b7c4101 100644 --- a/libs/common/src/vault/types/sync-event-args.ts +++ b/libs/common/src/platform/sync/sync-event-args.ts @@ -1,4 +1,4 @@ -import { SyncResponse } from "../models/response/sync.response"; +import { SyncResponse } from "./sync.response"; type SyncStatus = "Started" | "Completed"; diff --git a/libs/common/src/vault/models/response/sync.response.ts b/libs/common/src/platform/sync/sync.response.ts similarity index 69% rename from libs/common/src/vault/models/response/sync.response.ts rename to libs/common/src/platform/sync/sync.response.ts index 42778a8cef9..9e7173d3ebb 100644 --- a/libs/common/src/vault/models/response/sync.response.ts +++ b/libs/common/src/platform/sync/sync.response.ts @@ -1,12 +1,11 @@ -import { PolicyResponse } from "../../../admin-console/models/response/policy.response"; -import { BaseResponse } from "../../../models/response/base.response"; -import { DomainsResponse } from "../../../models/response/domains.response"; -import { ProfileResponse } from "../../../models/response/profile.response"; -import { SendResponse } from "../../../tools/send/models/response/send.response"; - -import { CipherResponse } from "./cipher.response"; -import { CollectionDetailsResponse } from "./collection.response"; -import { FolderResponse } from "./folder.response"; +import { PolicyResponse } from "../../admin-console/models/response/policy.response"; +import { BaseResponse } from "../../models/response/base.response"; +import { DomainsResponse } from "../../models/response/domains.response"; +import { ProfileResponse } from "../../models/response/profile.response"; +import { SendResponse } from "../../tools/send/models/response/send.response"; +import { CipherResponse } from "../../vault/models/response/cipher.response"; +import { CollectionDetailsResponse } from "../../vault/models/response/collection.response"; +import { FolderResponse } from "../../vault/models/response/folder.response"; export class SyncResponse extends BaseResponse { profile?: ProfileResponse; diff --git a/libs/common/src/platform/sync/sync.service.ts b/libs/common/src/platform/sync/sync.service.ts new file mode 100644 index 00000000000..741657d5353 --- /dev/null +++ b/libs/common/src/platform/sync/sync.service.ts @@ -0,0 +1,58 @@ +import { + SyncCipherNotification, + SyncFolderNotification, + SyncSendNotification, +} from "../../models/response/notification.response"; + +/** + * A class encapsulating sync operations and data. + */ +export abstract class SyncService { + /** + * A boolean indicating if a sync is currently in progress via this instance and this instance only. + * + * @deprecated Trusting this property is not safe as it only tells if the current instance is currently + * doing a sync operation but does not tell if another instance of SyncService is doing a sync operation. + */ + abstract syncInProgress: boolean; + + /** + * Gets the date of the last sync for the currently active user. + * + * @returns The date of the last sync or null if there is no active user or the active user has not synced before. + */ + abstract getLastSync(): Promise; + + /** + * Updates a users last sync date. + * @param date The date to be set as the users last sync date. + * @param userId The userId of the user to update the last sync date for. + */ + abstract setLastSync(date: Date, userId?: string): Promise; + + /** + * Optionally does a full sync operation including going to the server to gather the source + * of truth and set that data to state. + * @param forceSync A boolean dictating if a sync should be forced. If `true` a sync will happen + * as long as the current user is authenticated. If `false` it will only sync if either a sync + * has not happened before or the last sync date for the active user is before their account + * revision date. Try to always use `false` if possible. + * + * @param allowThrowOnError A boolean dictating whether or not caught errors should be rethrown. + * `true` if they can be rethrown, `false` if they should not be rethrown. + */ + abstract fullSync(forceSync: boolean, allowThrowOnError?: boolean): Promise; + + abstract syncUpsertFolder( + notification: SyncFolderNotification, + isEdit: boolean, + ): Promise; + abstract syncDeleteFolder(notification: SyncFolderNotification): Promise; + abstract syncUpsertCipher( + notification: SyncCipherNotification, + isEdit: boolean, + ): Promise; + abstract syncDeleteCipher(notification: SyncFolderNotification): Promise; + abstract syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise; + abstract syncDeleteSend(notification: SyncSendNotification): Promise; +} diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index bae9a34c10c..61cfcb25837 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -121,6 +121,7 @@ import { EnvironmentService } from "../platform/abstractions/environment.service import { LogService } from "../platform/abstractions/log.service"; import { PlatformUtilsService } from "../platform/abstractions/platform-utils.service"; import { Utils } from "../platform/misc/utils"; +import { SyncResponse } from "../platform/sync"; import { UserId } from "../types/guid"; import { AttachmentRequest } from "../vault/models/request/attachment.request"; import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request"; @@ -142,7 +143,6 @@ import { CollectionResponse, } from "../vault/models/response/collection.response"; import { OptionalCipherResponse } from "../vault/models/response/optional-cipher.response"; -import { SyncResponse } from "../vault/models/response/sync.response"; /** * @deprecated The `ApiService` class is deprecated and calls should be extracted into individual diff --git a/libs/common/src/services/notifications.service.ts b/libs/common/src/services/notifications.service.ts index cae6fedbb8b..51589f52fae 100644 --- a/libs/common/src/services/notifications.service.ts +++ b/libs/common/src/services/notifications.service.ts @@ -21,8 +21,8 @@ import { EnvironmentService } from "../platform/abstractions/environment.service import { LogService } from "../platform/abstractions/log.service"; import { MessagingService } from "../platform/abstractions/messaging.service"; import { StateService } from "../platform/abstractions/state.service"; +import { SyncService } from "../platform/sync/sync.service"; import { UserId } from "../types/guid"; -import { SyncService } from "../vault/abstractions/sync/sync.service.abstraction"; export class NotificationsService implements NotificationsServiceAbstraction { private signalrConnection: signalR.HubConnection; diff --git a/libs/common/src/vault/abstractions/sync/sync-notifier.service.abstraction.ts b/libs/common/src/vault/abstractions/sync/sync-notifier.service.abstraction.ts deleted file mode 100644 index f519850aa66..00000000000 --- a/libs/common/src/vault/abstractions/sync/sync-notifier.service.abstraction.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Observable } from "rxjs"; - -import { SyncEventArgs } from "../../types/sync-event-args"; - -export abstract class SyncNotifierService { - sync$: Observable; - next: (event: SyncEventArgs) => void; -} diff --git a/libs/common/src/vault/abstractions/sync/sync.service.abstraction.ts b/libs/common/src/vault/abstractions/sync/sync.service.abstraction.ts index cfe73317555..1a1b8e7c758 100644 --- a/libs/common/src/vault/abstractions/sync/sync.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/sync/sync.service.abstraction.ts @@ -1,19 +1,2 @@ -import { - SyncCipherNotification, - SyncFolderNotification, - SyncSendNotification, -} from "../../../models/response/notification.response"; - -export abstract class SyncService { - syncInProgress: boolean; - - getLastSync: () => Promise; - setLastSync: (date: Date, userId?: string) => Promise; - fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise; - syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise; - syncDeleteFolder: (notification: SyncFolderNotification) => Promise; - syncUpsertCipher: (notification: SyncCipherNotification, isEdit: boolean) => Promise; - syncDeleteCipher: (notification: SyncFolderNotification) => Promise; - syncUpsertSend: (notification: SyncSendNotification, isEdit: boolean) => Promise; - syncDeleteSend: (notification: SyncSendNotification) => Promise; -} +// TEMP: Re-export of original SyncService location to allow for team specific PR's +export { SyncService } from "../../../platform/sync"; diff --git a/libs/common/src/vault/services/sync/sync-notifier.service.ts b/libs/common/src/vault/services/sync/sync-notifier.service.ts deleted file mode 100644 index 870ccfb849c..00000000000 --- a/libs/common/src/vault/services/sync/sync-notifier.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Subject } from "rxjs"; - -import { SyncNotifierService as SyncNotifierServiceAbstraction } from "../../abstractions/sync/sync-notifier.service.abstraction"; -import { SyncEventArgs } from "../../types/sync-event-args"; - -/** - * This class should most likely have 0 dependencies because it will hopefully - * be rolled into SyncService once upon a time. - */ -export class SyncNotifierService implements SyncNotifierServiceAbstraction { - private _sync = new Subject(); - - sync$ = this._sync.asObservable(); - - next(event: SyncEventArgs): void { - this._sync.next(event); - } -} From 1cfbcf4ee09b0285767dc5607fb1df99eab88c33 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Wed, 5 Jun 2024 08:10:36 -0700 Subject: [PATCH 04/12] [PM-8334] Sort ciphers after autofill (#9511) * [PM-8334] Add localData$ to CipherService and watch it for updates * Fix leftover tw-fixed class * [PM-8334] Fix tests --- .../vault-popup-items.service.spec.ts | 32 ++++++++++++++++++- .../services/vault-popup-items.service.ts | 7 ++-- .../src/vault/abstractions/cipher.service.ts | 3 ++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index 6a7cbbfc1a9..b7091eb87bf 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -6,6 +6,7 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ProductType } from "@bitwarden/common/enums"; +import { ObservableTracker } from "@bitwarden/common/spec"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; @@ -50,7 +51,8 @@ describe("VaultPopupItemsService", () => { cipherList[3].favorite = true; cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList); - cipherServiceMock.ciphers$ = new BehaviorSubject(null).asObservable(); + cipherServiceMock.ciphers$ = new BehaviorSubject(null); + cipherServiceMock.localData$ = new BehaviorSubject(null); searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers); cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) => ciphers.filter((c) => ["0", "1"].includes(c.id)), @@ -123,6 +125,34 @@ describe("VaultPopupItemsService", () => { }); }); + it("should update cipher list when cipherService.ciphers$ emits", async () => { + const tracker = new ObservableTracker(service.autoFillCiphers$); + + await tracker.expectEmission(); + + (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + + await tracker.expectEmission(); + + // Should only emit twice + expect(tracker.emissions.length).toBe(2); + await expect(tracker.pauseUntilReceived(3)).rejects.toThrow("Timeout exceeded"); + }); + + it("should update cipher list when cipherService.localData$ emits", async () => { + const tracker = new ObservableTracker(service.autoFillCiphers$); + + await tracker.expectEmission(); + + (cipherServiceMock.localData$ as BehaviorSubject).next(null); + + await tracker.expectEmission(); + + // Should only emit twice + expect(tracker.emissions.length).toBe(2); + await expect(tracker.pauseUntilReceived(3)).rejects.toThrow("Timeout exceeded"); + }); + describe("autoFillCiphers$", () => { it("should return empty array if there is no current tab", (done) => { jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(null); diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index ab98722d060..189ce2c09f9 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -5,6 +5,7 @@ import { distinctUntilKeyChanged, from, map, + merge, Observable, of, shareReplay, @@ -78,10 +79,12 @@ export class VaultPopupItemsService { * Observable that contains the list of all decrypted ciphers. * @private */ - private _cipherList$: Observable = this.cipherService.ciphers$.pipe( + private _cipherList$: Observable = merge( + this.cipherService.ciphers$, + this.cipherService.localData$, + ).pipe( runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())), - map((ciphers) => Object.values(ciphers)), switchMap((ciphers) => combineLatest([ this.organizationService.organizations$, diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 34bc8193553..2c0676f6442 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -1,5 +1,7 @@ import { Observable } from "rxjs"; +import { LocalData } from "@bitwarden/common/vault/models/data/local.data"; + import { UriMatchStrategySetting } from "../../models/domain/domain-service"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; import { CipherId, CollectionId, OrganizationId } from "../../types/guid"; @@ -14,6 +16,7 @@ import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; export abstract class CipherService { cipherViews$: Observable>; ciphers$: Observable>; + localData$: Observable>; /** * An observable monitoring the add/edit cipher info saved to memory. */ From 419c107f87a9b1d57cfc27f643913b9b60330c27 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Wed, 5 Jun 2024 11:39:40 -0400 Subject: [PATCH 05/12] [CL-243] honor initial disabled state in `bitFormButton` (#9510) * honor initial disabled state * update story --- libs/components/src/async-actions/form-button.directive.ts | 4 ++-- libs/components/src/async-actions/in-forms.stories.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/components/src/async-actions/form-button.directive.ts b/libs/components/src/async-actions/form-button.directive.ts index 5fe40423644..4e0facf17b2 100644 --- a/libs/components/src/async-actions/form-button.directive.ts +++ b/libs/components/src/async-actions/form-button.directive.ts @@ -40,13 +40,13 @@ export class BitFormButtonDirective implements OnDestroy { if (this.type === "submit") { buttonComponent.loading = loading; } else { - buttonComponent.disabled = loading; + buttonComponent.disabled = this.disabled || loading; } }); submitDirective.disabled$.pipe(takeUntil(this.destroy$)).subscribe((disabled) => { if (this.disabled !== false) { - buttonComponent.disabled = disabled; + buttonComponent.disabled = this.disabled || disabled; } }); } diff --git a/libs/components/src/async-actions/in-forms.stories.ts b/libs/components/src/async-actions/in-forms.stories.ts index ff060c6a7de..ec6005dd607 100644 --- a/libs/components/src/async-actions/in-forms.stories.ts +++ b/libs/components/src/async-actions/in-forms.stories.ts @@ -33,6 +33,7 @@ const template = ` + `; From 24fb3f71f1689d0df46bbc9fad756b6427d32f83 Mon Sep 17 00:00:00 2001 From: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:29:51 +0530 Subject: [PATCH 06/12] [PM-2057] update two factor email dialog (#8974) * migrating two factor email component * two factor email component migration * two factor email component migration * two factor email component migration --- .../settings/two-factor-email.component.html | 150 ++++++------------ .../settings/two-factor-email.component.ts | 77 ++++++--- .../settings/two-factor-setup.component.ts | 12 +- 3 files changed, 118 insertions(+), 121 deletions(-) diff --git a/apps/web/src/app/auth/settings/two-factor-email.component.html b/apps/web/src/app/auth/settings/two-factor-email.component.html index 93a6b0bb18a..cf1dba98842 100644 --- a/apps/web/src/app/auth/settings/two-factor-email.component.html +++ b/apps/web/src/app/auth/settings/two-factor-email.component.html @@ -1,101 +1,53 @@ - + + 2. {{ "twoFactorEmailEnterCode" | i18n }} + + + + + + + + + + diff --git a/apps/web/src/app/auth/settings/two-factor-email.component.ts b/apps/web/src/app/auth/settings/two-factor-email.component.ts index 7a2e6de5801..8a5c0292230 100644 --- a/apps/web/src/app/auth/settings/two-factor-email.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-email.component.ts @@ -1,4 +1,6 @@ -import { Component } from "@angular/core"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, EventEmitter, Inject, Output } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -19,18 +21,22 @@ import { TwoFactorBaseComponent } from "./two-factor-base.component"; @Component({ selector: "app-two-factor-email", templateUrl: "two-factor-email.component.html", + outputs: ["onUpdated"], }) export class TwoFactorEmailComponent extends TwoFactorBaseComponent { + @Output() onChangeStatus: EventEmitter = new EventEmitter(); type = TwoFactorProviderType.Email; - email: string; - token: string; sentEmail: string; formPromise: Promise; emailPromise: Promise; - override componentName = "app-two-factor-email"; + formGroup = this.formBuilder.group({ + token: [null], + email: ["", [Validators.email, Validators.required]], + }); constructor( + @Inject(DIALOG_DATA) protected data: AuthResponse, apiService: ApiService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, @@ -38,6 +44,8 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { userVerificationService: UserVerificationService, private accountService: AccountService, dialogService: DialogService, + private formBuilder: FormBuilder, + private dialogRef: DialogRef, ) { super( apiService, @@ -48,31 +56,49 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { dialogService, ); } + get token() { + return this.formGroup.get("token").value; + } + set token(value: string) { + this.formGroup.get("token").setValue(value); + } + get email() { + return this.formGroup.get("email").value; + } + set email(value: string) { + this.formGroup.get("email").setValue(value); + } + + async ngOnInit() { + await this.auth(this.data); + } auth(authResponse: AuthResponse) { super.auth(authResponse); return this.processResponse(authResponse.response); } - submit() { + submit = async () => { if (this.enabled) { - return super.disable(this.formPromise); + await this.disableEmail(); + this.onChangeStatus.emit(false); } else { - return this.enable(); + await this.enable(); + this.onChangeStatus.emit(true); } + }; + + private disableEmail() { + return super.disable(this.formPromise); } - async sendEmail() { - try { - const request = await this.buildRequestModel(TwoFactorEmailRequest); - request.email = this.email; - this.emailPromise = this.apiService.postTwoFactorEmailSetup(request); - await this.emailPromise; - this.sentEmail = this.email; - } catch (e) { - this.logService.error(e); - } - } + sendEmail = async () => { + const request = await this.buildRequestModel(TwoFactorEmailRequest); + request.email = this.email; + this.emailPromise = this.apiService.postTwoFactorEmailSetup(request); + await this.emailPromise; + this.sentEmail = this.email; + }; protected async enable() { const request = await this.buildRequestModel(UpdateTwoFactorEmailRequest); @@ -86,6 +112,10 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { }); } + onClose = () => { + this.dialogRef.close(this.enabled); + }; + private async processResponse(response: TwoFactorEmailResponse) { this.token = null; this.email = response.email; @@ -96,4 +126,15 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { ); } } + /** + * Strongly typed helper to open a TwoFactorEmailComponentComponent + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ + static open( + dialogService: DialogService, + config: DialogConfig>, + ) { + return dialogService.open(TwoFactorEmailComponent, config); + } } diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor-setup.component.ts index dc7871baf94..dd4c69aa277 100644 --- a/apps/web/src/app/auth/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-setup.component.ts @@ -1,3 +1,4 @@ +import { DialogRef } from "@angular/cdk/dialog"; import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core"; import { firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs"; @@ -178,11 +179,14 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const emailComp = await this.openModal(this.emailModalRef, TwoFactorEmailComponent); - await emailComp.auth(result); - emailComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => { - this.updateStatus(enabled, TwoFactorProviderType.Email); + const authComp: DialogRef = TwoFactorEmailComponent.open(this.dialogService, { + data: result, }); + authComp.componentInstance.onChangeStatus + .pipe(takeUntil(this.destroy$)) + .subscribe((enabled: boolean) => { + this.updateStatus(enabled, TwoFactorProviderType.Email); + }); break; } case TwoFactorProviderType.WebAuthn: { From 1cec69e3776b4bb47f9adbe5c0546399fa4dbadb Mon Sep 17 00:00:00 2001 From: vinith-kovan <156108204+vinith-kovan@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:44:33 +0530 Subject: [PATCH 07/12] [PM 2164] api-key component migration (#8562) * api-key component migration * api-key component migration * api-key component migration * api-key component migration --- .../settings/account.component.ts | 44 +++---- .../settings/security/api-key.component.html | 114 +++++++----------- .../settings/security/api-key.component.ts | 60 +++++---- .../security/security-keys.component.ts | 46 +++---- 4 files changed, 126 insertions(+), 138 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index 41cf9b9e8f5..5e083de9cc0 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -216,31 +216,33 @@ export class AccountComponent { }; async viewApiKey() { - await this.modalService.openViewRef(ApiKeyComponent, this.apiKeyModalRef, (comp) => { - comp.keyType = "organization"; - comp.entityId = this.organizationId; - comp.postKey = this.organizationApiService.getOrCreateApiKey.bind( - this.organizationApiService, - ); - comp.scope = "api.organization"; - comp.grantType = "client_credentials"; - comp.apiKeyTitle = "apiKey"; - comp.apiKeyWarning = "apiKeyWarning"; - comp.apiKeyDescription = "apiKeyDesc"; + await ApiKeyComponent.open(this.dialogService, { + data: { + keyType: "organization", + entityId: this.organizationId, + postKey: this.organizationApiService.getOrCreateApiKey.bind(this.organizationApiService), + scope: "api.organization", + grantType: "client_credentials", + apiKeyTitle: "apiKey", + apiKeyWarning: "apiKeyWarning", + apiKeyDescription: "apiKeyDesc", + }, }); } async rotateApiKey() { - await this.modalService.openViewRef(ApiKeyComponent, this.rotateApiKeyModalRef, (comp) => { - comp.keyType = "organization"; - comp.isRotation = true; - comp.entityId = this.organizationId; - comp.postKey = this.organizationApiService.rotateApiKey.bind(this.organizationApiService); - comp.scope = "api.organization"; - comp.grantType = "client_credentials"; - comp.apiKeyTitle = "apiKey"; - comp.apiKeyWarning = "apiKeyWarning"; - comp.apiKeyDescription = "apiKeyRotateDesc"; + await ApiKeyComponent.open(this.dialogService, { + data: { + keyType: "organization", + isRotation: true, + entityId: this.organizationId, + postKey: this.organizationApiService.rotateApiKey.bind(this.organizationApiService), + scope: "api.organization", + grantType: "client_credentials", + apiKeyTitle: "apiKey", + apiKeyWarning: "apiKeyWarning", + apiKeyDescription: "apiKeyRotateDesc", + }, }); } } diff --git a/apps/web/src/app/auth/settings/security/api-key.component.html b/apps/web/src/app/auth/settings/security/api-key.component.html index 1402a993881..118b17643cc 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.html +++ b/apps/web/src/app/auth/settings/security/api-key.component.html @@ -1,72 +1,42 @@ - +
+ + {{ data.apiKeyTitle | i18n }} +
+

{{ data.apiKeyDescription | i18n }}

+ + + {{ data.apiKeyWarning | i18n }} + +

+ client_id:
+ {{ clientId }} +

+

+ client_secret:
+ {{ clientSecret }} +

+

+ scope:
+ {{ data.scope }} +

+

+ grant_type:
+ {{ data.grantType }} +

+
+
+
+ + +
+
+
diff --git a/apps/web/src/app/auth/settings/security/api-key.component.ts b/apps/web/src/app/auth/settings/security/api-key.component.ts index 9d005562725..d171bc35617 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.ts +++ b/apps/web/src/app/auth/settings/security/api-key.component.ts @@ -1,46 +1,58 @@ -import { Component } from "@angular/core"; +import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response"; import { Verification } from "@bitwarden/common/auth/types/verification"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { DialogService } from "@bitwarden/components"; -@Component({ - selector: "app-api-key", - templateUrl: "api-key.component.html", -}) -export class ApiKeyComponent { +export type ApiKeyDialogData = { keyType: string; - isRotation: boolean; - postKey: (entityId: string, request: SecretVerificationRequest) => Promise; + isRotation?: boolean; entityId: string; + postKey: (entityId: string, request: SecretVerificationRequest) => Promise; scope: string; grantType: string; apiKeyTitle: string; apiKeyWarning: string; apiKeyDescription: string; - - masterPassword: Verification; - formPromise: Promise; +}; +@Component({ + selector: "app-api-key", + templateUrl: "api-key.component.html", +}) +export class ApiKeyComponent { clientId: string; clientSecret: string; + formGroup = this.formBuilder.group({ + masterPassword: [null as Verification, [Validators.required]], + }); constructor( + @Inject(DIALOG_DATA) protected data: ApiKeyDialogData, + private formBuilder: FormBuilder, private userVerificationService: UserVerificationService, - private logService: LogService, ) {} - async submit() { - try { - this.formPromise = this.userVerificationService - .buildRequest(this.masterPassword) - .then((request) => this.postKey(this.entityId, request)); - const response = await this.formPromise; - this.clientSecret = response.apiKey; - this.clientId = `${this.keyType}.${this.entityId}`; - } catch (e) { - this.logService.error(e); + submit = async () => { + if (this.formGroup.invalid) { + this.formGroup.markAllAsTouched(); + return; } - } + const response = await this.userVerificationService + .buildRequest(this.formGroup.value.masterPassword) + .then((request) => this.data.postKey(this.data.entityId, request)); + this.clientSecret = response.apiKey; + this.clientId = `${this.data.keyType}.${this.data.entityId}`; + }; + /** + * Strongly typed helper to open a ApiKeyComponent + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ + static open = (dialogService: DialogService, config: DialogConfig) => { + return dialogService.open(ApiKeyComponent, config); + }; } diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index e29417fad74..8de629dc83e 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { DialogService } from "@bitwarden/components"; import { ApiKeyComponent } from "./api-key.component"; @@ -22,8 +22,8 @@ export class SecurityKeysComponent implements OnInit { constructor( private userVerificationService: UserVerificationService, private stateService: StateService, - private modalService: ModalService, private apiService: ApiService, + private dialogService: DialogService, ) {} async ngOnInit() { @@ -32,30 +32,34 @@ export class SecurityKeysComponent implements OnInit { async viewUserApiKey() { const entityId = await this.stateService.getUserId(); - await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => { - comp.keyType = "user"; - comp.entityId = entityId; - comp.postKey = this.apiService.postUserApiKey.bind(this.apiService); - comp.scope = "api"; - comp.grantType = "client_credentials"; - comp.apiKeyTitle = "apiKey"; - comp.apiKeyWarning = "userApiKeyWarning"; - comp.apiKeyDescription = "userApiKeyDesc"; + await ApiKeyComponent.open(this.dialogService, { + data: { + keyType: "user", + entityId: entityId, + postKey: this.apiService.postUserApiKey.bind(this.apiService), + scope: "api", + grantType: "client_credentials", + apiKeyTitle: "apiKey", + apiKeyWarning: "userApiKeyWarning", + apiKeyDescription: "userApiKeyDesc", + }, }); } async rotateUserApiKey() { const entityId = await this.stateService.getUserId(); - await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => { - comp.keyType = "user"; - comp.isRotation = true; - comp.entityId = entityId; - comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService); - comp.scope = "api"; - comp.grantType = "client_credentials"; - comp.apiKeyTitle = "apiKey"; - comp.apiKeyWarning = "userApiKeyWarning"; - comp.apiKeyDescription = "apiKeyRotateDesc"; + await ApiKeyComponent.open(this.dialogService, { + data: { + keyType: "user", + isRotation: true, + entityId: entityId, + postKey: this.apiService.postUserRotateApiKey.bind(this.apiService), + scope: "api", + grantType: "client_credentials", + apiKeyTitle: "apiKey", + apiKeyWarning: "userApiKeyWarning", + apiKeyDescription: "apiKeyRotateDesc", + }, }); } } From 6e557338730479766bacb8595efe787c271d7a1b Mon Sep 17 00:00:00 2001 From: KiruthigaManivannan <162679756+KiruthigaManivannan@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:22:04 +0530 Subject: [PATCH 08/12] PM-4951 Migrate Recover Two Factor Component (#9170) * PM-4951 Migrate Recover Two Factor Component * PM-4951 Addressed review comments * PM-4951 Addressed review comments * update route * add type safety to data properties --------- Co-authored-by: rr-bw <102181210+rr-bw@users.noreply.github.com> --- .../auth/recover-two-factor.component.html | 112 ++++++------------ .../app/auth/recover-two-factor.component.ts | 60 +++++----- apps/web/src/app/oss-routing.module.ts | 34 +++++- 3 files changed, 97 insertions(+), 109 deletions(-) diff --git a/apps/web/src/app/auth/recover-two-factor.component.html b/apps/web/src/app/auth/recover-two-factor.component.html index 11d281b742b..e3641765800 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.html +++ b/apps/web/src/app/auth/recover-two-factor.component.html @@ -1,76 +1,40 @@ -
-
-
-

{{ "recoverAccountTwoStep" | i18n }}

-
-
-

- {{ "recoverAccountTwoStepDesc" | i18n }} - {{ "learnMore" | i18n }} -

-
- - -
-
- - -
-
- - -
-
-
- - - {{ "cancel" | i18n }} - -
-
-
-
+ +

+ {{ "recoverAccountTwoStepDesc" | i18n }} + {{ "learnMore" | i18n }} +

+ + {{ "emailAddress" | i18n }} + + + + {{ "masterPass" | i18n }} + + + + {{ "recoveryCodeTitle" | i18n }} + + +
+
+ + + {{ "cancel" | i18n }} +
diff --git a/apps/web/src/app/auth/recover-two-factor.component.ts b/apps/web/src/app/auth/recover-two-factor.component.ts index 145c46c8df5..4996dbe0a50 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -1,4 +1,5 @@ import { Component } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; @@ -6,7 +7,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TwoFactorRecoveryRequest } from "@bitwarden/common/auth/models/request/two-factor-recovery.request"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; 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"; @Component({ @@ -14,10 +14,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl templateUrl: "recover-two-factor.component.html", }) export class RecoverTwoFactorComponent { - email: string; - masterPassword: string; - recoveryCode: string; - formPromise: Promise; + protected formGroup = new FormGroup({ + email: new FormControl(null, [Validators.required]), + masterPassword: new FormControl(null, [Validators.required]), + recoveryCode: new FormControl(null, [Validators.required]), + }); constructor( private router: Router, @@ -26,31 +27,32 @@ export class RecoverTwoFactorComponent { private i18nService: I18nService, private cryptoService: CryptoService, private loginStrategyService: LoginStrategyServiceAbstraction, - private logService: LogService, ) {} - async submit() { - try { - const request = new TwoFactorRecoveryRequest(); - request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase(); - request.email = this.email.trim().toLowerCase(); - const key = await this.loginStrategyService.makePreloginKey( - this.masterPassword, - request.email, - ); - request.masterPasswordHash = await this.cryptoService.hashMasterKey(this.masterPassword, key); - this.formPromise = this.apiService.postTwoFactorRecover(request); - await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("twoStepRecoverDisabled"), - ); - // 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.router.navigate(["/"]); - } catch (e) { - this.logService.error(e); - } + get email(): string { + return this.formGroup.value.email; } + + get masterPassword(): string { + return this.formGroup.value.masterPassword; + } + + get recoveryCode(): string { + return this.formGroup.value.recoveryCode; + } + + submit = async () => { + const request = new TwoFactorRecoveryRequest(); + request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase(); + request.email = this.email.trim().toLowerCase(); + const key = await this.loginStrategyService.makePreloginKey(this.masterPassword, request.email); + request.masterPasswordHash = await this.cryptoService.hashMasterKey(this.masterPassword, key); + await this.apiService.postTwoFactorRecover(request); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("twoStepRecoverDisabled"), + ); + await this.router.navigate(["/"]); + }; } diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index c7b4631fa30..8509e987eb4 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -7,7 +7,9 @@ import { redirectGuard, tdeDecryptionRequiredGuard, UnauthGuard, + unauthGuardFn, } from "@bitwarden/angular/auth/guards"; +import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/auth/angular"; import { flagEnabled, Flags } from "../utils/flags"; @@ -40,6 +42,7 @@ import { UpdatePasswordComponent } from "./auth/update-password.component"; import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component"; +import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component"; import { DataProperties } from "./core"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { UserLayoutComponent } from "./layouts/user-layout.component"; @@ -141,12 +144,6 @@ const routes: Routes = [ data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false } satisfies DataProperties, }, { path: "recover", pathMatch: "full", redirectTo: "recover-2fa" }, - { - path: "recover-2fa", - component: RecoverTwoFactorComponent, - canActivate: [UnauthGuard], - data: { titleId: "recoverAccountTwoStep" } satisfies DataProperties, - }, { path: "recover-delete", component: RecoverDeleteComponent, @@ -203,6 +200,31 @@ const routes: Routes = [ }, ], }, + { + path: "", + component: AnonLayoutWrapperComponent, + children: [ + { + path: "recover-2fa", + canActivate: [unauthGuardFn()], + children: [ + { + path: "", + component: RecoverTwoFactorComponent, + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + data: { + pageTitle: "recoverAccountTwoStep", + titleId: "recoverAccountTwoStep", + } satisfies DataProperties & AnonLayoutWrapperData, + }, + ], + }, { path: "", component: UserLayoutComponent, From bf51469404a6fe97c11723c02511a6029f59e2ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 5 Jun 2024 14:58:28 -0400 Subject: [PATCH 09/12] specify generator algorithm during TDE (#9519) --- .../src/auth/components/login-via-auth-request.component.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/angular/src/auth/components/login-via-auth-request.component.ts b/libs/angular/src/auth/components/login-via-auth-request.component.ts index 401abab3b19..f898fa4ee8d 100644 --- a/libs/angular/src/auth/components/login-via-auth-request.component.ts +++ b/libs/angular/src/auth/components/login-via-auth-request.component.ts @@ -246,7 +246,10 @@ export class LoginViaAuthRequestComponent const deviceIdentifier = await this.appIdService.getAppId(); const publicKey = Utils.fromBufferToB64(this.authRequestKeyPair.publicKey); - const accessCode = await this.passwordGenerationService.generatePassword({ length: 25 }); + const accessCode = await this.passwordGenerationService.generatePassword({ + type: "password", + length: 25, + }); this.fingerprintPhrase = ( await this.cryptoService.getFingerprint(this.email, this.authRequestKeyPair.publicKey) From d3f1992ad59940f3046125e0c1ca072beacdb177 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Wed, 5 Jun 2024 15:07:00 -0400 Subject: [PATCH 10/12] [CL-294] Make section-header work across clients (#9386) --- .../popup-section-header.component.html | 11 -- .../popup-section-header.component.ts | 13 -- .../popup-section-header.stories.ts | 104 ------------ apps/browser/src/popup/app.module.ts | 2 - .../autofill-vault-list-items.component.html | 8 +- .../autofill-vault-list-items.component.ts | 10 +- .../vault-list-items-container.component.html | 10 +- .../vault-list-items-container.component.ts | 4 +- libs/components/src/section/index.ts | 1 + .../src/section/section-header.component.html | 8 + .../src/section/section-header.component.ts | 16 ++ libs/components/src/section/section.mdx | 89 ++++++++++ .../components/src/section/section.stories.ts | 155 +++++++++++++++++- 13 files changed, 281 insertions(+), 150 deletions(-) delete mode 100644 apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html delete mode 100644 apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts delete mode 100644 apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts create mode 100644 libs/components/src/section/section-header.component.html create mode 100644 libs/components/src/section/section-header.component.ts create mode 100644 libs/components/src/section/section.mdx diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html deleted file mode 100644 index 4fdbb823120..00000000000 --- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
-
-

- {{ title }} -

- -
-
- -
-
diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts deleted file mode 100644 index b33a2a0f330..00000000000 --- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component, Input } from "@angular/core"; - -import { TypographyModule } from "@bitwarden/components"; - -@Component({ - standalone: true, - selector: "popup-section-header", - templateUrl: "./popup-section-header.component.html", - imports: [TypographyModule], -}) -export class PopupSectionHeaderComponent { - @Input() title: string; -} diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts deleted file mode 100644 index f5cb472a59c..00000000000 --- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; - -import { - CardComponent, - IconButtonModule, - SectionComponent, - TypographyModule, -} from "@bitwarden/components"; - -import { PopupSectionHeaderComponent } from "./popup-section-header.component"; - -export default { - title: "Browser/Popup Section Header", - component: PopupSectionHeaderComponent, - args: { - title: "Title", - }, - decorators: [ - moduleMetadata({ - imports: [SectionComponent, CardComponent, TypographyModule, IconButtonModule], - }), - ], -} as Meta; - -type Story = StoryObj; - -export const OnlyTitle: Story = { - render: (args) => ({ - props: args, - template: ` - - `, - }), - args: { - title: "Only Title", - }, -}; - -export const TrailingText: Story = { - render: (args) => ({ - props: args, - template: ` - - 13 - - `, - }), - args: { - title: "Trailing Text", - }, -}; - -export const TailingIcon: Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), - args: { - title: "Trailing Icon", - }, -}; - -export const TitleSuffix: Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), - args: { - title: "Title Suffix", - }, -}; - -export const WithSections: Story = { - render: () => ({ - template: ` -
- - - - - -

Card 1 Content

-
-
- - - - - -

Card 2 Content

-
-
-
- `, - }), -}; diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 86f4d9fa768..3c7f45e55f7 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -46,7 +46,6 @@ import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.comp import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../platform/popup/layout/popup-page.component"; import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component"; -import { PopupSectionHeaderComponent } from "../platform/popup/popup-section-header/popup-section-header.component"; import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component"; import { GeneratorComponent } from "../tools/popup/generator/generator.component"; import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component"; @@ -119,7 +118,6 @@ import "../platform/popup/locales"; PopupFooterComponent, PopupHeaderComponent, UserVerificationDialogComponent, - PopupSectionHeaderComponent, CurrentAccountComponent, ], declarations: [ diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html index 83b07fc14cb..e52018ab27f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html @@ -8,17 +8,19 @@ > - + +

+ {{ "autofillSuggestions" | i18n }} +

-
+ {{ "autofillSuggestionsTip" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts index 9a4670bb4c8..1b9876759f0 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts @@ -3,10 +3,14 @@ import { Component } from "@angular/core"; import { combineLatest, map, Observable } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { IconButtonModule, SectionComponent, TypographyModule } from "@bitwarden/components"; +import { + IconButtonModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, +} from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; -import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component"; import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; import { PopupCipherView } from "../../../views/popup-cipher.view"; import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component"; @@ -19,7 +23,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/ TypographyModule, VaultListItemsContainerComponent, JslibModule, - PopupSectionHeaderComponent, + SectionHeaderComponent, IconButtonModule, ], selector: "app-autofill-vault-list-items", diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html index 74ee1af1ff5..c2c345fd757 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -1,16 +1,18 @@ - - {{ ciphers.length }} + +

+ {{ title }} +

-
+ {{ ciphers.length }} + +
+ +
+
+ +
+
diff --git a/libs/components/src/section/section-header.component.ts b/libs/components/src/section/section-header.component.ts new file mode 100644 index 00000000000..9f7b1a21f16 --- /dev/null +++ b/libs/components/src/section/section-header.component.ts @@ -0,0 +1,16 @@ +import { Component } from "@angular/core"; + +import { TypographyModule } from "../typography"; + +@Component({ + standalone: true, + selector: "bit-section-header", + templateUrl: "./section-header.component.html", + imports: [TypographyModule], + host: { + class: + // apply bottom and x padding when a `bit-card` or `bit-item` is the immediate sibling, or nested in the immediate sibling + "tw-block has-[+_*_bit-card]:tw-pb-1 has-[+_bit-card]:tw-pb-1 has-[+_*_bit-item]:tw-pb-1 has-[+_bit-item]:tw-pb-1 has-[+_*_bit-card]:tw-px-1 has-[+_bit-card]:tw-px-1 has-[+_*_bit-item]:tw-px-1 has-[+_bit-item]:tw-px-1", + }, +}) +export class SectionHeaderComponent {} diff --git a/libs/components/src/section/section.mdx b/libs/components/src/section/section.mdx new file mode 100644 index 00000000000..33e640e4f06 --- /dev/null +++ b/libs/components/src/section/section.mdx @@ -0,0 +1,89 @@ +import { Meta, Story, Primary, Controls, Canvas } from "@storybook/addon-docs"; + +import * as stories from "./section.stories"; + + + +```ts +import { SectionComponent, SectionHeaderComponent } from "@bitwarden/components"; +``` + +# Section + +Sections are simple containers that apply a responsive bottom margin and utilize the semantic +`section` HTML element. + + + + + +## Section Header + +Sections often contain a heading. Use `bit-section-header` inside of the `bit-section`. + +```html + + +

I'm a section header

+
+
Section content here!
+
+``` + +### Section Header Padding + +When placed inside of a section with a `bit-card` or `bit-item` as the immediate next sibling (or +nested in the immediate next sibling), the section header will automatically apply bottom and x-axis +padding to align the header with the border radius of the card/item. + +```html + + +

I'm a section header

+ +
+ +

I'm card content

+
+
+``` + + + + + +If placed inside of a section without a `bit-card` or `bit-item`, or with a `bit-card`/`bit-item` +that is not a descendant of the immediate next sibling, the padding is not applied. + + + + + +### Section Header Content Slots + +`bit-section-header` contains the following slots to help position the content: + +| Slot | Description | +| ------------ | ------------------------------- | +| default | title text of the header | +| `slot="end"` | placed at the end of the header | + +#### Default slot + +Anything passed to the default slot will display as part of the title. The title should be a +`bitTypography` element, usually an `h2` styled as an `h6`. + +Title suffixes (typically an icon or icon button) can be added as well. A gap is automatically +applied between the children of the default slot. + + + + + +#### End slot + +The `end` slot will typically be used for text or an icon button. + + + + diff --git a/libs/components/src/section/section.stories.ts b/libs/components/src/section/section.stories.ts index 65b6a67d476..0f720d1dba0 100644 --- a/libs/components/src/section/section.stories.ts +++ b/libs/components/src/section/section.stories.ts @@ -1,15 +1,24 @@ import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@storybook/angular"; +import { CardComponent } from "../card"; +import { IconButtonModule } from "../icon-button"; +import { ItemModule } from "../item"; import { TypographyModule } from "../typography"; -import { SectionComponent } from "./section.component"; +import { SectionComponent, SectionHeaderComponent } from "./"; export default { title: "Component Library/Section", component: SectionComponent, decorators: [ moduleMetadata({ - imports: [TypographyModule], + imports: [ + TypographyModule, + SectionHeaderComponent, + CardComponent, + IconButtonModule, + ItemModule, + ], }), componentWrapperDecorator((story) => `
${story}
`), ], @@ -17,19 +26,149 @@ export default { type Story = StoryObj; -/** Sections are simple containers that apply a responsive bottom margin. They often contain a heading. */ export const Default: Story = { - render: (args) => ({ - props: args, - template: ` + render: () => ({ + template: /*html*/ ` +

Foo

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.

+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.

+

Bar

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.

+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras vitae congue risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nunc elementum odio nibh, eget pellentesque sem ornare vitae. Etiam vel ante et velit fringilla egestas a sed sem. Fusce molestie nisl et nisi accumsan dapibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eu risus ex.

`, }), }; + +export const HeaderVariants: Story = { + render: () => ({ + template: /*html*/ ` + +

+ Title only +

+
+ +

+ Title with icon button suffix +

+ +
+ `, + }), +}; + +export const HeaderEndSlotVariants: Story = { + render: () => ({ + template: /*html*/ ` + +

+ Title with end slot text +

+ 13 +
+ +

+ Title with end slot icon button +

+ +
+ `, + }), +}; + +export const HeaderWithPadding: Story = { + render: () => ({ + template: /*html*/ ` +
+ + +

+ Card as immediate sibling +

+ +
+ +

bit-section-header has padding

+
+
+ + +

+ Card nested in immediate sibling +

+ +
+
+ +

bit-section-header has padding

+
+
+
+ + +

+ Item as immediate sibling +

+ +
+ + bit-section-header has padding + +
+ + +

+ Item nested in immediate sibling +

+ +
+ + + bit-section-header has padding + + +
+
+ `, + }), +}; + +export const HeaderWithoutPadding: Story = { + render: () => ({ + template: /*html*/ ` +
+ + +

+ No card or item used +

+ +
+
+

just a div, so bit-section-header has no padding

+
+
+ + +

+ Card nested in non-immediate sibling +

+ +
+
+ a div here +
+ +

bit-section-header has no padding

+
+
+
+ `, + }), +}; From 917c5fff5bc151de6b36ccd65cf045e9ce74d1ae Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Wed, 5 Jun 2024 16:05:12 -0400 Subject: [PATCH 11/12] remove extra unused method (#9500) --- .../abstractions/login-strategy.service.ts | 9 ---- .../login-strategy.service.ts | 44 ------------------- 2 files changed, 53 deletions(-) diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index eae6dc2a275..a46636532bf 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -3,7 +3,6 @@ import { Observable } from "rxjs"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { MasterKey } from "@bitwarden/common/types/key"; import { @@ -72,12 +71,4 @@ export abstract class LoginStrategyServiceAbstraction { * Creates a master key from the provided master password and email. */ makePreloginKey: (masterPassword: string, email: string) => Promise; - /** - * Sends a response to an auth request. - */ - passwordlessLogin: ( - id: string, - key: string, - requestApproved: boolean, - ) => Promise; } diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index f425bc697c5..7169fd69e93 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -24,8 +24,6 @@ import { PBKDF2KdfConfig, } from "@bitwarden/common/auth/models/domain/kdf-config"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request"; -import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -39,7 +37,6 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { KdfType } from "@bitwarden/common/platform/enums/kdf-type.enum"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -263,47 +260,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { return await this.cryptoService.makeMasterKey(masterPassword, email, kdfConfig); } - // TODO: move to auth request service - async passwordlessLogin( - id: string, - key: string, - requestApproved: boolean, - ): Promise { - const pubKey = Utils.fromB64ToArray(key); - - const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; - const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); - let keyToEncrypt; - let encryptedMasterKeyHash = null; - - if (masterKey) { - keyToEncrypt = masterKey.encKey; - - // Only encrypt the master password hash if masterKey exists as - // we won't have a masterKeyHash without a masterKey - const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId)); - if (masterKeyHash != null) { - encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt( - Utils.fromUtf8ToArray(masterKeyHash), - pubKey, - ); - } - } else { - const userKey = await this.cryptoService.getUserKey(); - keyToEncrypt = userKey.key; - } - - const encryptedKey = await this.cryptoService.rsaEncrypt(keyToEncrypt, pubKey); - - const request = new PasswordlessAuthRequest( - encryptedKey.encryptedString, - encryptedMasterKeyHash?.encryptedString, - await this.appIdService.getAppId(), - requestApproved, - ); - return await this.apiService.putAuthRequest(id, request); - } - private async clearCache(): Promise { await this.currentAuthnTypeState.update((_) => null); await this.loginStrategyCacheState.update((_) => null); From ce69b25d546fb4f5e6164aae674a146b3548b5a6 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Wed, 5 Jun 2024 15:14:34 -0500 Subject: [PATCH 12/12] [PM-8519] Inline menu fails to update credentials after saving credentials when unlocking extension (#9462) * Add Back Foreground & Background Derived State * Do Dependency Injection * Defend `map` Calls * Remove Folder Change * Add Helper For Preparing a Record For Use in `forkJoin` * Update & Test CryptoService Changes * Delete Unused Code * Update DeviceTrustService * Update CipherService * Make `userPublicKey$` Public * Rename convertValues File * Switch to `MonoTypeOperatorFunction` * Remove Unnecessary `async` * Remove Unnecessary Mock * Update libs/common/src/platform/abstractions/crypto.service.ts Co-authored-by: Andreas Coroiu * Add `convertValues` Tests * Add Doc Comments * Convert to `function`'s Co-authored-by: Andreas Coroiu * Fix Test Typos * Add param doc * Update Test Name * Add `@throws` Docs * [PM-8519] Inline Menu fails to Update Credentials After Saving New Cipher When Unlocking Extension * Revert "Merge branch 'reintroduce-foreground-background-derived' into autofill/pm-8519-inline-menu-fails-to-update-credentials-after-saving-credentials-when-unlocking-extension" This reverts commit 53b060dac108cf008ae1ab8f9a1199431496697b, reversing changes made to 15b9a237b82aa6276856596e02a032379e9fe73e. --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Andreas Coroiu --- .../abstractions/overlay.background.ts | 2 ++ .../background/overlay.background.spec.ts | 34 ++++++++----------- .../autofill/background/overlay.background.ts | 2 ++ 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index c9b230fe18c..aa62194af5c 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -77,7 +77,9 @@ type OverlayBackgroundExtensionMessageHandlers = { updateFocusedFieldData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; unlockCompleted: ({ message }: BackgroundMessageParam) => void; + addedCipher: () => void; addEditCipherSubmitted: () => void; + editedCipher: () => void; deletedCipher: () => void; }; diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index c469eb7dbbc..df4867640f4 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -1000,29 +1000,23 @@ describe("OverlayBackground", () => { }); }); - describe("addEditCipherSubmitted message handler", () => { - it("updates the overlay ciphers", () => { - const message = { - command: "addEditCipherSubmitted", - }; - jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation(); + describe("extension messages that trigger an update of the inline menu ciphers", () => { + const extensionMessages = [ + "addedCipher", + "addEditCipherSubmitted", + "editedCipher", + "deletedCipher", + ]; - sendMockExtensionMessage(message); - - expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled(); + beforeEach(() => { + jest.spyOn(overlayBackground, "updateOverlayCiphers").mockImplementation(); }); - }); - describe("deletedCipher message handler", () => { - it("updates the overlay ciphers", () => { - const message = { - command: "deletedCipher", - }; - jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation(); - - sendMockExtensionMessage(message); - - expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled(); + extensionMessages.forEach((message) => { + it(`triggers an update of the overlay ciphers when the ${message} message is received`, () => { + sendMockExtensionMessage({ command: message }); + expect(overlayBackground.updateOverlayCiphers).toHaveBeenCalled(); + }); }); }); }); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 56f6a3a2158..bf954c3419f 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -72,7 +72,9 @@ class OverlayBackground implements OverlayBackgroundInterface { updateFocusedFieldData: ({ message, sender }) => this.setFocusedFieldData(message, sender), collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender), unlockCompleted: ({ message }) => this.unlockCompleted(message), + addedCipher: () => this.updateOverlayCiphers(), addEditCipherSubmitted: () => this.updateOverlayCiphers(), + editedCipher: () => this.updateOverlayCiphers(), deletedCipher: () => this.updateOverlayCiphers(), }; private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = {