mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[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 7c18a5e512.
This commit is contained in:
@@ -38,11 +38,11 @@
|
||||
<div class="tw-flex tw-flex-col">
|
||||
@let showBadge = firstTimeDialog();
|
||||
@if (showBadge) {
|
||||
<span bitBadge variant="info" class="tw-w-28 tw-my-2"> {{ "availableNow" | i18n }}</span>
|
||||
<span bitBadge variant="info" class="tw-w-[99px] tw-my-2"> {{ "availableNow" | i18n }}</span>
|
||||
}
|
||||
<span>
|
||||
{{ (firstTimeDialog ? "autoConfirm" : "editPolicy") | i18n }}
|
||||
@if (!firstTimeDialog) {
|
||||
{{ (showBadge ? "autoConfirm" : "editPolicy") | i18n }}
|
||||
@if (!showBadge) {
|
||||
<span class="tw-text-muted tw-font-normal tw-text-sm">
|
||||
{{ policy.name | i18n }}
|
||||
</span>
|
||||
@@ -64,7 +64,7 @@
|
||||
type="submit"
|
||||
>
|
||||
@let autoConfirmEnabled = autoConfirmEnabled$ | async;
|
||||
@let managePoliciesOnly = managePolicies$ | async;
|
||||
@let managePoliciesOnly = managePoliciesOnly$ | async;
|
||||
@if (autoConfirmEnabled || managePoliciesOnly) {
|
||||
{{ "save" | i18n }}
|
||||
} @else {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
tap,
|
||||
} 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";
|
||||
@@ -85,7 +86,10 @@ export class AutoConfirmPolicyDialogComponent
|
||||
switchMap((userId) => this.policyService.policies$(userId)),
|
||||
map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm)?.enabled ?? false),
|
||||
);
|
||||
protected managePolicies$: Observable<boolean> = this.accountService.activeAccount$.pipe(
|
||||
// Users with manage policies custom permission should not see the dialog's second step since
|
||||
// they do not have permission to configure the setting. This will only allow them to configure
|
||||
// the policy.
|
||||
protected managePoliciesOnly$: Observable<boolean> = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.organizationService.organizations$(userId)),
|
||||
getById(this.data.organizationId),
|
||||
@@ -116,6 +120,7 @@ export class AutoConfirmPolicyDialogComponent
|
||||
private organizationService: OrganizationService,
|
||||
private policyService: PolicyService,
|
||||
private router: Router,
|
||||
private autoConfirmService: AutomaticUserConfirmationService,
|
||||
) {
|
||||
super(
|
||||
data,
|
||||
@@ -161,7 +166,7 @@ export class AutoConfirmPolicyDialogComponent
|
||||
}
|
||||
|
||||
private buildMultiStepSubmit(singleOrgPolicyEnabled: boolean): Observable<MultiStepSubmit[]> {
|
||||
return this.managePolicies$.pipe(
|
||||
return this.managePoliciesOnly$.pipe(
|
||||
map((managePoliciesOnly) => {
|
||||
const submitSteps = [
|
||||
{
|
||||
@@ -206,6 +211,17 @@ export class AutoConfirmPolicyDialogComponent
|
||||
autoConfirmRequest,
|
||||
);
|
||||
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
|
||||
const currentAutoConfirmState = await firstValueFrom(
|
||||
this.autoConfirmService.configuration$(userId),
|
||||
);
|
||||
|
||||
await this.autoConfirmService.upsert(userId, {
|
||||
...currentAutoConfirmState,
|
||||
showSetupDialog: false,
|
||||
});
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
message: this.i18nService.t("editedPolicyId", this.i18nService.t(this.data.policy.name)),
|
||||
|
||||
@@ -2,3 +2,6 @@ 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";
|
||||
export { PolicyEditDialogResult } from "./policy-edit-dialog.component";
|
||||
|
||||
@@ -47,12 +47,12 @@
|
||||
<bit-icon class="tw-w-[233px]" [icon]="autoConfirmSvg"></bit-icon>
|
||||
</div>
|
||||
<ol>
|
||||
<li>1. {{ "autoConfirmStep1" | i18n }}</li>
|
||||
<li>1. {{ "autoConfirmExtension1" | i18n }}</li>
|
||||
|
||||
<li>
|
||||
2. {{ "autoConfirmStep2a" | i18n }}
|
||||
2. {{ "autoConfirmExtension2" | i18n }}
|
||||
<strong>
|
||||
{{ "autoConfirmStep2b" | i18n }}
|
||||
{{ "autoConfirmExtension3" | i18n }}
|
||||
</strong>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
@@ -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";
|
||||
@@ -44,7 +48,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,
|
||||
@@ -338,6 +345,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,
|
||||
|
||||
@@ -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,11 @@ import {
|
||||
getNestedCollectionTree,
|
||||
getFlatCollectionTree,
|
||||
} from "../../admin-console/organizations/collections";
|
||||
import {
|
||||
AutoConfirmPolicy,
|
||||
AutoConfirmPolicyDialogComponent,
|
||||
PolicyEditDialogResult,
|
||||
} from "../../admin-console/organizations/policies";
|
||||
import {
|
||||
CollectionDialogAction,
|
||||
CollectionDialogTabType,
|
||||
@@ -213,6 +222,8 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined;
|
||||
private autoConfirmDialogRef?: DialogRef<PolicyEditDialogResult> | undefined;
|
||||
|
||||
protected showAddCipherBtn: boolean = false;
|
||||
|
||||
organizations$ = this.accountService.activeAccount$
|
||||
@@ -328,6 +339,8 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
private policyService: PolicyService,
|
||||
private unifiedUpgradePromptService: UnifiedUpgradePromptService,
|
||||
private premiumUpgradePromptService: PremiumUpgradePromptService,
|
||||
private autoConfirmService: AutomaticUserConfirmationService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -629,6 +642,8 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
},
|
||||
);
|
||||
void this.unifiedUpgradePromptService.displayUpgradePromptConditionally();
|
||||
|
||||
this.setupAutoConfirm();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -1547,6 +1562,72 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr
|
||||
const cipherView = await this.cipherService.decrypt(_cipher, activeUserId);
|
||||
return cipherView.login?.password;
|
||||
}
|
||||
|
||||
private async openAutoConfirmFeatureDialog(organization: Organization) {
|
||||
if (this.autoConfirmDialogRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.autoConfirmDialogRef = AutoConfirmPolicyDialogComponent.open(this.dialogService, {
|
||||
data: {
|
||||
policy: new AutoConfirmPolicy(),
|
||||
organizationId: organization.id,
|
||||
firstTimeDialog: true,
|
||||
},
|
||||
});
|
||||
|
||||
await lastValueFrom(this.autoConfirmDialogRef.closed);
|
||||
this.autoConfirmDialogRef = undefined;
|
||||
}
|
||||
|
||||
private setupAutoConfirm() {
|
||||
// if the policy is enabled, then the user may only belong to one organization at most.
|
||||
const organization$ = this.organizations$.pipe(map((organizations) => organizations[0]));
|
||||
|
||||
const featureFlag$ = this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm);
|
||||
|
||||
const autoConfirmState$ = this.userId$.pipe(
|
||||
switchMap((userId) => this.autoConfirmService.configuration$(userId)),
|
||||
);
|
||||
|
||||
const policyEnabled$ = combineLatest([
|
||||
this.userId$.pipe(
|
||||
switchMap((userId) => this.policyService.policies$(userId)),
|
||||
map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm && p.enabled)),
|
||||
),
|
||||
organization$,
|
||||
]).pipe(
|
||||
map(
|
||||
([policy, organization]) => (policy && policy.organizationId === organization?.id) ?? false,
|
||||
),
|
||||
);
|
||||
|
||||
zip([organization$, featureFlag$, autoConfirmState$, policyEnabled$, this.userId$])
|
||||
.pipe(
|
||||
first(),
|
||||
switchMap(async ([organization, flagEnabled, autoConfirmState, policyEnabled, userId]) => {
|
||||
const showDialog =
|
||||
flagEnabled &&
|
||||
!policyEnabled &&
|
||||
autoConfirmState.showSetupDialog &&
|
||||
!!organization &&
|
||||
(organization.canManageUsers || organization.canManagePolicies);
|
||||
|
||||
if (showDialog) {
|
||||
await this.openAutoConfirmFeatureDialog(organization);
|
||||
|
||||
await this.autoConfirmService.upsert(userId, {
|
||||
...autoConfirmState,
|
||||
showSetupDialog: false,
|
||||
});
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe({
|
||||
error: (err: unknown) => this.logService.error("Failed to update auto-confirm state", err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5832,16 +5832,16 @@
|
||||
"howToTurnOnAutoConfirm": {
|
||||
"message": "How to turn on automatic user confirmation"
|
||||
},
|
||||
"autoConfirmStep1": {
|
||||
"message": "Open your Bitwarden extension."
|
||||
"autoConfirmExtension1": {
|
||||
"message": "Open your Bitwarden extension"
|
||||
},
|
||||
"autoConfirmStep2a": {
|
||||
"autoConfirmExtension2": {
|
||||
"message": "Select",
|
||||
"description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'"
|
||||
"description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'"
|
||||
},
|
||||
"autoConfirmStep2b": {
|
||||
"message": " Turn on.",
|
||||
"description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'"
|
||||
"autoConfirmExtension3": {
|
||||
"message": " Turn on",
|
||||
"description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'"
|
||||
},
|
||||
"autoConfirmExtensionOpened": {
|
||||
"message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting."
|
||||
|
||||
@@ -16,6 +16,6 @@ export const AUTO_CONFIRM_STATE = UserKeyDefinition.record<AutoConfirmState>(
|
||||
"autoConfirm",
|
||||
{
|
||||
deserializer: (autoConfirmState) => autoConfirmState,
|
||||
clearOn: ["logout"],
|
||||
clearOn: [],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -285,6 +285,8 @@ export class DefaultPolicyService implements PolicyService {
|
||||
case PolicyType.RemoveUnlockWithPin:
|
||||
// Remove Unlock with PIN policy
|
||||
return false;
|
||||
case PolicyType.AutoConfirm:
|
||||
return false;
|
||||
case PolicyType.OrganizationDataOwnership:
|
||||
// organization data ownership policy applies to everyone except admins and owners
|
||||
return organization.isAdmin;
|
||||
|
||||
@@ -36,7 +36,7 @@ export const DELETE_MANAGED_USER_WARNING = new StateDefinition(
|
||||
web: "disk-local",
|
||||
},
|
||||
);
|
||||
export const AUTO_CONFIRM = new StateDefinition("autoConfirm", "disk");
|
||||
export const AUTO_CONFIRM = new StateDefinition("autoConfirm", "disk", { web: "disk-local" });
|
||||
|
||||
// Billing
|
||||
export const BILLING_DISK = new StateDefinition("billing", "disk");
|
||||
|
||||
Reference in New Issue
Block a user