diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 9bf7fb7c90b..75552448ae0 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -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); diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index fdd1f0da8c5..53739f65f07 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -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, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 717ff54b1ca..c1f1f73eee8 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -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({ diff --git a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts index a7ffca9163c..34e1602c58f 100644 --- a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts +++ b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts @@ -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; diff --git a/libs/common/src/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction.ts b/libs/common/src/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction.ts new file mode 100644 index 00000000000..1ca1eb5a23f --- /dev/null +++ b/libs/common/src/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction.ts @@ -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; + + abstract handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise; +} diff --git a/libs/common/src/auth/services/auth-request-answering/auth-request-answering.service.ts b/libs/common/src/auth/services/auth-request-answering/auth-request-answering.service.ts new file mode 100644 index 00000000000..7495acdd0f2 --- /dev/null +++ b/libs/common/src/auth/services/auth-request-answering/auth-request-answering.service.ts @@ -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 { + 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 { + await this.systemNotificationsService.create({ + id: notificationId, + type: ButtonActions.AuthRequestNotification, + title: "Test (i18n)", + body: "Pending Auth Request to Approve (i18n)", + buttons: [], + }); + } +} diff --git a/libs/common/src/auth/services/auth-request-answering/unsupported-auth-request-answering.service.ts b/libs/common/src/auth/services/auth-request-answering/unsupported-auth-request-answering.service.ts new file mode 100644 index 00000000000..fc7f9e439a4 --- /dev/null +++ b/libs/common/src/auth/services/auth-request-answering/unsupported-auth-request-answering.service.ts @@ -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 { + throw new Error("Received pending auth request not supported."); + } + + async receivedPendingAuthRequest(userId: UserId, notificationId: string): Promise { + throw new Error("Received pending auth request not supported."); + } +} diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts index 3842622d328..d8aebe10856 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts @@ -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; let authService: MockProxy; let webPushNotificationConnectionService: MockProxy; + let systemNotificationService: MockProxy; let activeAccount: BehaviorSubject>; @@ -104,6 +106,7 @@ describe("NotificationsService", () => { signalRNotificationConnectionService, authService, webPushNotificationConnectionService, + systemNotificationService, ); }); diff --git a/libs/common/src/platform/notifications/internal/default-server-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-server-notifications.service.ts index 3b024008f4d..25e6108b1aa 100644 --- a/libs/common/src/platform/notifications/internal/default-server-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/default-server-notifications.service.ts @@ -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, });