Skip to content

Commit 7488fbe

Browse files
BTrestonJingo88
authored andcommitted
[PM-26363] Add one time setup dialog for auto confirm (#17104)
* add one time setup dialog for auto confirm * add one time setup dialog for auto confirm * fix copy, padding, cleanup observable logic * cleanup * cleanup * refactor * clean up * more cleanup * Fix deleted files This reverts commit 7c18a5e.
1 parent e396013 commit 7488fbe

File tree

10 files changed

+151
-19
lines changed

10 files changed

+151
-19
lines changed

apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@
3838
<div class="tw-flex tw-flex-col">
3939
@let showBadge = firstTimeDialog();
4040
@if (showBadge) {
41-
<span bitBadge variant="info" class="tw-w-28 tw-my-2"> {{ "availableNow" | i18n }}</span>
41+
<span bitBadge variant="info" class="tw-w-[99px] tw-my-2"> {{ "availableNow" | i18n }}</span>
4242
}
4343
<span>
44-
{{ (firstTimeDialog ? "autoConfirm" : "editPolicy") | i18n }}
45-
@if (!firstTimeDialog) {
44+
{{ (showBadge ? "autoConfirm" : "editPolicy") | i18n }}
45+
@if (!showBadge) {
4646
<span class="tw-text-muted tw-font-normal tw-text-sm">
4747
{{ policy.name | i18n }}
4848
</span>
@@ -64,7 +64,7 @@
6464
type="submit"
6565
>
6666
@let autoConfirmEnabled = autoConfirmEnabled$ | async;
67-
@let managePoliciesOnly = managePolicies$ | async;
67+
@let managePoliciesOnly = managePoliciesOnly$ | async;
6868
@if (autoConfirmEnabled || managePoliciesOnly) {
6969
{{ "save" | i18n }}
7070
} @else {

apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
tap,
2323
} from "rxjs";
2424

25+
import { AutomaticUserConfirmationService } from "@bitwarden/admin-console/common";
2526
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
2627
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
2728
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -85,7 +86,10 @@ export class AutoConfirmPolicyDialogComponent
8586
switchMap((userId) => this.policyService.policies$(userId)),
8687
map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm)?.enabled ?? false),
8788
);
88-
protected managePolicies$: Observable<boolean> = this.accountService.activeAccount$.pipe(
89+
// Users with manage policies custom permission should not see the dialog's second step since
90+
// they do not have permission to configure the setting. This will only allow them to configure
91+
// the policy.
92+
protected managePoliciesOnly$: Observable<boolean> = this.accountService.activeAccount$.pipe(
8993
getUserId,
9094
switchMap((userId) => this.organizationService.organizations$(userId)),
9195
getById(this.data.organizationId),
@@ -116,6 +120,7 @@ export class AutoConfirmPolicyDialogComponent
116120
private organizationService: OrganizationService,
117121
private policyService: PolicyService,
118122
private router: Router,
123+
private autoConfirmService: AutomaticUserConfirmationService,
119124
) {
120125
super(
121126
data,
@@ -161,7 +166,7 @@ export class AutoConfirmPolicyDialogComponent
161166
}
162167

163168
private buildMultiStepSubmit(singleOrgPolicyEnabled: boolean): Observable<MultiStepSubmit[]> {
164-
return this.managePolicies$.pipe(
169+
return this.managePoliciesOnly$.pipe(
165170
map((managePoliciesOnly) => {
166171
const submitSteps = [
167172
{
@@ -206,6 +211,17 @@ export class AutoConfirmPolicyDialogComponent
206211
autoConfirmRequest,
207212
);
208213

214+
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
215+
216+
const currentAutoConfirmState = await firstValueFrom(
217+
this.autoConfirmService.configuration$(userId),
218+
);
219+
220+
await this.autoConfirmService.upsert(userId, {
221+
...currentAutoConfirmState,
222+
showSetupDialog: false,
223+
});
224+
209225
this.toastService.showToast({
210226
variant: "success",
211227
message: this.i18nService.t("editedPolicyId", this.i18nService.t(this.data.policy.name)),

apps/web/src/app/admin-console/organizations/policies/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ export { PoliciesComponent } from "./policies.component";
22
export { ossPolicyEditRegister } from "./policy-edit-register";
33
export { BasePolicyEditDefinition, BasePolicyEditComponent } from "./base-policy-edit.component";
44
export { POLICY_EDIT_REGISTER } from "./policy-register-token";
5+
export { AutoConfirmPolicyDialogComponent } from "./auto-confirm-edit-policy-dialog.component";
6+
export { AutoConfirmPolicy } from "./policy-edit-definitions";
7+
export { PolicyEditDialogResult } from "./policy-edit-dialog.component";

apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@
4747
<bit-icon class="tw-w-[233px]" [icon]="autoConfirmSvg"></bit-icon>
4848
</div>
4949
<ol>
50-
<li>1. {{ "autoConfirmStep1" | i18n }}</li>
50+
<li>1. {{ "autoConfirmExtension1" | i18n }}</li>
5151

5252
<li>
53-
2. {{ "autoConfirmStep2a" | i18n }}
53+
2. {{ "autoConfirmExtension2" | i18n }}
5454
<strong>
55-
{{ "autoConfirmStep2b" | i18n }}
55+
{{ "autoConfirmExtension3" | i18n }}
5656
</strong>
5757
</li>
5858
</ol>

apps/web/src/app/core/core.module.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import {
99
DefaultCollectionAdminService,
1010
OrganizationUserApiService,
1111
CollectionService,
12+
AutomaticUserConfirmationService,
13+
DefaultAutomaticUserConfirmationService,
14+
OrganizationUserService,
15+
DefaultOrganizationUserService,
1216
} from "@bitwarden/admin-console/common";
1317
import { DefaultDeviceManagementComponentService } from "@bitwarden/angular/auth/device-management/default-device-management-component.service";
1418
import { DeviceManagementComponentServiceAbstraction } from "@bitwarden/angular/auth/device-management/device-management-component.service.abstraction";
@@ -44,7 +48,10 @@ import {
4448
} from "@bitwarden/auth/common";
4549
import { ApiService } from "@bitwarden/common/abstractions/api.service";
4650
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
47-
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
51+
import {
52+
InternalOrganizationServiceAbstraction,
53+
OrganizationService,
54+
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
4855
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
4956
import {
5057
InternalPolicyService,
@@ -338,6 +345,29 @@ const safeProviders: SafeProvider[] = [
338345
OrganizationService,
339346
],
340347
}),
348+
safeProvider({
349+
provide: OrganizationUserService,
350+
useClass: DefaultOrganizationUserService,
351+
deps: [
352+
KeyServiceAbstraction,
353+
EncryptService,
354+
OrganizationUserApiService,
355+
AccountService,
356+
I18nServiceAbstraction,
357+
],
358+
}),
359+
safeProvider({
360+
provide: AutomaticUserConfirmationService,
361+
useClass: DefaultAutomaticUserConfirmationService,
362+
deps: [
363+
ConfigService,
364+
ApiService,
365+
OrganizationUserService,
366+
StateProvider,
367+
InternalOrganizationServiceAbstraction,
368+
OrganizationUserApiService,
369+
],
370+
}),
341371
safeProvider({
342372
provide: SdkLoadService,
343373
useClass: flagEnabled("sdk") ? WebSdkLoadService : NoopSdkLoadService,

apps/web/src/app/vault/individual-vault/vault.component.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
lastValueFrom,
1010
Observable,
1111
Subject,
12+
zip,
1213
} from "rxjs";
1314
import {
1415
concatMap,
@@ -25,6 +26,7 @@ import {
2526
} from "rxjs/operators";
2627

2728
import {
29+
AutomaticUserConfirmationService,
2830
CollectionData,
2931
CollectionDetailsResponse,
3032
CollectionService,
@@ -54,7 +56,9 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
5456
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
5557
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
5658
import { EventType } from "@bitwarden/common/enums";
59+
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
5760
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
61+
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
5862
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
5963
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
6064
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@@ -102,6 +106,11 @@ import {
102106
getNestedCollectionTree,
103107
getFlatCollectionTree,
104108
} from "../../admin-console/organizations/collections";
109+
import {
110+
AutoConfirmPolicy,
111+
AutoConfirmPolicyDialogComponent,
112+
PolicyEditDialogResult,
113+
} from "../../admin-console/organizations/policies";
105114
import {
106115
CollectionDialogAction,
107116
CollectionDialogTabType,
@@ -213,6 +222,8 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
213222
private destroy$ = new Subject<void>();
214223

215224
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
225+
private autoConfirmDialogRef?: DialogRef<PolicyEditDialogResult> | undefined;
226+
216227
protected showAddCipherBtn: boolean = false;
217228

218229
organizations$ = this.accountService.activeAccount$
@@ -328,6 +339,8 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
328339
private policyService: PolicyService,
329340
private unifiedUpgradePromptService: UnifiedUpgradePromptService,
330341
private premiumUpgradePromptService: PremiumUpgradePromptService,
342+
private autoConfirmService: AutomaticUserConfirmationService,
343+
private configService: ConfigService,
331344
) {}
332345

333346
async ngOnInit() {
@@ -629,6 +642,8 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
629642
},
630643
);
631644
void this.unifiedUpgradePromptService.displayUpgradePromptConditionally();
645+
646+
this.setupAutoConfirm();
632647
}
633648

634649
ngOnDestroy() {
@@ -1547,6 +1562,72 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
15471562
const cipherView = await this.cipherService.decrypt(_cipher, activeUserId);
15481563
return cipherView.login?.password;
15491564
}
1565+
1566+
private async openAutoConfirmFeatureDialog(organization: Organization) {
1567+
if (this.autoConfirmDialogRef) {
1568+
return;
1569+
}
1570+
1571+
this.autoConfirmDialogRef = AutoConfirmPolicyDialogComponent.open(this.dialogService, {
1572+
data: {
1573+
policy: new AutoConfirmPolicy(),
1574+
organizationId: organization.id,
1575+
firstTimeDialog: true,
1576+
},
1577+
});
1578+
1579+
await lastValueFrom(this.autoConfirmDialogRef.closed);
1580+
this.autoConfirmDialogRef = undefined;
1581+
}
1582+
1583+
private setupAutoConfirm() {
1584+
// if the policy is enabled, then the user may only belong to one organization at most.
1585+
const organization$ = this.organizations$.pipe(map((organizations) => organizations[0]));
1586+
1587+
const featureFlag$ = this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm);
1588+
1589+
const autoConfirmState$ = this.userId$.pipe(
1590+
switchMap((userId) => this.autoConfirmService.configuration$(userId)),
1591+
);
1592+
1593+
const policyEnabled$ = combineLatest([
1594+
this.userId$.pipe(
1595+
switchMap((userId) => this.policyService.policies$(userId)),
1596+
map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm && p.enabled)),
1597+
),
1598+
organization$,
1599+
]).pipe(
1600+
map(
1601+
([policy, organization]) => (policy && policy.organizationId === organization?.id) ?? false,
1602+
),
1603+
);
1604+
1605+
zip([organization$, featureFlag$, autoConfirmState$, policyEnabled$, this.userId$])
1606+
.pipe(
1607+
first(),
1608+
switchMap(async ([organization, flagEnabled, autoConfirmState, policyEnabled, userId]) => {
1609+
const showDialog =
1610+
flagEnabled &&
1611+
!policyEnabled &&
1612+
autoConfirmState.showSetupDialog &&
1613+
!!organization &&
1614+
(organization.canManageUsers || organization.canManagePolicies);
1615+
1616+
if (showDialog) {
1617+
await this.openAutoConfirmFeatureDialog(organization);
1618+
1619+
await this.autoConfirmService.upsert(userId, {
1620+
...autoConfirmState,
1621+
showSetupDialog: false,
1622+
});
1623+
}
1624+
}),
1625+
takeUntil(this.destroy$),
1626+
)
1627+
.subscribe({
1628+
error: (err: unknown) => this.logService.error("Failed to update auto-confirm state", err),
1629+
});
1630+
}
15501631
}
15511632

15521633
/**

apps/web/src/locales/en/messages.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5832,16 +5832,16 @@
58325832
"howToTurnOnAutoConfirm": {
58335833
"message": "How to turn on automatic user confirmation"
58345834
},
5835-
"autoConfirmStep1": {
5836-
"message": "Open your Bitwarden extension."
5835+
"autoConfirmExtension1": {
5836+
"message": "Open your Bitwarden extension"
58375837
},
5838-
"autoConfirmStep2a": {
5838+
"autoConfirmExtension2": {
58395839
"message": "Select",
5840-
"description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'"
5840+
"description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'"
58415841
},
5842-
"autoConfirmStep2b": {
5843-
"message": " Turn on.",
5844-
"description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'"
5842+
"autoConfirmExtension3": {
5843+
"message": " Turn on",
5844+
"description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'"
58455845
},
58465846
"autoConfirmExtensionOpened": {
58475847
"message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting."

libs/admin-console/src/common/auto-confirm/models/auto-confirm-state.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ export const AUTO_CONFIRM_STATE = UserKeyDefinition.record<AutoConfirmState>(
1616
"autoConfirm",
1717
{
1818
deserializer: (autoConfirmState) => autoConfirmState,
19-
clearOn: ["logout"],
19+
clearOn: [],
2020
},
2121
);

libs/common/src/admin-console/services/policy/default-policy.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ export class DefaultPolicyService implements PolicyService {
285285
case PolicyType.RemoveUnlockWithPin:
286286
// Remove Unlock with PIN policy
287287
return false;
288+
case PolicyType.AutoConfirm:
289+
return false;
288290
case PolicyType.OrganizationDataOwnership:
289291
// organization data ownership policy applies to everyone except admins and owners
290292
return organization.isAdmin;

libs/state/src/core/state-definitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const DELETE_MANAGED_USER_WARNING = new StateDefinition(
3636
web: "disk-local",
3737
},
3838
);
39-
export const AUTO_CONFIRM = new StateDefinition("autoConfirm", "disk");
39+
export const AUTO_CONFIRM = new StateDefinition("autoConfirm", "disk", { web: "disk-local" });
4040

4141
// Billing
4242
export const BILLING_DISK = new StateDefinition("billing", "disk");

0 commit comments

Comments
 (0)