diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 291a56f52e9..29f8c8001b3 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"; @@ -124,7 +126,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; // eslint-disable-next-line no-restricted-imports -- Needed for service creation import { - DefaultNotificationsService, + DefaultServerNotificationsService, SignalRConnectionService, UnsupportedWebPushConnectionService, WebPushNotificationsApiService, @@ -346,9 +348,10 @@ export default class MainBackground { importService: ImportServiceAbstraction; exportService: VaultExportServiceAbstraction; searchService: SearchServiceAbstraction; - notificationsService: ServerNotificationsService; + serverNotificationsService: ServerNotificationsService; systemNotificationService: SystemNotificationsService; actionsService: ActionsService; + authRequestAnsweringService: AuthRequestAnsweringServiceAbstraction; stateService: StateServiceAbstraction; userNotificationSettingsService: UserNotificationSettingsServiceAbstraction; autofillSettingsService: AutofillSettingsServiceAbstraction; @@ -1145,7 +1148,24 @@ export default class MainBackground { this.systemNotificationService = new UnsupportedSystemNotificationsService(); } - this.notificationsService = new DefaultNotificationsService( + // 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, this.appIdService, @@ -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); @@ -1216,7 +1237,7 @@ export default class MainBackground { this, this.autofillService, this.platformUtilsService as BrowserPlatformUtilsService, - this.notificationsService, + this.serverNotificationsService, this.autofillSettingsService, this.processReloadService, this.environmentService, @@ -1255,7 +1276,7 @@ export default class MainBackground { this.apiService, this.organizationService, this.authService, - this.notificationsService, + this.serverNotificationsService, messageListener, ); @@ -1332,7 +1353,7 @@ export default class MainBackground { this.idleBackground = new IdleBackground( this.vaultTimeoutService, - this.notificationsService, + this.serverNotificationsService, this.accountService, this.vaultTimeoutSettingsService, ); @@ -1390,7 +1411,7 @@ export default class MainBackground { this.endUserNotificationService = new DefaultEndUserNotificationService( this.stateProvider, this.apiService, - this.notificationsService, + this.serverNotificationsService, this.authService, this.logService, ); @@ -1484,7 +1505,7 @@ export default class MainBackground { setTimeout(async () => { await this.fullSync(false); this.backgroundSyncService.init(); - this.notificationsService.startListening(); + this.serverNotificationsService.startListening(); this.taskService.listenForTaskNotifications(); 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 9b52bca3999..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"; @@ -210,8 +211,8 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta import { ServerNotificationsService } from "@bitwarden/common/platform/notifications"; // eslint-disable-next-line no-restricted-imports -- Needed for service creation import { - DefaultNotificationsService, - NoopNotificationsService, + DefaultServerNotificationsService, + UnsupportedServerNotificationsService, SignalRConnectionService, UnsupportedWebPushConnectionService, WebPushConnectionService, @@ -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,11 +955,26 @@ 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") - ? NoopNotificationsService - : DefaultNotificationsService, + ? UnsupportedServerNotificationsService + : DefaultServerNotificationsService, deps: [ LogService, SyncService, @@ -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 9dca079bdba..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"; @@ -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; let authService: MockProxy; let webPushNotificationConnectionService: MockProxy; + let systemNotificationService: MockProxy; let activeAccount: BehaviorSubject>; @@ -52,7 +54,7 @@ describe("NotificationsService", () => { notificationsUrl: string, ) => Subject; - let sut: DefaultNotificationsService; + let sut: DefaultServerNotificationsService; beforeEach(() => { syncService = mock(); @@ -93,7 +95,7 @@ describe("NotificationsService", () => { () => new Subject(), ); - sut = new DefaultNotificationsService( + sut = new DefaultServerNotificationsService( mock(), syncService, appIdService, @@ -104,6 +106,7 @@ describe("NotificationsService", () => { signalRNotificationConnectionService, authService, webPushNotificationConnectionService, + systemNotificationService, ); }); diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-server-notifications.service.ts similarity index 93% rename from libs/common/src/platform/notifications/internal/default-notifications.service.ts rename to libs/common/src/platform/notifications/internal/default-server-notifications.service.ts index 7f366318ded..25e6108b1aa 100644 --- a/libs/common/src/platform/notifications/internal/default-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"; @@ -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; 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, }); diff --git a/libs/common/src/platform/notifications/internal/index.ts b/libs/common/src/platform/notifications/internal/index.ts index 067320ee56c..c6ffd8162b8 100644 --- a/libs/common/src/platform/notifications/internal/index.ts +++ b/libs/common/src/platform/notifications/internal/index.ts @@ -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"; diff --git a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts b/libs/common/src/platform/notifications/internal/unsupported-server-notifications.service.ts similarity index 91% rename from libs/common/src/platform/notifications/internal/noop-notifications.service.ts rename to libs/common/src/platform/notifications/internal/unsupported-server-notifications.service.ts index 513adf918bf..34cd9e9db4c 100644 --- a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/unsupported-server-notifications.service.ts @@ -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 = new Subject(); constructor(private logService: LogService) {} diff --git a/libs/common/src/platform/notifications/server-notifications-service.ts b/libs/common/src/platform/notifications/server-notifications-service.ts index 7e7784de4ba..02d261aca35 100644 --- a/libs/common/src/platform/notifications/server-notifications-service.ts +++ b/libs/common/src/platform/notifications/server-notifications-service.ts @@ -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; /** diff --git a/libs/common/src/platform/notifications/unsupported-system-notifications.service.ts b/libs/common/src/platform/notifications/unsupported-system-notifications.service.ts index 3877043c4ba..32748f9a599 100644 --- a/libs/common/src/platform/notifications/unsupported-system-notifications.service.ts +++ b/libs/common/src/platform/notifications/unsupported-system-notifications.service.ts @@ -15,7 +15,7 @@ export class UnsupportedSystemNotificationsService implements SystemNotification throw new Error("Create OS Notification unsupported."); } - clear(clearInfo: SystemNotificationClearInfo): undefined { + clear(clearInfo: SystemNotificationClearInfo): Promise { throw new Error("Clear OS Notification unsupported."); }