mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +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:
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
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 { DeviceView } from "@bitwarden/common/auth/abstractions/devices/views/device.view";
|
||||
import { DeviceType } from "@bitwarden/common/enums";
|
||||
@@ -79,7 +79,7 @@ describe("DeviceManagementComponent", () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AuthRequestApiService,
|
||||
provide: AuthRequestApiServiceAbstraction,
|
||||
useValue: {
|
||||
getAuthRequest: jest.fn().mockResolvedValue(mockDeviceResponse),
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
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 {
|
||||
DevicePendingAuthRequest,
|
||||
@@ -61,7 +61,7 @@ export class DeviceManagementComponent {
|
||||
private toastService: ToastService,
|
||||
private validationService: ValidationService,
|
||||
private messageListener: MessageListener,
|
||||
private authRequestApiService: AuthRequestApiService,
|
||||
private authRequestApiService: AuthRequestApiServiceAbstraction,
|
||||
private destroyRef: DestroyRef,
|
||||
) {
|
||||
void this.initializeDevices();
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
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 {
|
||||
AuthRequestServiceAbstraction,
|
||||
UserDecryptionOptions,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
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 { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
@@ -41,11 +44,15 @@ describe("VaultBannersService", () => {
|
||||
[userId]: { email: "test@bitwarden.com", emailVerified: true, name: "name" } as AccountInfo,
|
||||
});
|
||||
const devices$ = new BehaviorSubject<DeviceView[]>([]);
|
||||
const pendingAuthRequests$ = new BehaviorSubject<Array<AuthRequestResponse>>([]);
|
||||
let configService: MockProxy<ConfigService>;
|
||||
|
||||
beforeEach(() => {
|
||||
lastSync$.next(new Date("2024-05-14"));
|
||||
isSelfHost.mockClear();
|
||||
getEmailVerified.mockClear().mockResolvedValue(true);
|
||||
configService = mock<ConfigService>();
|
||||
configService.getFeatureFlag$.mockImplementation(() => of(true));
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
@@ -88,6 +95,14 @@ describe("VaultBannersService", () => {
|
||||
provide: DevicesServiceAbstraction,
|
||||
useValue: { getDevices$: () => devices$ },
|
||||
},
|
||||
{
|
||||
provide: AuthRequestServiceAbstraction,
|
||||
useValue: { getPendingAuthRequests$: () => pendingAuthRequests$ },
|
||||
},
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: configService,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
@@ -286,31 +301,25 @@ describe("VaultBannersService", () => {
|
||||
|
||||
describe("PendingAuthRequest", () => {
|
||||
const now = new Date();
|
||||
let deviceResponse: DeviceResponse;
|
||||
let authRequestResponse: AuthRequestResponse;
|
||||
|
||||
beforeEach(() => {
|
||||
deviceResponse = new DeviceResponse({
|
||||
Id: "device1",
|
||||
UserId: userId,
|
||||
Name: "Test Device",
|
||||
Identifier: "test-device",
|
||||
Type: DeviceType.Android,
|
||||
CreationDate: now.toISOString(),
|
||||
RevisionDate: now.toISOString(),
|
||||
IsTrusted: false,
|
||||
authRequestResponse = new AuthRequestResponse({
|
||||
id: "authRequest1",
|
||||
deviceId: "device1",
|
||||
deviceName: "Test Device",
|
||||
deviceType: DeviceType.Android,
|
||||
creationDate: now.toISOString(),
|
||||
requestApproved: null,
|
||||
});
|
||||
// Reset devices list, single user state, and active user state before each test
|
||||
devices$.next([]);
|
||||
pendingAuthRequests$.next([]);
|
||||
fakeStateProvider.singleUser.states.clear();
|
||||
fakeStateProvider.activeUser.states.clear();
|
||||
});
|
||||
|
||||
it("shows pending auth request banner when there is a pending request", async () => {
|
||||
deviceResponse.devicePendingAuthRequest = {
|
||||
id: "123",
|
||||
creationDate: now.toISOString(),
|
||||
};
|
||||
devices$.next([new DeviceView(deviceResponse)]);
|
||||
pendingAuthRequests$.next([new AuthRequestResponse(authRequestResponse)]);
|
||||
|
||||
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 () => {
|
||||
deviceResponse.devicePendingAuthRequest = null;
|
||||
devices$.next([new DeviceView(deviceResponse)]);
|
||||
pendingAuthRequests$.next([]);
|
||||
|
||||
service = TestBed.inject(VaultBannersService);
|
||||
|
||||
@@ -327,11 +335,7 @@ describe("VaultBannersService", () => {
|
||||
});
|
||||
|
||||
it("dismisses pending auth request banner", async () => {
|
||||
deviceResponse.devicePendingAuthRequest = {
|
||||
id: "123",
|
||||
creationDate: now.toISOString(),
|
||||
};
|
||||
devices$.next([new DeviceView(deviceResponse)]);
|
||||
pendingAuthRequests$.next([new AuthRequestResponse(authRequestResponse)]);
|
||||
|
||||
service = TestBed.inject(VaultBannersService);
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
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 { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||
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 {
|
||||
StateProvider,
|
||||
@@ -66,20 +71,33 @@ export class VaultBannersService {
|
||||
private syncService: SyncService,
|
||||
private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
|
||||
private devicesService: DevicesServiceAbstraction,
|
||||
private authRequestService: AuthRequestServiceAbstraction,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/** Returns true when the pending auth request banner should be shown */
|
||||
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(
|
||||
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> {
|
||||
|
||||
Reference in New Issue
Block a user