diff --git a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts index 179dda5a5f4..994a0678bab 100644 --- a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts @@ -8,10 +8,13 @@ import { TemplateRef, viewChild, } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder } from "@angular/forms"; import { Router } from "@angular/router"; import { combineLatest, + filter, + first, firstValueFrom, map, Observable, @@ -20,8 +23,10 @@ import { startWith, switchMap, tap, + zip, } from "rxjs"; +import { AutomaticUserConfirmationService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -116,6 +121,7 @@ export class AutoConfirmPolicyDialogComponent private organizationService: OrganizationService, private policyService: PolicyService, private router: Router, + private autoConfirmService: AutomaticUserConfirmationService, ) { super( data, @@ -131,6 +137,18 @@ export class AutoConfirmPolicyDialogComponent ); this.firstTimeDialog.set(data.firstTimeDialog ?? false); + const userId$ = this.accountService.activeAccount$.pipe(getUserId); + + zip([userId$.pipe(switchMap((userId) => autoConfirmService.configuration$(userId))), userId$]) + .pipe( + first(), + filter(([state]) => state.showSetupDialog), + switchMap(([autoConfirmState, userId]) => + this.autoConfirmService.upsert(userId, { ...autoConfirmState, showSetupDialog: false }), + ), + takeUntilDestroyed(), + ) + .subscribe(); } /** diff --git a/apps/web/src/app/admin-console/organizations/policies/index.ts b/apps/web/src/app/admin-console/organizations/policies/index.ts index 624e5132faf..5a9cf0db2ad 100644 --- a/apps/web/src/app/admin-console/organizations/policies/index.ts +++ b/apps/web/src/app/admin-console/organizations/policies/index.ts @@ -2,3 +2,5 @@ export { PoliciesComponent } from "./policies.component"; export { ossPolicyEditRegister } from "./policy-edit-register"; export { BasePolicyEditDefinition, BasePolicyEditComponent } from "./base-policy-edit.component"; export { POLICY_EDIT_REGISTER } from "./policy-register-token"; +export { AutoConfirmPolicyDialogComponent } from "./auto-confirm-edit-policy-dialog.component"; +export { AutoConfirmPolicy } from "./policy-edit-definitions"; diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 9619c3e23bf..e8c43844a80 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -9,6 +9,10 @@ import { DefaultCollectionAdminService, OrganizationUserApiService, CollectionService, + AutomaticUserConfirmationService, + DefaultAutomaticUserConfirmationService, + OrganizationUserService, + DefaultOrganizationUserService, } from "@bitwarden/admin-console/common"; import { DefaultDeviceManagementComponentService } from "@bitwarden/angular/auth/device-management/default-device-management-component.service"; import { DeviceManagementComponentServiceAbstraction } from "@bitwarden/angular/auth/device-management/device-management-component.service.abstraction"; @@ -43,7 +47,10 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + InternalOrganizationServiceAbstraction, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService, @@ -334,6 +341,29 @@ const safeProviders: SafeProvider[] = [ OrganizationService, ], }), + safeProvider({ + provide: OrganizationUserService, + useClass: DefaultOrganizationUserService, + deps: [ + KeyServiceAbstraction, + EncryptService, + OrganizationUserApiService, + AccountService, + I18nServiceAbstraction, + ], + }), + safeProvider({ + provide: AutomaticUserConfirmationService, + useClass: DefaultAutomaticUserConfirmationService, + deps: [ + ConfigService, + ApiService, + OrganizationUserService, + StateProvider, + InternalOrganizationServiceAbstraction, + OrganizationUserApiService, + ], + }), safeProvider({ provide: SdkLoadService, useClass: flagEnabled("sdk") ? WebSdkLoadService : NoopSdkLoadService, 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 7bdd290336d..f9d2f13814e 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -9,6 +9,7 @@ import { lastValueFrom, Observable, Subject, + zip, } from "rxjs"; import { concatMap, @@ -25,6 +26,7 @@ import { } from "rxjs/operators"; import { + AutomaticUserConfirmationService, CollectionData, CollectionDetailsResponse, CollectionService, @@ -54,7 +56,9 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -102,6 +106,10 @@ import { getNestedCollectionTree, getFlatCollectionTree, } from "../../admin-console/organizations/collections"; +import { + AutoConfirmPolicy, + AutoConfirmPolicyDialogComponent, +} from "../../admin-console/organizations/policies"; import { CollectionDialogAction, CollectionDialogTabType, @@ -328,6 +336,8 @@ export class VaultComponent implements OnInit, OnDestr private policyService: PolicyService, private unifiedUpgradePromptService: UnifiedUpgradePromptService, private premiumUpgradePromptService: PremiumUpgradePromptService, + private autoConfirmService: AutomaticUserConfirmationService, + private configService: ConfigService, ) {} async ngOnInit() { @@ -629,6 +639,26 @@ export class VaultComponent implements OnInit, OnDestr }, ); void this.unifiedUpgradePromptService.displayUpgradePromptConditionally(); + + zip([ + this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm), + this.userId$.pipe(switchMap((userId) => this.autoConfirmService.configuration$(userId))), + this.organizations$.pipe(map((organizations) => organizations[0])), + ]) + .pipe( + filter( + ([enabled, autoConfirmState, organization]) => + enabled && + autoConfirmState.showSetupDialog && + !!organization && + organization.canManageUsers, + ), + first(), + takeUntil(this.destroy$), + ) + .subscribe(([enabled, autoConfirmState, organization]) => + this.openAutoConfirmFeatureDialog(organization), + ); } ngOnDestroy() { @@ -1547,6 +1577,16 @@ export class VaultComponent implements OnInit, OnDestr const cipherView = await this.cipherService.decrypt(_cipher, activeUserId); return cipherView.login?.password; } + + private openAutoConfirmFeatureDialog(organization: Organization) { + AutoConfirmPolicyDialogComponent.open(this.dialogService, { + data: { + policy: new AutoConfirmPolicy(), + organizationId: organization.id, + firstTimeDialog: true, + }, + }); + } } /** diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index fc9c5b8b15c..d9038af39a5 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -6,7 +6,9 @@ import { Subject } from "rxjs"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { + AutomaticUserConfirmationService, CollectionService, + DefaultAutomaticUserConfirmationService, DefaultCollectionService, DefaultOrganizationUserApiService, DefaultOrganizationUserService, @@ -1136,6 +1138,18 @@ const safeProviders: SafeProvider[] = [ I18nServiceAbstraction, ], }), + safeProvider({ + provide: AutomaticUserConfirmationService, + useClass: DefaultAutomaticUserConfirmationService, + deps: [ + ConfigService, + ApiService, + OrganizationUserService, + StateProvider, + InternalOrganizationServiceAbstraction, + OrganizationUserApiService, + ], + }), safeProvider({ provide: OrganizationServiceAbstraction, useExisting: InternalOrganizationServiceAbstraction,