1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 19:53:59 +00:00

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

This commit is contained in:
Patrick Pimentel
2025-07-24 11:38:28 -04:00
9 changed files with 150 additions and 1 deletions

View File

@@ -31,6 +31,7 @@ import { DefaultPolicyService } from "@bitwarden/common/admin-console/services/p
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
@@ -41,6 +42,7 @@ import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/ab
import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/auth-request-answering.service";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
@@ -349,6 +351,7 @@ export default class MainBackground {
serverNotificationsService: ServerNotificationsService;
systemNotificationService: SystemNotificationsService;
actionsService: ActionsService;
authRequestAnsweringService: AuthRequestAnsweringServiceAbstraction;
stateService: StateServiceAbstraction;
userNotificationSettingsService: UserNotificationSettingsServiceAbstraction;
autofillSettingsService: AutofillSettingsServiceAbstraction;
@@ -1145,6 +1148,23 @@ export default class MainBackground {
this.systemNotificationService = new UnsupportedSystemNotificationsService();
}
// void Promise.all([this.configService.getFeatureFlag(FeatureFlag.PM14938_BrowserExtensionLoginApproval)])
// .then((isBrowserExtensionLoginApprovalFFOn) => {
// if (isBrowserExtensionLoginApprovalFFOn) {
// this.authRequestAnsweringService = new AuthRequestAnsweringService(
// this.systemNotificationService,
// this.actionsService,
// );
// } else {
// this.authRequestAnsweringService = new UnsupportedAuthRequestAnsweringService();
// }
// });
this.authRequestAnsweringService = new AuthRequestAnsweringService(
this.systemNotificationService,
this.actionsService,
);
this.serverNotificationsService = new DefaultServerNotificationsService(
this.logService,
this.syncService,
@@ -1156,6 +1176,7 @@ export default class MainBackground {
new SignalRConnectionService(this.apiService, this.logService),
this.authService,
this.webPushConnectionService,
this.authRequestAnsweringService,
);
this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.authService);

View File

@@ -588,6 +588,16 @@ const safeProviders: SafeProvider[] = [
PlatformUtilsService,
],
}),
safeProvider({
provide: ActionsService,
useClass: BrowserActionsService,
deps: [LogService, PlatformUtilsService],
}),
safeProvider({
provide: SystemNotificationsService,
useClass: BrowserSystemNotificationService,
deps: [LogService, PlatformUtilsService],
}),
safeProvider({
provide: Fido2UserVerificationService,
useClass: Fido2UserVerificationService,

View File

@@ -93,6 +93,7 @@ import {
InternalAccountService,
} from "@bitwarden/common/auth/abstractions/account.service";
import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/auth/abstractions/anonymous-hub.service";
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
@@ -385,6 +386,9 @@ import {
WINDOW,
} from "./injection-tokens";
import { ModalService } from "./modal.service";
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/auth-request-answering.service";
import { SystemNotificationsService } from "@bitwarden/common/platform/notifications/system-notifications-service";
import { UnsupportedSystemNotificationsService } from "@bitwarden/common/platform/notifications/unsupported-system-notifications.service";
/**
* Provider definitions used in the ngModule.
@@ -951,6 +955,21 @@ const safeProviders: SafeProvider[] = [
useClass: UnsupportedActionsService,
deps: [],
}),
safeProvider({
provide: ActionsService,
useClass: UnsupportedActionsService,
deps: [],
}),
safeProvider({
provide: SystemNotificationsService,
useClass: UnsupportedSystemNotificationsService,
deps: [],
}),
safeProvider({
provide: AuthRequestAnsweringServiceAbstraction,
useClass: AuthRequestAnsweringService,
deps: [SystemNotificationsService, ActionsService],
}),
safeProvider({
provide: ServerNotificationsService,
useClass: devFlagEnabled("noopNotifications")
@@ -967,6 +986,7 @@ const safeProviders: SafeProvider[] = [
SignalRConnectionService,
AuthServiceAbstraction,
WebPushConnectionService,
AuthRequestAnsweringServiceAbstraction,
],
}),
safeProvider({

View File

@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Component, HostListener, OnDestroy, OnInit } from "@angular/core";
import {
AbstractControl,
FormBuilder,
@@ -160,6 +160,21 @@ export class SelfHostedEnvConfigDialogComponent implements OnInit, OnDestroy {
});
}
@HostListener("document:keydown.control.b", ["$event"])
onCtrlB(event: KeyboardEvent) {
if (process.env.ENV === "development") {
event.preventDefault();
this.formGroup.patchValue({
baseUrl: "",
webVaultUrl: "https://localhost:8080",
apiUrl: "http://localhost:4000",
identityUrl: "http://localhost:33656",
iconsUrl: "http://localhost:50024",
notificationsUrl: "http://localhost:61840",
});
}
}
submit = async () => {
this.showErrorSummary = false;

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";
@@ -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$"]>>;
@@ -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";
@@ -55,6 +56,7 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
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 DefaultServerNotificationsService implements ServerNotificationsSer
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,
});