mirror of
https://github.com/bitwarden/browser
synced 2026-01-21 11:53:34 +00:00
Ac/pm 26365 auto confirm extension one time setup dialog (#17339)
* create nav link for auto confirm in settings page * wip * WIP * create auto confirm library * migrate auto confirm files to lib * update imports * fix tests * fix nudge * cleanup, add documentation * clean up * cleanup * fix import * fix more imports * implement one time dialog * add tests * design changes * fix styles * edit copy * fix tests * fix tw issue * fix typo, add tests * CR feedback * more clean up, fix race condition * CR feedback, cache policies, refactor tests * run prettier with updated version * clean up duplicate logic * clean up * add missing export * fix test * fix dialog position * add tests
This commit is contained in:
@@ -4829,6 +4829,21 @@
|
||||
"autoConfirmWarningLink": {
|
||||
"message": "Learn about the risks"
|
||||
},
|
||||
"autoConfirmSetup": {
|
||||
"message": "Automatically confirm new users"
|
||||
},
|
||||
"autoConfirmSetupDesc": {
|
||||
"message": "New users will be automatically confirmed while this device is unlocked."
|
||||
},
|
||||
"autoConfirmSetupHint": {
|
||||
"message": "What are the potential security risks?"
|
||||
},
|
||||
"autoConfirmEnabled": {
|
||||
"message": "Turned on automatic confirmation"
|
||||
},
|
||||
"availableNow": {
|
||||
"message": "Available now"
|
||||
},
|
||||
"accountSecurity": {
|
||||
"message": "Account security"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,10 @@ import { BehaviorSubject, Observable, Subject, of } from "rxjs";
|
||||
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
|
||||
import { NudgeType, NudgesService } from "@bitwarden/angular/vault";
|
||||
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
|
||||
import {
|
||||
AutoConfirmExtensionSetupDialogComponent,
|
||||
AutomaticUserConfirmationService,
|
||||
} from "@bitwarden/auto-confirm";
|
||||
import { CurrentAccountComponent } from "@bitwarden/browser/auth/popup/account-switching/current-account.component";
|
||||
import AutofillService from "@bitwarden/browser/autofill/services/autofill.service";
|
||||
import { PopOutComponent } from "@bitwarden/browser/platform/popup/components/pop-out.component";
|
||||
@@ -136,6 +140,7 @@ class VaultListItemsContainerStubComponent {
|
||||
const mockDialogRef = {
|
||||
close: jest.fn(),
|
||||
afterClosed: jest.fn().mockReturnValue(of(undefined)),
|
||||
closed: of(undefined),
|
||||
} as unknown as import("@bitwarden/components").DialogRef<any, any>;
|
||||
|
||||
jest
|
||||
@@ -145,6 +150,11 @@ jest
|
||||
jest
|
||||
.spyOn(DecryptionFailureDialogComponent, "open")
|
||||
.mockImplementation((_: DialogService, _params: any) => mockDialogRef as any);
|
||||
|
||||
const autoConfirmDialogSpy = jest
|
||||
.spyOn(AutoConfirmExtensionSetupDialogComponent, "open")
|
||||
.mockImplementation((_: DialogService) => mockDialogRef as any);
|
||||
|
||||
jest.spyOn(BrowserApi, "isPopupOpen").mockResolvedValue(false);
|
||||
jest.spyOn(BrowserPopupUtils, "openCurrentPagePopout").mockResolvedValue();
|
||||
|
||||
@@ -222,6 +232,13 @@ describe("VaultV2Component", () => {
|
||||
getFeatureFlag$: jest.fn().mockImplementation((_flag: string) => of(false)),
|
||||
};
|
||||
|
||||
const autoConfirmSvc = {
|
||||
configuration$: jest.fn().mockReturnValue(of({})),
|
||||
canManageAutoConfirm$: jest.fn().mockReturnValue(of(false)),
|
||||
upsert: jest.fn().mockResolvedValue(undefined),
|
||||
autoConfirmUser: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
await TestBed.configureTestingModule({
|
||||
@@ -275,6 +292,10 @@ describe("VaultV2Component", () => {
|
||||
provide: SearchService,
|
||||
useValue: { isCipherSearching$: of(false) },
|
||||
},
|
||||
{
|
||||
provide: AutomaticUserConfirmationService,
|
||||
useValue: autoConfirmSvc,
|
||||
},
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
@@ -588,4 +609,86 @@ describe("VaultV2Component", () => {
|
||||
const spotlights = queryAllSpotlights(fixture);
|
||||
expect(spotlights.length).toBe(0);
|
||||
}));
|
||||
|
||||
describe("AutoConfirmExtensionSetupDialog", () => {
|
||||
beforeEach(() => {
|
||||
autoConfirmDialogSpy.mockClear();
|
||||
});
|
||||
|
||||
it("opens dialog when canManage is true and showBrowserNotification is undefined", fakeAsync(() => {
|
||||
autoConfirmSvc.canManageAutoConfirm$.mockReturnValue(of(true));
|
||||
autoConfirmSvc.configuration$.mockReturnValue(
|
||||
of({
|
||||
enabled: false,
|
||||
showSetupDialog: true,
|
||||
showBrowserNotification: undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
const fixture = TestBed.createComponent(VaultV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
|
||||
void component.ngOnInit();
|
||||
tick();
|
||||
|
||||
expect(autoConfirmDialogSpy).toHaveBeenCalledWith(expect.any(Object));
|
||||
}));
|
||||
|
||||
it("does not open dialog when showBrowserNotification is false", fakeAsync(() => {
|
||||
autoConfirmSvc.canManageAutoConfirm$.mockReturnValue(of(true));
|
||||
autoConfirmSvc.configuration$.mockReturnValue(
|
||||
of({
|
||||
enabled: false,
|
||||
showSetupDialog: true,
|
||||
showBrowserNotification: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const fixture = TestBed.createComponent(VaultV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
|
||||
void component.ngOnInit();
|
||||
tick();
|
||||
|
||||
expect(autoConfirmDialogSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it("does not open dialog when showBrowserNotification is true", fakeAsync(() => {
|
||||
autoConfirmSvc.canManageAutoConfirm$.mockReturnValue(of(true));
|
||||
autoConfirmSvc.configuration$.mockReturnValue(
|
||||
of({
|
||||
enabled: true,
|
||||
showSetupDialog: true,
|
||||
showBrowserNotification: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const fixture = TestBed.createComponent(VaultV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
|
||||
void component.ngOnInit();
|
||||
tick();
|
||||
|
||||
expect(autoConfirmDialogSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it("does not open dialog when canManage is false even if showBrowserNotification is undefined", fakeAsync(() => {
|
||||
autoConfirmSvc.canManageAutoConfirm$.mockReturnValue(of(false));
|
||||
autoConfirmSvc.configuration$.mockReturnValue(
|
||||
of({
|
||||
enabled: false,
|
||||
showSetupDialog: true,
|
||||
showBrowserNotification: undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
const fixture = TestBed.createComponent(VaultV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
|
||||
void component.ngOnInit();
|
||||
tick();
|
||||
|
||||
expect(autoConfirmDialogSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
shareReplay,
|
||||
switchMap,
|
||||
take,
|
||||
withLatestFrom,
|
||||
tap,
|
||||
BehaviorSubject,
|
||||
} from "rxjs";
|
||||
@@ -25,6 +26,11 @@ import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
|
||||
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
|
||||
import { DeactivatedOrg, NoResults, VaultOpen } from "@bitwarden/assets/svg";
|
||||
import {
|
||||
AutoConfirmExtensionSetupDialogComponent,
|
||||
AutoConfirmState,
|
||||
AutomaticUserConfirmationService,
|
||||
} from "@bitwarden/auto-confirm";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
@@ -41,6 +47,7 @@ import {
|
||||
ButtonModule,
|
||||
DialogService,
|
||||
NoItemsModule,
|
||||
ToastService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import {
|
||||
@@ -267,6 +274,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
private introCarouselService: IntroCarouselService,
|
||||
private nudgesService: NudgesService,
|
||||
private router: Router,
|
||||
private autoConfirmService: AutomaticUserConfirmationService,
|
||||
private toastService: ToastService,
|
||||
private vaultProfileService: VaultProfileService,
|
||||
private billingAccountService: BillingAccountProfileStateService,
|
||||
private liveAnnouncer: LiveAnnouncer,
|
||||
@@ -329,6 +338,36 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
|
||||
});
|
||||
});
|
||||
|
||||
const autoConfirmState$ = this.autoConfirmService.configuration$(this.activeUserId);
|
||||
|
||||
combineLatest([
|
||||
this.autoConfirmService.canManageAutoConfirm$(this.activeUserId),
|
||||
autoConfirmState$,
|
||||
])
|
||||
.pipe(
|
||||
filter(([canManage, state]) => canManage && state.showBrowserNotification === undefined),
|
||||
take(1),
|
||||
switchMap(() => AutoConfirmExtensionSetupDialogComponent.open(this.dialogService).closed),
|
||||
withLatestFrom(autoConfirmState$, this.accountService.activeAccount$.pipe(getUserId)),
|
||||
switchMap(([result, state, userId]) => {
|
||||
const newState: AutoConfirmState = {
|
||||
...state,
|
||||
enabled: result ?? false,
|
||||
showBrowserNotification: !result,
|
||||
};
|
||||
|
||||
if (result) {
|
||||
this.toastService.showToast({
|
||||
message: this.i18nService.t("autoConfirmEnabled"),
|
||||
variant: "success",
|
||||
});
|
||||
}
|
||||
|
||||
return this.autoConfirmService.upsert(userId, newState);
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
.subscribe();
|
||||
await this.vaultItemsTransferService.enforceOrganizationDataOwnership(this.activeUserId);
|
||||
|
||||
this.readySubject.next(true);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { DialogRef } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
BadgeComponent,
|
||||
ButtonModule,
|
||||
CenterPositionStrategy,
|
||||
DialogModule,
|
||||
DialogService,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<bit-simple-dialog dialogSize="small" hideIcon>
|
||||
<div class="tw-flex tw-flex-col tw-justify-start" bitDialogTitle>
|
||||
<div class="tw-flex tw-justify-start tw-pb-2">
|
||||
<span bitBadge variant="info"> {{ "availableNow" | i18n }}</span>
|
||||
</div>
|
||||
<div class="tw-flex tw-flex-col">
|
||||
<h3 class="tw-text-start">
|
||||
<strong>
|
||||
{{ "autoConfirmSetup" | i18n }}
|
||||
</strong>
|
||||
</h3>
|
||||
<span class="tw-overflow-y-auto tw-text-start tw-break-words tw-hyphens-auto tw-text-sm">
|
||||
{{ "autoConfirmSetupDesc" | i18n }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container bitDialogFooter>
|
||||
<div class="tw-flex tw-flex-col tw-justify-center">
|
||||
<button
|
||||
class="tw-mb-2"
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
(click)="dialogRef.close(true)"
|
||||
>
|
||||
{{ "turnOn" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
class="tw-mb-4"
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
(click)="dialogRef.close(false)"
|
||||
>
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
<a
|
||||
class="tw-text-sm tw-text-center"
|
||||
bitLink
|
||||
href="https://bitwarden.com/help/automatic-confirmation/"
|
||||
target="_blank"
|
||||
>
|
||||
<strong class="tw-pr-1">
|
||||
{{ "autoConfirmSetupHint" | i18n }}
|
||||
</strong>
|
||||
<i class="bwi bwi-external-link bwi-fw"></i>
|
||||
</a>
|
||||
</div>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
`,
|
||||
imports: [ButtonModule, DialogModule, CommonModule, JslibModule, BadgeComponent],
|
||||
})
|
||||
export class AutoConfirmExtensionSetupDialogComponent {
|
||||
constructor(public dialogRef: DialogRef<boolean>) {}
|
||||
|
||||
static open(dialogService: DialogService) {
|
||||
return dialogService.open<boolean>(AutoConfirmExtensionSetupDialogComponent, {
|
||||
positionStrategy: new CenterPositionStrategy(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,12 @@ import { DialogRef } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
|
||||
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";
|
||||
import {
|
||||
ButtonModule,
|
||||
CenterPositionStrategy,
|
||||
DialogModule,
|
||||
DialogService,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
@Component({
|
||||
@@ -14,6 +19,8 @@ export class AutoConfirmWarningDialogComponent {
|
||||
constructor(public dialogRef: DialogRef<boolean>) {}
|
||||
|
||||
static open(dialogService: DialogService) {
|
||||
return dialogService.open<boolean>(AutoConfirmWarningDialogComponent);
|
||||
return dialogService.open<boolean>(AutoConfirmWarningDialogComponent, {
|
||||
positionStrategy: new CenterPositionStrategy(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./auto-confirm-extension-dialog.component";
|
||||
export * from "./auto-confirm-warning-dialog.component";
|
||||
|
||||
Reference in New Issue
Block a user