1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

Auth/pm 23620/auth request answering service (#15760)

* feat(notification-processing): [PM-19877] System Notification Implementation - Implemented auth request answering service.

* test(notification-processing): [PM-19877] System Notification Implementation - Added tests.
This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-08-28 13:47:05 -04:00
committed by GitHub
parent 3b5342dfb3
commit c828b3c4f4
16 changed files with 569 additions and 26 deletions

View File

@@ -1,9 +1,11 @@
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, of, Subject } from "rxjs";
import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, Subject } from "rxjs";
// 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
import { LogoutReason } from "@bitwarden/auth/common";
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { awaitAsync } from "../../../../spec";
import { Matrix } from "../../../../spec/matrix";
@@ -39,6 +41,7 @@ describe("NotificationsService", () => {
let signalRNotificationConnectionService: MockProxy<SignalRConnectionService>;
let authService: MockProxy<AuthService>;
let webPushNotificationConnectionService: MockProxy<WebPushConnectionService>;
let authRequestAnsweringService: MockProxy<AuthRequestAnsweringServiceAbstraction>;
let configService: MockProxy<ConfigService>;
let activeAccount: BehaviorSubject<ObservedValueOf<AccountService["activeAccount$"]>>;
@@ -66,9 +69,16 @@ describe("NotificationsService", () => {
signalRNotificationConnectionService = mock<SignalRConnectionService>();
authService = mock<AuthService>();
webPushNotificationConnectionService = mock<WorkerWebPushConnectionService>();
authRequestAnsweringService = mock<AuthRequestAnsweringServiceAbstraction>();
configService = mock<ConfigService>();
configService.getFeatureFlag$.mockReturnValue(of(true));
// For these tests, use the active-user implementation (feature flag disabled)
configService.getFeatureFlag$.mockImplementation((flag: FeatureFlag) => {
const flagValueByFlag: Partial<Record<FeatureFlag, boolean>> = {
[FeatureFlag.PushNotificationsWhenLocked]: true,
};
return new BehaviorSubject(flagValueByFlag[flag] ?? false) as any;
});
activeAccount = new BehaviorSubject<ObservedValueOf<AccountService["activeAccount$"]>>(null);
accountService.activeAccount$ = activeAccount.asObservable();
@@ -109,6 +119,7 @@ describe("NotificationsService", () => {
signalRNotificationConnectionService,
authService,
webPushNotificationConnectionService,
authRequestAnsweringService,
configService,
);
});
@@ -116,7 +127,7 @@ describe("NotificationsService", () => {
const mockUser1 = "user1" as UserId;
const mockUser2 = "user2" as UserId;
function emitActiveUser(userId: UserId) {
function emitActiveUser(userId: UserId | null) {
if (userId == null) {
activeAccount.next(null);
} else {

View File

@@ -4,6 +4,7 @@ import {
distinctUntilChanged,
EMPTY,
filter,
firstValueFrom,
map,
mergeMap,
Observable,
@@ -14,6 +15,7 @@ import {
// 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
import { LogoutReason } from "@bitwarden/auth/common";
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AccountService } from "../../../auth/abstractions/account.service";
@@ -57,6 +59,7 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
private readonly signalRConnectionService: SignalRConnectionService,
private readonly authService: AuthService,
private readonly webPushConnectionService: WebPushConnectionService,
private readonly authRequestAnsweringService: AuthRequestAnsweringServiceAbstraction,
private readonly configService: ConfigService,
) {
this.notifications$ = this.accountService.activeAccount$.pipe(
@@ -227,8 +230,16 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification);
break;
case NotificationType.AuthRequest:
// create notification
if (
await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.PM14938_BrowserExtensionLoginApproval),
)
) {
await this.authRequestAnsweringService.receivedPendingAuthRequest(
notification.payload.userId,
notification.payload.id,
);
}
this.messagingService.send("openLoginApproval", {
notificationId: notification.payload.id,
});

View File

@@ -1,6 +1,6 @@
import { Observable } from "rxjs";
// This is currently tailored for chrome extension's api, if safari works
// This is currently tailored for Chrome extension's api, if Safari works
// differently where clicking a notification button produces a different
// identifier we need to reconcile that here.
export const ButtonLocation = Object.freeze({