1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-24 08:33:29 +00:00

fix(browser-approval): [PM-23620] Auth Request Answering Service - Work in progress.

This commit is contained in:
Patrick Pimentel
2025-07-24 11:26:51 -04:00
parent 49e7f15eda
commit 924adb500d
13 changed files with 174 additions and 25 deletions

View File

@@ -0,0 +1,8 @@
import { SystemNotificationEvent } from "@bitwarden/common/platform/notifications/system-notifications-service";
import { UserId } from "@bitwarden/user-core";
export abstract class AuthRequestAnsweringServiceAbstraction {
abstract receivedPendingAuthRequest(userId: UserId, notificationId: string): Promise<void>;
abstract handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise<void>;
}

View File

@@ -0,0 +1,49 @@
import { filter, mergeMap } from "rxjs";
import { ActionsService } from "@bitwarden/common/platform/actions";
import {
ButtonActions,
ButtonLocation,
SystemNotificationEvent,
SystemNotificationsService,
} from "@bitwarden/common/platform/notifications/system-notifications-service";
import { UserId } from "@bitwarden/user-core";
import { AuthRequestAnsweringServiceAbstraction } from "../../abstractions/auth-request-answering/auth-request-answering.service.abstraction";
export class AuthRequestAnsweringService implements AuthRequestAnsweringServiceAbstraction {
constructor(
private readonly systemNotificationsService: SystemNotificationsService,
private readonly actionService: ActionsService,
) {
this.systemNotificationsService.notificationClicked$
.pipe(
filter(
(event: SystemNotificationEvent) => event.type === ButtonActions.AuthRequestNotification,
),
mergeMap((event: SystemNotificationEvent) =>
this.handleAuthRequestNotificationClicked(event),
),
)
.subscribe();
}
async handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise<void> {
if (event.buttonIdentifier === ButtonLocation.NotificationButton) {
// TODO: Uncomment this before going into review
// await this.systemNotificationService.clear({
// id: event.id,
// })
await this.actionService.openPopup();
}
}
async receivedPendingAuthRequest(userId: UserId, notificationId: string): Promise<void> {
await this.systemNotificationsService.create({
id: notificationId,
type: ButtonActions.AuthRequestNotification,
title: "Test (i18n)",
body: "Pending Auth Request to Approve (i18n)",
buttons: [],
});
}
}

View File

@@ -0,0 +1,17 @@
import { SystemNotificationEvent } from "@bitwarden/common/platform/notifications/system-notifications-service";
import { UserId } from "@bitwarden/user-core";
import { AuthRequestAnsweringServiceAbstraction } from "../../abstractions/auth-request-answering/auth-request-answering.service.abstraction";
export class UnsupportedAuthRequestAnsweringService
implements AuthRequestAnsweringServiceAbstraction
{
constructor() {}
async handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise<void> {
throw new Error("Received pending auth request not supported.");
}
async receivedPendingAuthRequest(userId: UserId, notificationId: string): Promise<void> {
throw new Error("Received pending auth request not supported.");
}
}

View File

