mirror of
https://github.com/bitwarden/browser
synced 2026-02-27 18:13:29 +00:00
Ac/pm 26364 extension UI for auto confirm (#17258)
* 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 * add tests * design changes * 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 * fix test * add missing prop for test mock * clean up
This commit is contained in:
committed by
jaasen-livefront
parent
68d534a63e
commit
3f225119f8
@@ -4811,6 +4811,24 @@
|
||||
"adminConsole": {
|
||||
"message": "Admin Console"
|
||||
},
|
||||
"admin" :{
|
||||
"message": "Admin"
|
||||
},
|
||||
"automaticUserConfirmation": {
|
||||
"message": "Automatic user confirmation"
|
||||
},
|
||||
"automaticUserConfirmationHint": {
|
||||
"message": "Automatically confirm pending users while this device is unlocked"
|
||||
},
|
||||
"autoConfirmOnboardingCallout":{
|
||||
"message": "Save time with automatic user confirmation"
|
||||
},
|
||||
"autoConfirmWarning": {
|
||||
"message": "This could impact your organization’s data security. "
|
||||
},
|
||||
"autoConfirmWarningLink": {
|
||||
"message": "Learn about the risks"
|
||||
},
|
||||
"accountSecurity": {
|
||||
"message": "Account security"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import { firstValueFrom, of, BehaviorSubject } from "rxjs";
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { NudgesService } from "@bitwarden/angular/vault";
|
||||
import { LockService } from "@bitwarden/auth/common";
|
||||
import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@@ -124,6 +125,12 @@ describe("AccountSecurityComponent", () => {
|
||||
{ provide: ToastService, useValue: mock<ToastService>() },
|
||||
{ provide: UserVerificationService, useValue: mock<UserVerificationService>() },
|
||||
{ provide: ValidationService, useValue: validationService },
|
||||
{ provide: LockService, useValue: lockService },
|
||||
{
|
||||
provide: AutomaticUserConfirmationService,
|
||||
useValue: mock<AutomaticUserConfirmationService>(),
|
||||
},
|
||||
{ provide: ConfigService, useValue: configService },
|
||||
{ provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService },
|
||||
],
|
||||
})
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
TwoFactorAuthComponent,
|
||||
TwoFactorAuthGuard,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { canAccessAutoConfirmSettings } from "@bitwarden/auto-confirm";
|
||||
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components";
|
||||
import {
|
||||
LockComponent,
|
||||
@@ -90,6 +91,7 @@ import {
|
||||
} from "../vault/popup/guards/at-risk-passwords.guard";
|
||||
import { clearVaultStateGuard } from "../vault/popup/guards/clear-vault-state.guard";
|
||||
import { IntroCarouselGuard } from "../vault/popup/guards/intro-carousel.guard";
|
||||
import { AdminSettingsComponent } from "../vault/popup/settings/admin-settings.component";
|
||||
import { AppearanceV2Component } from "../vault/popup/settings/appearance-v2.component";
|
||||
import { ArchiveComponent } from "../vault/popup/settings/archive.component";
|
||||
import { DownloadBitwardenComponent } from "../vault/popup/settings/download-bitwarden.component";
|
||||
@@ -332,6 +334,12 @@ const routes: Routes = [
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
},
|
||||
{
|
||||
path: "admin",
|
||||
component: AdminSettingsComponent,
|
||||
canActivate: [authGuard, canAccessAutoConfirmSettings],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
},
|
||||
{
|
||||
path: "clone-cipher",
|
||||
component: AddEditV2Component,
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
|
||||
import { merge, of, Subject } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
CollectionService,
|
||||
OrganizationUserApiService,
|
||||
OrganizationUserService,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { DeviceManagementComponentServiceAbstraction } from "@bitwarden/angular/auth/device-management/device-management-component.service.abstraction";
|
||||
import { ChangePasswordService } from "@bitwarden/angular/auth/password-management/change-password";
|
||||
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
|
||||
@@ -40,11 +44,18 @@ import {
|
||||
LogoutService,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import {
|
||||
AutomaticUserConfirmationService,
|
||||
DefaultAutomaticUserConfirmationService,
|
||||
} from "@bitwarden/auto-confirm";
|
||||
import { ExtensionAuthRequestAnsweringService } from "@bitwarden/browser/auth/services/auth-request-answering/extension-auth-request-answering.service";
|
||||
import { ExtensionNewDeviceVerificationComponentService } from "@bitwarden/browser/auth/services/new-device-verification/extension-new-device-verification-component.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import {
|
||||
AccountService,
|
||||
@@ -745,6 +756,19 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: ExtensionNewDeviceVerificationComponentService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AutomaticUserConfirmationService,
|
||||
useClass: DefaultAutomaticUserConfirmationService,
|
||||
deps: [
|
||||
ConfigService,
|
||||
ApiService,
|
||||
OrganizationUserService,
|
||||
StateProvider,
|
||||
InternalOrganizationServiceAbstraction,
|
||||
OrganizationUserApiService,
|
||||
PolicyService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutTypeService,
|
||||
useClass: BrowserSessionTimeoutTypeService,
|
||||
|
||||
@@ -82,6 +82,24 @@
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
|
||||
@if (showAdminSettingsLink$ | async) {
|
||||
<bit-item>
|
||||
<a bit-item-content routerLink="/admin">
|
||||
<i slot="start" class="bwi bwi-business" aria-hidden="true"></i>
|
||||
<div class="tw-flex tw-items-center tw-justify-center">
|
||||
<p class="tw-pr-2">{{ "admin" | i18n }}</p>
|
||||
@if (showAdminBadge$ | async) {
|
||||
<span bitBadge variant="notification" [attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||
>1</span
|
||||
>
|
||||
}
|
||||
</div>
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
}
|
||||
|
||||
<bit-item>
|
||||
<a bit-item-content routerLink="/about">
|
||||
<i slot="start" class="bwi bwi-info-circle" aria-hidden="true"></i>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { BehaviorSubject, firstValueFrom, of, Subject } from "rxjs";
|
||||
|
||||
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
|
||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
|
||||
import { AutofillBrowserSettingsService } from "@bitwarden/browser/autofill/services/autofill-browser-settings.service";
|
||||
import { BrowserApi } from "@bitwarden/browser/platform/browser/browser-api";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
@@ -42,6 +43,9 @@ describe("SettingsV2Component", () => {
|
||||
defaultBrowserAutofillDisabled$: Subject<boolean>;
|
||||
isBrowserAutofillSettingOverridden: jest.Mock<Promise<boolean>>;
|
||||
};
|
||||
let mockAutoConfirmService: {
|
||||
canManageAutoConfirm$: jest.Mock;
|
||||
};
|
||||
let dialogService: MockProxy<DialogService>;
|
||||
let openSpy: jest.SpyInstance;
|
||||
|
||||
@@ -66,6 +70,10 @@ describe("SettingsV2Component", () => {
|
||||
isBrowserAutofillSettingOverridden: jest.fn().mockResolvedValue(false),
|
||||
};
|
||||
|
||||
mockAutoConfirmService = {
|
||||
canManageAutoConfirm$: jest.fn().mockReturnValue(of(false)),
|
||||
};
|
||||
|
||||
jest.spyOn(BrowserApi, "getBrowserClientVendor").mockReturnValue("Chrome");
|
||||
|
||||
const cfg = TestBed.configureTestingModule({
|
||||
@@ -75,6 +83,7 @@ describe("SettingsV2Component", () => {
|
||||
{ provide: BillingAccountProfileStateService, useValue: mockBillingState },
|
||||
{ provide: NudgesService, useValue: mockNudges },
|
||||
{ provide: AutofillBrowserSettingsService, useValue: mockAutofillSettings },
|
||||
{ provide: AutomaticUserConfirmationService, useValue: mockAutoConfirmService },
|
||||
{ provide: DialogService, useValue: dialogService },
|
||||
{ provide: I18nService, useValue: { t: jest.fn((key: string) => key) } },
|
||||
{ provide: GlobalStateProvider, useValue: new FakeGlobalStateProvider() },
|
||||
|
||||
@@ -7,7 +7,9 @@ import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/compon
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
|
||||
import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
@@ -65,13 +67,25 @@ export class SettingsV2Component {
|
||||
),
|
||||
);
|
||||
|
||||
showAdminBadge$: Observable<boolean> = this.authenticatedAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
this.nudgesService.showNudgeBadge$(NudgeType.AutoConfirmNudge, account.id),
|
||||
),
|
||||
);
|
||||
|
||||
showAutofillBadge$: Observable<boolean> = this.authenticatedAccount$.pipe(
|
||||
switchMap((account) => this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id)),
|
||||
);
|
||||
|
||||
showAdminSettingsLink$: Observable<boolean> = this.accountService.activeAccount$.pipe(
|
||||
getUserId,
|
||||
switchMap((userId) => this.autoConfirmService.canManageAutoConfirm$(userId)),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly nudgesService: NudgesService,
|
||||
private readonly accountService: AccountService,
|
||||
private readonly autoConfirmService: AutomaticUserConfirmationService,
|
||||
private readonly accountProfileStateService: BillingAccountProfileStateService,
|
||||
private readonly dialogService: DialogService,
|
||||
) {}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<popup-page [loading]="formLoading()">
|
||||
<popup-header slot="header" [pageTitle]="'admin' | i18n" showBackButton>
|
||||
<ng-container slot="end">
|
||||
<app-pop-out></app-pop-out>
|
||||
</ng-container>
|
||||
</popup-header>
|
||||
|
||||
<div class="tw-px-1 tw-pt-1">
|
||||
@if (showAutoConfirmSpotlight$ | async) {
|
||||
<bit-spotlight [persistent]="true">
|
||||
<div class="tw-flex tw-flex-row tw-items-center tw-justify-between">
|
||||
<span class="tw-text-sm">
|
||||
{{ "autoConfirmOnboardingCallout" | i18n }}
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-close"
|
||||
size="small"
|
||||
(click)="dismissSpotlight()"
|
||||
class="tw-ml-1 tw-mt-[2px]"
|
||||
[label]="'close' | i18n"
|
||||
></button>
|
||||
</div>
|
||||
</bit-spotlight>
|
||||
}
|
||||
|
||||
<form [formGroup]="adminForm">
|
||||
<bit-card>
|
||||
<bit-switch formControlName="autoConfirm">
|
||||
<bit-label>
|
||||
<span class="tw-text-sm">
|
||||
{{ "automaticUserConfirmation" | i18n }}
|
||||
</span>
|
||||
</bit-label>
|
||||
<bit-hint class="tw-max-w-[18rem]">{{ "automaticUserConfirmationHint" | i18n }}</bit-hint>
|
||||
</bit-switch>
|
||||
</bit-card>
|
||||
</form>
|
||||
</div>
|
||||
</popup-page>
|
||||
@@ -0,0 +1,199 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { provideNoopAnimations } from "@angular/platform-browser/animations";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
import { AutoConfirmState, AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
|
||||
import { PopOutComponent } from "@bitwarden/browser/platform/popup/components/pop-out.component";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
|
||||
|
||||
import { AdminSettingsComponent } from "./admin-settings.component";
|
||||
|
||||
@Component({
|
||||
selector: "popup-header",
|
||||
template: `<ng-content></ng-content>`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
class MockPopupHeaderComponent {
|
||||
readonly pageTitle = input<string>();
|
||||
readonly backAction = input<() => void>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "popup-page",
|
||||
template: `<ng-content></ng-content>`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
class MockPopupPageComponent {
|
||||
readonly loading = input<boolean>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "app-pop-out",
|
||||
template: ``,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
class MockPopOutComponent {
|
||||
readonly show = input<boolean>(true);
|
||||
}
|
||||
|
||||
describe("AdminSettingsComponent", () => {
|
||||
let component: AdminSettingsComponent;
|
||||
let fixture: ComponentFixture<AdminSettingsComponent>;
|
||||
let autoConfirmService: MockProxy<AutomaticUserConfirmationService>;
|
||||
let nudgesService: MockProxy<NudgesService>;
|
||||
let mockDialogService: MockProxy<DialogService>;
|
||||
|
||||
const userId = "test-user-id" as UserId;
|
||||
const mockAutoConfirmState: AutoConfirmState = {
|
||||
enabled: false,
|
||||
showSetupDialog: true,
|
||||
showBrowserNotification: false,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
autoConfirmService = mock<AutomaticUserConfirmationService>();
|
||||
nudgesService = mock<NudgesService>();
|
||||
mockDialogService = mock<DialogService>();
|
||||
|
||||
autoConfirmService.configuration$.mockReturnValue(of(mockAutoConfirmState));
|
||||
autoConfirmService.upsert.mockResolvedValue(undefined);
|
||||
nudgesService.showNudgeSpotlight$.mockReturnValue(of(false));
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AdminSettingsComponent],
|
||||
providers: [
|
||||
provideNoopAnimations(),
|
||||
{ provide: AccountService, useValue: mockAccountServiceWith(userId) },
|
||||
{ provide: AutomaticUserConfirmationService, useValue: autoConfirmService },
|
||||
{ provide: DialogService, useValue: mockDialogService },
|
||||
{ provide: NudgesService, useValue: nudgesService },
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
],
|
||||
})
|
||||
.overrideComponent(AdminSettingsComponent, {
|
||||
remove: {
|
||||
imports: [PopupHeaderComponent, PopupPageComponent, PopOutComponent],
|
||||
},
|
||||
add: {
|
||||
imports: [MockPopupHeaderComponent, MockPopupPageComponent, MockPopOutComponent],
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AdminSettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
describe("initialization", () => {
|
||||
it("should populate form with current auto-confirm state", async () => {
|
||||
const mockState: AutoConfirmState = {
|
||||
enabled: true,
|
||||
showSetupDialog: false,
|
||||
showBrowserNotification: true,
|
||||
};
|
||||
autoConfirmService.configuration$.mockReturnValue(of(mockState));
|
||||
|
||||
await component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component["adminForm"].value).toEqual({
|
||||
autoConfirm: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should populate form with disabled auto-confirm state", async () => {
|
||||
await component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component["adminForm"].value).toEqual({
|
||||
autoConfirm: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("spotlight", () => {
|
||||
beforeEach(async () => {
|
||||
await component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should expose showAutoConfirmSpotlight$ observable", (done) => {
|
||||
nudgesService.showNudgeSpotlight$.mockReturnValue(of(true));
|
||||
|
||||
const newFixture = TestBed.createComponent(AdminSettingsComponent);
|
||||
const newComponent = newFixture.componentInstance;
|
||||
|
||||
newComponent["showAutoConfirmSpotlight$"].subscribe((show) => {
|
||||
expect(show).toBe(true);
|
||||
expect(nudgesService.showNudgeSpotlight$).toHaveBeenCalledWith(
|
||||
NudgeType.AutoConfirmNudge,
|
||||
userId,
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should dismiss spotlight and update state", async () => {
|
||||
autoConfirmService.upsert.mockResolvedValue();
|
||||
|
||||
await component.dismissSpotlight();
|
||||
|
||||
expect(autoConfirmService.upsert).toHaveBeenCalledWith(userId, {
|
||||
...mockAutoConfirmState,
|
||||
showBrowserNotification: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("should use current userId when dismissing spotlight", async () => {
|
||||
autoConfirmService.upsert.mockResolvedValue();
|
||||
|
||||
await component.dismissSpotlight();
|
||||
|
||||
expect(autoConfirmService.upsert).toHaveBeenCalledWith(userId, expect.any(Object));
|
||||
});
|
||||
|
||||
it("should preserve existing state when dismissing spotlight", async () => {
|
||||
const customState: AutoConfirmState = {
|
||||
enabled: true,
|
||||
showSetupDialog: false,
|
||||
showBrowserNotification: true,
|
||||
};
|
||||
autoConfirmService.configuration$.mockReturnValue(of(customState));
|
||||
autoConfirmService.upsert.mockResolvedValue();
|
||||
|
||||
await component.dismissSpotlight();
|
||||
|
||||
expect(autoConfirmService.upsert).toHaveBeenCalledWith(userId, {
|
||||
...customState,
|
||||
showBrowserNotification: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("form validation", () => {
|
||||
beforeEach(async () => {
|
||||
await component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should have a valid form", () => {
|
||||
expect(component["adminForm"].valid).toBe(true);
|
||||
});
|
||||
|
||||
it("should have autoConfirm control", () => {
|
||||
expect(component["adminForm"].controls.autoConfirm).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
OnInit,
|
||||
signal,
|
||||
WritableSignal,
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
||||
import { firstValueFrom, map, Observable, of, switchMap, tap, withLatestFrom } from "rxjs";
|
||||
|
||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
|
||||
import {
|
||||
AutoConfirmWarningDialogComponent,
|
||||
AutomaticUserConfirmationService,
|
||||
} from "@bitwarden/auto-confirm";
|
||||
import { PopOutComponent } from "@bitwarden/browser/platform/popup/components/pop-out.component";
|
||||
import { PopupHeaderComponent } from "@bitwarden/browser/platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "@bitwarden/browser/platform/popup/layout/popup-page.component";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import {
|
||||
BitIconButtonComponent,
|
||||
CardComponent,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
SwitchComponent,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./admin-settings.component.html",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
PopupPageComponent,
|
||||
PopupHeaderComponent,
|
||||
PopOutComponent,
|
||||
FormFieldModule,
|
||||
ReactiveFormsModule,
|
||||
SwitchComponent,
|
||||
CardComponent,
|
||||
SpotlightComponent,
|
||||
BitIconButtonComponent,
|
||||
I18nPipe,
|
||||
],
|
||||
})
|
||||
export class AdminSettingsComponent implements OnInit {
|
||||
private userId$: Observable<UserId> = this.accountService.activeAccount$.pipe(getUserId);
|
||||
|
||||
protected readonly formLoading: WritableSignal<boolean> = signal(true);
|
||||
protected adminForm = this.formBuilder.group({
|
||||
autoConfirm: false,
|
||||
});
|
||||
protected showAutoConfirmSpotlight$: Observable<boolean> = this.userId$.pipe(
|
||||
switchMap((userId) =>
|
||||
this.nudgesService.showNudgeSpotlight$(NudgeType.AutoConfirmNudge, userId),
|
||||
),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private accountService: AccountService,
|
||||
private autoConfirmService: AutomaticUserConfirmationService,
|
||||
private destroyRef: DestroyRef,
|
||||
private dialogService: DialogService,
|
||||
private nudgesService: NudgesService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
const userId = await firstValueFrom(this.userId$);
|
||||
const autoConfirmEnabled = (
|
||||
await firstValueFrom(this.autoConfirmService.configuration$(userId))
|
||||
).enabled;
|
||||
this.adminForm.setValue({ autoConfirm: autoConfirmEnabled });
|
||||
|
||||
this.formLoading.set(false);
|
||||
|
||||
this.adminForm.controls.autoConfirm.valueChanges
|
||||
.pipe(
|
||||
switchMap((newValue) => {
|
||||
if (newValue) {
|
||||
return this.confirm();
|
||||
}
|
||||
return of(false);
|
||||
}),
|
||||
withLatestFrom(this.autoConfirmService.configuration$(userId)),
|
||||
switchMap(([newValue, existingState]) =>
|
||||
this.autoConfirmService.upsert(userId, {
|
||||
...existingState,
|
||||
enabled: newValue,
|
||||
showBrowserNotification: false,
|
||||
}),
|
||||
),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private confirm(): Observable<boolean> {
|
||||
return AutoConfirmWarningDialogComponent.open(this.dialogService).closed.pipe(
|
||||
map((result) => result ?? false),
|
||||
tap((result) => {
|
||||
if (!result) {
|
||||
this.adminForm.setValue({ autoConfirm: false }, { emitEvent: false });
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async dismissSpotlight() {
|
||||
const userId = await firstValueFrom(this.userId$);
|
||||
const state = await firstValueFrom(this.autoConfirmService.configuration$(userId));
|
||||
|
||||
await this.autoConfirmService.upsert(userId, { ...state, showBrowserNotification: false });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user