mirror of
https://github.com/bitwarden/browser
synced 2026-02-21 03:43:58 +00:00
[PM-26901] Add notification handler for auto confirm (#18886)
* add notification handler for auto confirm * add missing state check * fix test * isolate angular specific code from shared lib code * clean up * use autoconfirm method * fix test
This commit is contained in:
@@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<bit-simple-dialog dialogSize="small">
|
||||
<span bitDialogTitle>
|
||||
<strong>{{ "warningCapitalized" | i18n }}</strong>
|
||||
</span>
|
||||
<span bitDialogContent>
|
||||
{{ "autoConfirmWarning" | i18n }}
|
||||
<a
|
||||
bitLink
|
||||
href="https://bitwarden.com/help/automatic-confirmation/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ "autoConfirmWarningLink" | i18n }}
|
||||
<i class="bwi bwi-external-link bwi-fw"></i>
|
||||
</a>
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="button" bitButton buttonType="primary" (click)="dialogRef.close(true)">
|
||||
{{ "turnOn" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton buttonType="secondary" (click)="dialogRef.close(false)">
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-simple-dialog>
|
||||
@@ -0,0 +1,26 @@
|
||||
import { DialogRef } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
|
||||
import {
|
||||
ButtonModule,
|
||||
CenterPositionStrategy,
|
||||
DialogModule,
|
||||
DialogService,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: "./auto-confirm-warning-dialog.component.html",
|
||||
imports: [ButtonModule, DialogModule, CommonModule, I18nPipe],
|
||||
})
|
||||
export class AutoConfirmWarningDialogComponent {
|
||||
constructor(public dialogRef: DialogRef<boolean>) {}
|
||||
|
||||
static open(dialogService: DialogService) {
|
||||
return dialogService.open<boolean>(AutoConfirmWarningDialogComponent, {
|
||||
positionStrategy: new CenterPositionStrategy(),
|
||||
});
|
||||
}
|
||||
}
|
||||
2
libs/auto-confirm/src/angular/components/index.ts
Normal file
2
libs/auto-confirm/src/angular/components/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./auto-confirm-extension-dialog.component";
|
||||
export * from "./auto-confirm-warning-dialog.component";
|
||||
@@ -0,0 +1,92 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { Router, UrlTree } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs";
|
||||
|
||||
import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { newGuid } from "@bitwarden/guid";
|
||||
|
||||
import { canAccessAutoConfirmSettings } from "./automatic-user-confirmation-settings.guard";
|
||||
|
||||
describe("canAccessAutoConfirmSettings", () => {
|
||||
let accountService: MockProxy<AccountService>;
|
||||
let autoConfirmService: MockProxy<AutomaticUserConfirmationService>;
|
||||
let toastService: MockProxy<ToastService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let router: MockProxy<Router>;
|
||||
|
||||
const mockUserId = newGuid() as UserId;
|
||||
const mockAccount: Account = {
|
||||
id: mockUserId,
|
||||
email: "test@example.com",
|
||||
emailVerified: true,
|
||||
name: "Test User",
|
||||
creationDate: undefined,
|
||||
};
|
||||
let activeAccount$: BehaviorSubject<Account | null>;
|
||||
|
||||
const runGuard = () => {
|
||||
return TestBed.runInInjectionContext(() => {
|
||||
return canAccessAutoConfirmSettings(null as any, null as any) as Observable<
|
||||
boolean | UrlTree
|
||||
>;
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
accountService = mock<AccountService>();
|
||||
autoConfirmService = mock<AutomaticUserConfirmationService>();
|
||||
toastService = mock<ToastService>();
|
||||
i18nService = mock<I18nService>();
|
||||
router = mock<Router>();
|
||||
|
||||
activeAccount$ = new BehaviorSubject<Account | null>(mockAccount);
|
||||
accountService.activeAccount$ = activeAccount$;
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: AccountService, useValue: accountService },
|
||||
{ provide: AutomaticUserConfirmationService, useValue: autoConfirmService },
|
||||
{ provide: ToastService, useValue: toastService },
|
||||
{ provide: I18nService, useValue: i18nService },
|
||||
{ provide: Router, useValue: router },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow access when user has permission", async () => {
|
||||
autoConfirmService.canManageAutoConfirm$.mockReturnValue(of(true));
|
||||
|
||||
const result = await firstValueFrom(runGuard());
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should redirect to vault when user lacks permission", async () => {
|
||||
autoConfirmService.canManageAutoConfirm$.mockReturnValue(of(false));
|
||||
const mockUrlTree = {} as UrlTree;
|
||||
router.createUrlTree.mockReturnValue(mockUrlTree);
|
||||
|
||||
const result = await firstValueFrom(runGuard());
|
||||
|
||||
expect(result).toBe(mockUrlTree);
|
||||
expect(router.createUrlTree).toHaveBeenCalledWith(["/tabs/vault"]);
|
||||
});
|
||||
|
||||
it("should not emit when active account is null", async () => {
|
||||
activeAccount$.next(null);
|
||||
autoConfirmService.canManageAutoConfirm$.mockReturnValue(of(true));
|
||||
|
||||
let guardEmitted = false;
|
||||
const subscription = runGuard().subscribe(() => {
|
||||
guardEmitted = true;
|
||||
});
|
||||
|
||||
expect(guardEmitted).toBe(false);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { inject } from "@angular/core";
|
||||
import { CanActivateFn, Router } from "@angular/router";
|
||||
import { map, switchMap } from "rxjs";
|
||||
|
||||
import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
export const canAccessAutoConfirmSettings: CanActivateFn = () => {
|
||||
const accountService = inject(AccountService);
|
||||
const autoConfirmService = inject(AutomaticUserConfirmationService);
|
||||
const toastService = inject(ToastService);
|
||||
const i18nService = inject(I18nService);
|
||||
const router = inject(Router);
|
||||
|
||||
return accountService.activeAccount$.pipe(
|
||||
filterOutNullish(),
|
||||
switchMap((user) => autoConfirmService.canManageAutoConfirm$(user.id)),
|
||||
map((canManageAutoConfirm) => {
|
||||
if (!canManageAutoConfirm) {
|
||||
toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: i18nService.t("noPermissionsViewPage"),
|
||||
});
|
||||
|
||||
return router.createUrlTree(["/tabs/vault"]);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
};
|
||||
1
libs/auto-confirm/src/angular/guards/index.ts
Normal file
1
libs/auto-confirm/src/angular/guards/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./automatic-user-confirmation-settings.guard";
|
||||
8
libs/auto-confirm/src/angular/index.ts
Normal file
8
libs/auto-confirm/src/angular/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// Re-export core auto-confirm functionality for convenience
|
||||
export * from "../abstractions";
|
||||
export * from "../models";
|
||||
export * from "../services";
|
||||
|
||||
// Angular-specific exports
|
||||
export * from "./components";
|
||||
export * from "./guards";
|
||||
Reference in New Issue
Block a user