@@ -4,6 +4,7 @@ import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, Subject
// 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 { SystemNotificationsService } from "@bitwarden/common/platform/notifications/system-notifications-service";
import { awaitAsync } from "../../../../spec";
import { Matrix } from "../../../../spec/matrix";
@@ -21,9 +22,9 @@ import { SupportStatus } from "../../misc/support-status";
import { SyncService } from "../../sync";
import {
DefaultNotificationsService,
DefaultServerNotificationsService,
DISABLED_NOTIFICATIONS_URL,
} from "./default-notifications.service";
} from "./default-server-notifications.service";
import { SignalRConnectionService, SignalRNotification } from "./signalr-connection.service";
import { WebPushConnectionService, WebPushConnector } from "./webpush-connection.service";
import { WorkerWebPushConnectionService } from "./worker-webpush-connection.service";
@@ -38,6 +39,7 @@ describe("NotificationsService", () => {
let signalRNotificationConnectionService: MockProxy<SignalRConnectionService>;
let authService: MockProxy<AuthService>;
let webPushNotificationConnectionService: MockProxy<WebPushConnectionService>;
let systemNotificationService: MockProxy<SystemNotificationsService>;
let activeAccount: BehaviorSubject<ObservedValueOf<AccountService["activeAccount$"]>>;
@@ -52,7 +54,7 @@ describe("NotificationsService", () => {
notificationsUrl: string,
) => Subject<SignalRNotification>;
let sut: DefaultNotificationsService;
let sut: DefaultServerNotificationsService;
beforeEach(() => {
syncService = mock<SyncService>();
@@ -93,7 +95,7 @@ describe("NotificationsService", () => {
() => new Subject<SignalRNotification>(),
);
sut = new DefaultNotificationsService(
sut = new DefaultServerNotificationsService(
mock<LogService>(),
syncService,
appIdService,
@@ -104,6 +106,7 @@ describe("NotificationsService", () => {
signalRNotificationConnectionService,
authService,
webPushNotificationConnectionService,
systemNotificationService,
);
});

View File

@@ -14,6 +14,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 { AccountService } from "../../../auth/abstractions/account.service";
import { AuthService } from "../../../auth/abstractions/auth.service";
@@ -32,14 +33,14 @@ import { EnvironmentService } from "../../abstractions/environment.service";
import { LogService } from "../../abstractions/log.service";
import { MessagingService } from "../../abstractions/messaging.service";
import { supportSwitch } from "../../misc/support-status";
import { ServerNotificationsService as NotificationsServiceAbstraction } from "../server-notifications-service";
import { ServerNotificationsService } from "../server-notifications-service";
import { ReceiveMessage, SignalRConnectionService } from "./signalr-connection.service";
import { WebPushConnectionService } from "./webpush-connection.service";
export const DISABLED_NOTIFICATIONS_URL = "http://-";
export class DefaultNotificationsService implements NotificationsServiceAbstraction {
export class DefaultServerNotificationsService implements ServerNotificationsService {
notifications$: Observable<readonly [NotificationResponse, UserId]>;
private activitySubject = new BehaviorSubject<"active" | "inactive">("active");
@@ -55,6 +56,7 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
private readonly signalRConnectionService: SignalRConnectionService,
private readonly authService: AuthService,
private readonly webPushConnectionService: WebPushConnectionService,
private readonly authRequestAnsweringService: AuthRequestAnsweringServiceAbstraction,
) {
this.notifications$ = this.accountService.activeAccount$.pipe(
map((account) => account?.id),
@@ -213,6 +215,10 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification);
break;
case NotificationType.AuthRequest:
await this.authRequestAnsweringService.receivedPendingAuthRequest(
notification.payload.userId,
notification.payload.id,
);
this.messagingService.send("openLoginApproval", {
notificationId: notification.payload.id,
});

View File

@@ -1,7 +1,7 @@
export * from "./worker-webpush-connection.service";
export * from "./signalr-connection.service";
export * from "./default-notifications.service";
export * from "./noop-notifications.service";
export * from "./default-server-notifications.service";
export * from "./unsupported-server-notifications.service";
export * from "./unsupported-webpush-connection.service";
export * from "./webpush-connection.service";
export * from "./websocket-webpush-connection.service";

View File

@@ -6,7 +6,7 @@ import { UserId } from "@bitwarden/common/types/guid";
import { LogService } from "../../abstractions/log.service";
import { ServerNotificationsService } from "../server-notifications-service";
export class NoopNotificationsService implements ServerNotificationsService {
export class UnsupportedServerNotificationsService implements ServerNotificationsService {
notifications$: Observable<readonly [NotificationResponse, UserId]> = new Subject();
constructor(private logService: LogService) {}

View File

@@ -4,7 +4,7 @@ import { NotificationResponse } from "@bitwarden/common/models/response/notifica
import { UserId } from "@bitwarden/common/types/guid";
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Needed to link to API
import type { DefaultNotificationsService } from "./internal";
import type { DefaultServerNotificationsService } from "./internal";
/**
* A service offering abilities to interact with push notifications from the server.
@@ -13,7 +13,7 @@ export abstract class ServerNotificationsService {
/**
* @deprecated This method should not be consumed, an observable to listen to server
* notifications will be available one day but it is not ready to be consumed generally.
* Please add code reacting to notifications in {@link DefaultNotificationsService.processNotification}
* Please add code reacting to notifications in {@link DefaultServerNotificationsService.processNotification}
*/
abstract notifications$: Observable<readonly [NotificationResponse, UserId]>;
/**

View File

@@ -15,7 +15,7 @@ export class UnsupportedSystemNotificationsService implements SystemNotification
throw new Error("Create OS Notification unsupported.");
}
clear(clearInfo: SystemNotificationClearInfo): undefined {
clear(clearInfo: SystemNotificationClearInfo): Promise<undefined> {
throw new Error("Clear OS Notification unsupported.");
}