mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
[PM -20329] browser auth approval client api service (#15161)
* feat: Create methods for calling GET auth-request/pending endpoint. * feat: update banner service on web, and desktop vault * test: updated banner test to use auth request services * fix: DI fixes * feat: add RequestDeviceId to AuthRequestResponse * fix: add Browser Approvals feature flags to desktop vault and web vault banner service * test: fix tests for feature flag
This commit is contained in:
@@ -6,8 +6,10 @@ import { filter, firstValueFrom, map, merge, Subject, timeout } from "rxjs";
|
|||||||
|
|
||||||
import { CollectionService, DefaultCollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService, DefaultCollectionService } from "@bitwarden/admin-console/common";
|
||||||
import {
|
import {
|
||||||
|
AuthRequestApiServiceAbstraction,
|
||||||
AuthRequestService,
|
AuthRequestService,
|
||||||
AuthRequestServiceAbstraction,
|
AuthRequestServiceAbstraction,
|
||||||
|
DefaultAuthRequestApiService,
|
||||||
DefaultLockService,
|
DefaultLockService,
|
||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
LoginEmailServiceAbstraction,
|
LoginEmailServiceAbstraction,
|
||||||
@@ -375,6 +377,7 @@ export default class MainBackground {
|
|||||||
devicesService: DevicesServiceAbstraction;
|
devicesService: DevicesServiceAbstraction;
|
||||||
deviceTrustService: DeviceTrustServiceAbstraction;
|
deviceTrustService: DeviceTrustServiceAbstraction;
|
||||||
authRequestService: AuthRequestServiceAbstraction;
|
authRequestService: AuthRequestServiceAbstraction;
|
||||||
|
authRequestApiService: AuthRequestApiServiceAbstraction;
|
||||||
accountService: AccountServiceAbstraction;
|
accountService: AccountServiceAbstraction;
|
||||||
globalStateProvider: GlobalStateProvider;
|
globalStateProvider: GlobalStateProvider;
|
||||||
pinService: PinServiceAbstraction;
|
pinService: PinServiceAbstraction;
|
||||||
@@ -813,14 +816,16 @@ export default class MainBackground {
|
|||||||
this.appIdService,
|
this.appIdService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.authRequestApiService = new DefaultAuthRequestApiService(this.apiService, this.logService);
|
||||||
|
|
||||||
this.authRequestService = new AuthRequestService(
|
this.authRequestService = new AuthRequestService(
|
||||||
this.appIdService,
|
this.appIdService,
|
||||||
this.accountService,
|
|
||||||
this.masterPasswordService,
|
this.masterPasswordService,
|
||||||
this.keyService,
|
this.keyService,
|
||||||
this.encryptService,
|
this.encryptService,
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.stateProvider,
|
this.stateProvider,
|
||||||
|
this.authRequestApiService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.authService = new AuthService(
|
this.authService = new AuthService(
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
PinServiceAbstraction,
|
PinServiceAbstraction,
|
||||||
UserDecryptionOptionsService,
|
UserDecryptionOptionsService,
|
||||||
SsoUrlService,
|
SsoUrlService,
|
||||||
|
AuthRequestApiServiceAbstraction,
|
||||||
|
DefaultAuthRequestApiService,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||||
@@ -265,6 +267,7 @@ export class ServiceContainer {
|
|||||||
devicesApiService: DevicesApiServiceAbstraction;
|
devicesApiService: DevicesApiServiceAbstraction;
|
||||||
deviceTrustService: DeviceTrustServiceAbstraction;
|
deviceTrustService: DeviceTrustServiceAbstraction;
|
||||||
authRequestService: AuthRequestService;
|
authRequestService: AuthRequestService;
|
||||||
|
authRequestApiService: AuthRequestApiServiceAbstraction;
|
||||||
configApiService: ConfigApiServiceAbstraction;
|
configApiService: ConfigApiServiceAbstraction;
|
||||||
configService: ConfigService;
|
configService: ConfigService;
|
||||||
accountService: AccountService;
|
accountService: AccountService;
|
||||||
@@ -616,14 +619,16 @@ export class ServiceContainer {
|
|||||||
this.stateProvider,
|
this.stateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.authRequestApiService = new DefaultAuthRequestApiService(this.apiService, this.logService);
|
||||||
|
|
||||||
this.authRequestService = new AuthRequestService(
|
this.authRequestService = new AuthRequestService(
|
||||||
this.appIdService,
|
this.appIdService,
|
||||||
this.accountService,
|
|
||||||
this.masterPasswordService,
|
this.masterPasswordService,
|
||||||
this.keyService,
|
this.keyService,
|
||||||
this.encryptService,
|
this.encryptService,
|
||||||
this.apiService,
|
this.apiService,
|
||||||
this.stateProvider,
|
this.stateProvider,
|
||||||
|
this.authRequestApiService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService(
|
this.billingAccountProfileStateService = new DefaultBillingAccountProfileStateService(
|
||||||
|
|||||||
@@ -16,13 +16,16 @@ import { filter, first, map, take } from "rxjs/operators";
|
|||||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
|
import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model";
|
||||||
|
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { EventType } from "@bitwarden/common/enums";
|
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 { 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
@@ -120,6 +123,8 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private cipherService: CipherService,
|
private cipherService: CipherService,
|
||||||
private folderService: FolderService,
|
private folderService: FolderService,
|
||||||
|
private authRequestService: AuthRequestServiceAbstraction,
|
||||||
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -237,11 +242,30 @@ export class VaultComponent implements OnInit, OnDestroy {
|
|||||||
this.searchBarService.setEnabled(true);
|
this.searchBarService.setEnabled(true);
|
||||||
this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault"));
|
this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault"));
|
||||||
|
|
||||||
const authRequest = await this.apiService.getLastAuthRequest();
|
const browserLoginApprovalFeatureFlag = await firstValueFrom(
|
||||||
if (authRequest != null) {
|
this.configService.getFeatureFlag$(FeatureFlag.PM14938_BrowserExtensionLoginApproval),
|
||||||
this.messagingService.send("openLoginApproval", {
|
);
|
||||||
notificationId: authRequest.id,
|
if (browserLoginApprovalFeatureFlag === true) {
|
||||||
});
|
const authRequests = await firstValueFrom(this.authRequestService.getPendingAuthRequests$());
|
||||||
|
// There is a chance that there is more than one auth request in the response we only show the most recent one
|
||||||
|
if (authRequests.length > 0) {
|
||||||
|
const mostRecentAuthRequest = authRequests.reduce((latest, current) => {
|
||||||
|
const latestDate = new Date(latest.creationDate).getTime();
|
||||||
|
const currentDate = new Date(current.creationDate).getTime();
|
||||||
|
return currentDate > latestDate ? current : latest;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.messagingService.send("openLoginApproval", {
|
||||||
|
notificationId: mostRecentAuthRequest.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const authRequest = await this.apiService.getLastAuthRequest();
|
||||||
|
if (authRequest != null) {
|
||||||
|
this.messagingService.send("openLoginApproval", {
|
||||||
|
notificationId: authRequest.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|||||||
import { RouterTestingModule } from "@angular/router/testing";
|
import { RouterTestingModule } from "@angular/router/testing";
|
||||||
import { of, Subject } from "rxjs";
|
import { of, Subject } from "rxjs";
|
||||||
|
|
||||||
import { AuthRequestApiService } from "@bitwarden/auth/common";
|
import { AuthRequestApiServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||||
import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view";
|
import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view";
|
||||||
import { DeviceType } from "@bitwarden/common/enums";
|
import { DeviceType } from "@bitwarden/common/enums";
|
||||||
@@ -79,7 +79,7 @@ describe("DeviceManagementComponent", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: AuthRequestApiService,
|
provide: AuthRequestApiServiceAbstraction,
|
||||||
useValue: {
|
useValue: {
|
||||||
getAuthRequest: jest.fn().mockResolvedValue(mockDeviceResponse),
|
getAuthRequest: jest.fn().mockResolvedValue(mockDeviceResponse),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { LoginApprovalComponent } from "@bitwarden/auth/angular";
|
import { LoginApprovalComponent } from "@bitwarden/auth/angular";
|
||||||
import { AuthRequestApiService } from "@bitwarden/auth/common";
|
import { AuthRequestApiServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||||
import {
|
import {
|
||||||
DevicePendingAuthRequest,
|
DevicePendingAuthRequest,
|
||||||
@@ -61,7 +61,7 @@ export class DeviceManagementComponent {
|
|||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private validationService: ValidationService,
|
private validationService: ValidationService,
|
||||||
private messageListener: MessageListener,
|
private messageListener: MessageListener,
|
||||||
private authRequestApiService: AuthRequestApiService,
|
private authRequestApiService: AuthRequestApiServiceAbstraction,
|
||||||
private destroyRef: DestroyRef,
|
private destroyRef: DestroyRef,
|
||||||
) {
|
) {
|
||||||
void this.initializeDevices();
|
void this.initializeDevices();
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import { TestBed } from "@angular/core/testing";
|
import { TestBed } from "@angular/core/testing";
|
||||||
import { BehaviorSubject, firstValueFrom, take, timeout } from "rxjs";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
import { BehaviorSubject, firstValueFrom, of, take, timeout } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AuthRequestServiceAbstraction,
|
||||||
UserDecryptionOptions,
|
UserDecryptionOptions,
|
||||||
UserDecryptionOptionsServiceAbstraction,
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||||
import { DeviceResponse } from "@bitwarden/common/auth/abstractions/devices/responses/device.response";
|
|
||||||
import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view";
|
import { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view";
|
||||||
|
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { DeviceType } from "@bitwarden/common/enums";
|
import { DeviceType } from "@bitwarden/common/enums";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||||
@@ -41,11 +44,15 @@ describe("VaultBannersService", () => {
|
|||||||
[userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo,
|
[userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo,
|
||||||
});
|
});
|
||||||
const devices$ = new BehaviorSubject<DeviceView[]>([]);
|
const devices$ = new BehaviorSubject<DeviceView[]>([]);
|
||||||
|
const pendingAuthRequests$ = new BehaviorSubject<Array<AuthRequestResponse>>([]);
|
||||||
|
let configService: MockProxy<ConfigService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
lastSync$.next(new Date("2024-05-14"));
|
lastSync$.next(new Date("2024-05-14"));
|
||||||
isSelfHost.mockClear();
|
isSelfHost.mockClear();
|
||||||
getEmailVerified.mockClear().mockResolvedValue(true);
|
getEmailVerified.mockClear().mockResolvedValue(true);
|
||||||
|
configService = mock<ConfigService>();
|
||||||
|
configService.getFeatureFlag$.mockImplementation(() => of(true));
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
@@ -88,6 +95,14 @@ describe("VaultBannersService", () => {
|
|||||||
provide: DevicesServiceAbstraction,
|
provide: DevicesServiceAbstraction,
|
||||||
useValue: { getDevices$: () => devices$ },
|
useValue: { getDevices$: () => devices$ },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: AuthRequestServiceAbstraction,
|
||||||
|
useValue: { getPendingAuthRequests$: () => pendingAuthRequests$ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ConfigService,
|
||||||
|
useValue: configService,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -286,31 +301,25 @@ describe("VaultBannersService", () => {
|
|||||||
|
|
||||||
describe("PendingAuthRequest", () => {
|
describe("PendingAuthRequest", () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
let deviceResponse: DeviceResponse;
|
let authRequestResponse: AuthRequestResponse;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
deviceResponse = new DeviceResponse({
|
authRequestResponse = new AuthRequestResponse({
|
||||||
Id: "device1",
|
id: "authRequest1",
|
||||||
UserId: userId,
|
deviceId: "device1",
|
||||||
Name: "Test Device",
|
deviceName: "Test Device",
|
||||||
Identifier: "test-device",
|
deviceType: DeviceType.Android,
|
||||||
Type: DeviceType.Android,
|
creationDate: now.toISOString(),
|
||||||
CreationDate: now.toISOString(),
|
requestApproved: null,
|
||||||
RevisionDate: now.toISOString(),
|
|
||||||
IsTrusted: false,
|
|
||||||
});
|
});
|
||||||
// Reset devices list, single user state, and active user state before each test
|
// Reset devices list, single user state, and active user state before each test
|
||||||
devices$.next([]);
|
pendingAuthRequests$.next([]);
|
||||||
fakeStateProvider.singleUser.states.clear();
|
fakeStateProvider.singleUser.states.clear();
|
||||||
fakeStateProvider.activeUser.states.clear();
|
fakeStateProvider.activeUser.states.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows pending auth request banner when there is a pending request", async () => {
|
it("shows pending auth request banner when there is a pending request", async () => {
|
||||||
deviceResponse.devicePendingAuthRequest = {
|
pendingAuthRequests$.next([new AuthRequestResponse(authRequestResponse)]);
|
||||||
id: "123",
|
|
||||||
creationDate: now.toISOString(),
|
|
||||||
};
|
|
||||||
devices$.next([new DeviceView(deviceResponse)]);
|
|
||||||
|
|
||||||
service = TestBed.inject(VaultBannersService);
|
service = TestBed.inject(VaultBannersService);
|
||||||
|
|
||||||
@@ -318,8 +327,7 @@ describe("VaultBannersService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not show pending auth request banner when there are no pending requests", async () => {
|
it("does not show pending auth request banner when there are no pending requests", async () => {
|
||||||
deviceResponse.devicePendingAuthRequest = null;
|
pendingAuthRequests$.next([]);
|
||||||
devices$.next([new DeviceView(deviceResponse)]);
|
|
||||||
|
|
||||||
service = TestBed.inject(VaultBannersService);
|
service = TestBed.inject(VaultBannersService);
|
||||||
|
|
||||||
@@ -327,11 +335,7 @@ describe("VaultBannersService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("dismisses pending auth request banner", async () => {
|
it("dismisses pending auth request banner", async () => {
|
||||||
deviceResponse.devicePendingAuthRequest = {
|
pendingAuthRequests$.next([new AuthRequestResponse(authRequestResponse)]);
|
||||||
id: "123",
|
|
||||||
creationDate: now.toISOString(),
|
|
||||||
};
|
|
||||||
devices$.next([new DeviceView(deviceResponse)]);
|
|
||||||
|
|
||||||
service = TestBed.inject(VaultBannersService);
|
service = TestBed.inject(VaultBannersService);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take } from "rxjs";
|
import { Observable, combineLatest, firstValueFrom, map, filter, mergeMap, take } from "rxjs";
|
||||||
|
|
||||||
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
import {
|
||||||
|
AuthRequestServiceAbstraction,
|
||||||
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
|
} from "@bitwarden/auth/common";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import {
|
import {
|
||||||
StateProvider,
|
StateProvider,
|
||||||
@@ -66,20 +71,33 @@ export class VaultBannersService {
|
|||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||||
private devicesService: DevicesServiceAbstraction,
|
private devicesService: DevicesServiceAbstraction,
|
||||||
|
private authRequestService: AuthRequestServiceAbstraction,
|
||||||
|
private configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/** Returns true when the pending auth request banner should be shown */
|
/** Returns true when the pending auth request banner should be shown */
|
||||||
async shouldShowPendingAuthRequestBanner(userId: UserId): Promise<boolean> {
|
async shouldShowPendingAuthRequestBanner(userId: UserId): Promise<boolean> {
|
||||||
const devices = await firstValueFrom(this.devicesService.getDevices$());
|
|
||||||
const hasPendingRequest = devices.some(
|
|
||||||
(device) => device.response?.devicePendingAuthRequest != null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes(
|
const alreadyDismissed = (await this.getBannerDismissedState(userId)).includes(
|
||||||
VisibleVaultBanner.PendingAuthRequest,
|
VisibleVaultBanner.PendingAuthRequest,
|
||||||
);
|
);
|
||||||
|
// TODO: PM-20439 remove feature flag
|
||||||
|
const browserLoginApprovalFeatureFlag = await firstValueFrom(
|
||||||
|
this.configService.getFeatureFlag$(FeatureFlag.PM14938_BrowserExtensionLoginApproval),
|
||||||
|
);
|
||||||
|
if (browserLoginApprovalFeatureFlag === true) {
|
||||||
|
const pendingAuthRequests = await firstValueFrom(
|
||||||
|
this.authRequestService.getPendingAuthRequests$(),
|
||||||
|
);
|
||||||
|
|
||||||
return hasPendingRequest && !alreadyDismissed;
|
return pendingAuthRequests.length > 0 && !alreadyDismissed;
|
||||||
|
} else {
|
||||||
|
const devices = await firstValueFrom(this.devicesService.getDevices$());
|
||||||
|
const hasPendingRequest = devices.some(
|
||||||
|
(device) => device.response?.devicePendingAuthRequest != null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return hasPendingRequest && !alreadyDismissed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldShowPremiumBanner$(userId: UserId): Observable<boolean> {
|
shouldShowPremiumBanner$(userId: UserId): Observable<boolean> {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import {
|
|||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import {
|
import {
|
||||||
AuthRequestApiService,
|
AuthRequestApiServiceAbstraction,
|
||||||
AuthRequestService,
|
AuthRequestService,
|
||||||
AuthRequestServiceAbstraction,
|
AuthRequestServiceAbstraction,
|
||||||
DefaultAuthRequestApiService,
|
DefaultAuthRequestApiService,
|
||||||
@@ -1181,6 +1181,11 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: DevicesServiceImplementation,
|
useClass: DevicesServiceImplementation,
|
||||||
deps: [DevicesApiServiceAbstraction, AppIdServiceAbstraction],
|
deps: [DevicesApiServiceAbstraction, AppIdServiceAbstraction],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: AuthRequestApiServiceAbstraction,
|
||||||
|
useClass: DefaultAuthRequestApiService,
|
||||||
|
deps: [ApiServiceAbstraction, LogService],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DeviceTrustServiceAbstraction,
|
provide: DeviceTrustServiceAbstraction,
|
||||||
useClass: DeviceTrustService,
|
useClass: DeviceTrustService,
|
||||||
@@ -1205,12 +1210,12 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: AuthRequestService,
|
useClass: AuthRequestService,
|
||||||
deps: [
|
deps: [
|
||||||
AppIdServiceAbstraction,
|
AppIdServiceAbstraction,
|
||||||
AccountServiceAbstraction,
|
|
||||||
InternalMasterPasswordServiceAbstraction,
|
InternalMasterPasswordServiceAbstraction,
|
||||||
KeyService,
|
KeyService,
|
||||||
EncryptService,
|
EncryptService,
|
||||||
ApiServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
StateProvider,
|
StateProvider,
|
||||||
|
AuthRequestApiServiceAbstraction,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
@@ -1477,11 +1482,6 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: DefaultCipherAuthorizationService,
|
useClass: DefaultCipherAuthorizationService,
|
||||||
deps: [CollectionService, OrganizationServiceAbstraction, AccountServiceAbstraction],
|
deps: [CollectionService, OrganizationServiceAbstraction, AccountServiceAbstraction],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
|
||||||
provide: AuthRequestApiService,
|
|
||||||
useClass: DefaultAuthRequestApiService,
|
|
||||||
deps: [ApiServiceAbstraction, LogService],
|
|
||||||
}),
|
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: LoginApprovalComponentServiceAbstraction,
|
provide: LoginApprovalComponentServiceAbstraction,
|
||||||
useClass: DefaultLoginApprovalComponentService,
|
useClass: DefaultLoginApprovalComponentService,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { UserId } from "@bitwarden/common/types/guid";
|
|||||||
import { ButtonModule, LinkModule, ToastService } from "@bitwarden/components";
|
import { ButtonModule, LinkModule, ToastService } from "@bitwarden/components";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||||
|
|
||||||
import { AuthRequestApiService } from "../../common/abstractions/auth-request-api.service";
|
import { AuthRequestApiServiceAbstraction } from "../../common/abstractions/auth-request-api.service";
|
||||||
import { LoginViaAuthRequestCacheService } from "../../common/services/auth-request/default-login-via-auth-request-cache.service";
|
import { LoginViaAuthRequestCacheService } from "../../common/services/auth-request/default-login-via-auth-request-cache.service";
|
||||||
|
|
||||||
// FIXME: update to use a const object instead of a typescript enum
|
// FIXME: update to use a const object instead of a typescript enum
|
||||||
@@ -85,7 +85,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
|
|||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private anonymousHubService: AnonymousHubService,
|
private anonymousHubService: AnonymousHubService,
|
||||||
private appIdService: AppIdService,
|
private appIdService: AppIdService,
|
||||||
private authRequestApiService: AuthRequestApiService,
|
private authRequestApiService: AuthRequestApiServiceAbstraction,
|
||||||
private authRequestService: AuthRequestServiceAbstraction,
|
private authRequestService: AuthRequestServiceAbstraction,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private cryptoFunctionService: CryptoFunctionService,
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
|
import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
|
||||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||||
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
|
|
||||||
|
export abstract class AuthRequestApiServiceAbstraction {
|
||||||
|
/**
|
||||||
|
* Gets a list of pending auth requests based on the user. There will only be one AuthRequest per device and the
|
||||||
|
* AuthRequest will be the most recent pending request.
|
||||||
|
*
|
||||||
|
* @returns A promise that resolves to a list response containing auth request responses.
|
||||||
|
*/
|
||||||
|
abstract getPendingAuthRequests(): Promise<ListResponse<AuthRequestResponse>>;
|
||||||
|
|
||||||
export abstract class AuthRequestApiService {
|
|
||||||
/**
|
/**
|
||||||
* Gets an auth request by its ID.
|
* Gets an auth request by its ID.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ export abstract class AuthRequestServiceAbstraction {
|
|||||||
* @throws If `userId` is not provided.
|
* @throws If `userId` is not provided.
|
||||||
*/
|
*/
|
||||||
abstract clearAdminAuthRequest: (userId: UserId) => Promise<void>;
|
abstract clearAdminAuthRequest: (userId: UserId) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* Gets a list of standard pending auth requests for the user.
|
||||||
|
* @returns An observable of an array of auth request.
|
||||||
|
* The array will be empty if there are no pending auth requests.
|
||||||
|
*/
|
||||||
|
abstract getPendingAuthRequests$(): Observable<Array<AuthRequestResponse>>;
|
||||||
/**
|
/**
|
||||||
* Approve or deny an auth request.
|
* Approve or deny an auth request.
|
||||||
* @param approve True to approve, false to deny.
|
* @param approve True to approve, false to deny.
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
|
import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request";
|
||||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||||
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
|
||||||
import { AuthRequestApiService } from "../../abstractions/auth-request-api.service";
|
import { AuthRequestApiServiceAbstraction } from "../../abstractions/auth-request-api.service";
|
||||||
|
|
||||||
export class DefaultAuthRequestApiService implements AuthRequestApiService {
|
export class DefaultAuthRequestApiService implements AuthRequestApiServiceAbstraction {
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async getPendingAuthRequests(): Promise<ListResponse<AuthRequestResponse>> {
|
||||||
|
const path = `/auth-requests/pending`;
|
||||||
|
const r = await this.apiService.send("GET", path, null, true, true);
|
||||||
|
return new ListResponse(r, AuthRequestResponse);
|
||||||
|
}
|
||||||
|
|
||||||
async getAuthRequest(requestId: string): Promise<AuthRequestResponse> {
|
async getAuthRequest(requestId: string): Promise<AuthRequestResponse> {
|
||||||
try {
|
try {
|
||||||
const path = `/auth-requests/${requestId}`;
|
const path = `/auth-requests/${requestId}`;
|
||||||
|
|||||||
@@ -10,23 +10,23 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
|
import { DefaultAuthRequestApiService } from "./auth-request-api.service";
|
||||||
import { AuthRequestService } from "./auth-request.service";
|
import { AuthRequestService } from "./auth-request.service";
|
||||||
|
|
||||||
describe("AuthRequestService", () => {
|
describe("AuthRequestService", () => {
|
||||||
let sut: AuthRequestService;
|
let sut: AuthRequestService;
|
||||||
|
|
||||||
const stateProvider = mock<StateProvider>();
|
const stateProvider = mock<StateProvider>();
|
||||||
let accountService: FakeAccountService;
|
|
||||||
let masterPasswordService: FakeMasterPasswordService;
|
let masterPasswordService: FakeMasterPasswordService;
|
||||||
const appIdService = mock<AppIdService>();
|
const appIdService = mock<AppIdService>();
|
||||||
const keyService = mock<KeyService>();
|
const keyService = mock<KeyService>();
|
||||||
const encryptService = mock<EncryptService>();
|
const encryptService = mock<EncryptService>();
|
||||||
const apiService = mock<ApiService>();
|
const apiService = mock<ApiService>();
|
||||||
|
const authRequestApiService = mock<DefaultAuthRequestApiService>();
|
||||||
|
|
||||||
let mockPrivateKey: Uint8Array;
|
let mockPrivateKey: Uint8Array;
|
||||||
let mockPublicKey: Uint8Array;
|
let mockPublicKey: Uint8Array;
|
||||||
@@ -34,17 +34,16 @@ describe("AuthRequestService", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
accountService = mockAccountServiceWith(mockUserId);
|
|
||||||
masterPasswordService = new FakeMasterPasswordService();
|
masterPasswordService = new FakeMasterPasswordService();
|
||||||
|
|
||||||
sut = new AuthRequestService(
|
sut = new AuthRequestService(
|
||||||
appIdService,
|
appIdService,
|
||||||
accountService,
|
|
||||||
masterPasswordService,
|
masterPasswordService,
|
||||||
keyService,
|
keyService,
|
||||||
encryptService,
|
encryptService,
|
||||||
apiService,
|
apiService,
|
||||||
stateProvider,
|
stateProvider,
|
||||||
|
authRequestApiService,
|
||||||
);
|
);
|
||||||
|
|
||||||
mockPrivateKey = new Uint8Array(64);
|
mockPrivateKey = new Uint8Array(64);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { Observable, Subject, firstValueFrom } from "rxjs";
|
import { Observable, Subject, defer, firstValueFrom, map } from "rxjs";
|
||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
|
||||||
import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable";
|
import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable";
|
||||||
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
|
import { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request";
|
||||||
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
|
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||||
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
|
import { AuthRequestPushNotification } from "@bitwarden/common/models/response/notification.response";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
@@ -24,6 +24,7 @@ import { UserId } from "@bitwarden/common/types/guid";
|
|||||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
|
import { AuthRequestApiServiceAbstraction } from "../../abstractions/auth-request-api.service";
|
||||||
import { AuthRequestServiceAbstraction } from "../../abstractions/auth-request.service.abstraction";
|
import { AuthRequestServiceAbstraction } from "../../abstractions/auth-request.service.abstraction";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,12 +50,12 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private appIdService: AppIdService,
|
private appIdService: AppIdService,
|
||||||
private accountService: AccountService,
|
|
||||||
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
private keyService: KeyService,
|
private keyService: KeyService,
|
||||||
private encryptService: EncryptService,
|
private encryptService: EncryptService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private stateProvider: StateProvider,
|
private stateProvider: StateProvider,
|
||||||
|
private authRequestApiService: AuthRequestApiServiceAbstraction,
|
||||||
) {
|
) {
|
||||||
this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable();
|
this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable();
|
||||||
this.adminLoginApproved$ = this.adminLoginApprovedSubject.asObservable();
|
this.adminLoginApproved$ = this.adminLoginApprovedSubject.asObservable();
|
||||||
@@ -91,6 +92,19 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
|
|||||||
await this.stateProvider.setUserState(ADMIN_AUTH_REQUEST_KEY, null, userId);
|
await this.stateProvider.setUserState(ADMIN_AUTH_REQUEST_KEY, null, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Gets the list of all standard (not admin approval) pending AuthRequests.
|
||||||
|
*/
|
||||||
|
getPendingAuthRequests$(): Observable<Array<AuthRequestResponse>> {
|
||||||
|
return defer(() => this.authRequestApiService.getPendingAuthRequests()).pipe(
|
||||||
|
map((authRequestResponses: ListResponse<AuthRequestResponse>) => {
|
||||||
|
return authRequestResponses.data.map((authRequestResponse: AuthRequestResponse) => {
|
||||||
|
return new AuthRequestResponse(authRequestResponse);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async approveOrDenyAuthRequest(
|
async approveOrDenyAuthRequest(
|
||||||
approve: boolean,
|
approve: boolean,
|
||||||
authRequest: AuthRequestResponse,
|
authRequest: AuthRequestResponse,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export class AuthRequestResponse extends BaseResponse {
|
|||||||
responseDate?: string;
|
responseDate?: string;
|
||||||
isAnswered: boolean;
|
isAnswered: boolean;
|
||||||
isExpired: boolean;
|
isExpired: boolean;
|
||||||
|
deviceId?: string; // could be null or empty
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
@@ -33,6 +34,7 @@ export class AuthRequestResponse extends BaseResponse {
|
|||||||
this.creationDate = this.getResponseProperty("CreationDate");
|
this.creationDate = this.getResponseProperty("CreationDate");
|
||||||
this.requestApproved = this.getResponseProperty("RequestApproved");
|
this.requestApproved = this.getResponseProperty("RequestApproved");
|
||||||
this.responseDate = this.getResponseProperty("ResponseDate");
|
this.responseDate = this.getResponseProperty("ResponseDate");
|
||||||
|
this.deviceId = this.getResponseProperty("RequestDeviceId");
|
||||||
|
|
||||||
const requestDate = new Date(this.creationDate);
|
const requestDate = new Date(this.creationDate);
|
||||||
const requestDateUTC = Date.UTC(
|
const requestDateUTC = Date.UTC(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export enum FeatureFlag {
|
|||||||
PM16117_SetInitialPasswordRefactor = "pm-16117-set-initial-password-refactor",
|
PM16117_SetInitialPasswordRefactor = "pm-16117-set-initial-password-refactor",
|
||||||
PM16117_ChangeExistingPasswordRefactor = "pm-16117-change-existing-password-refactor",
|
PM16117_ChangeExistingPasswordRefactor = "pm-16117-change-existing-password-refactor",
|
||||||
PM9115_TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence",
|
PM9115_TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence",
|
||||||
|
PM14938_BrowserExtensionLoginApproval = "pm-14938-browser-extension-login-approvals",
|
||||||
|
|
||||||
/* Autofill */
|
/* Autofill */
|
||||||
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
|
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
|
||||||
@@ -105,6 +106,7 @@ export const DefaultFeatureFlagValue = {
|
|||||||
[FeatureFlag.PM16117_SetInitialPasswordRefactor]: FALSE,
|
[FeatureFlag.PM16117_SetInitialPasswordRefactor]: FALSE,
|
||||||
[FeatureFlag.PM16117_ChangeExistingPasswordRefactor]: FALSE,
|
[FeatureFlag.PM16117_ChangeExistingPasswordRefactor]: FALSE,
|
||||||
[FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE,
|
[FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE,
|
||||||
|
[FeatureFlag.PM14938_BrowserExtensionLoginApproval]: FALSE,
|
||||||
|
|
||||||
/* Billing */
|
/* Billing */
|
||||||
[FeatureFlag.TrialPaymentOptional]: FALSE,
|
[FeatureFlag.TrialPaymentOptional]: FALSE,
|
||||||
|
|||||||
Reference in New Issue
Block a user