diff --git a/libs/common/src/platform/enums/multiUserAllowList.enum.ts b/libs/common/src/platform/enums/multiUserAllowList.enum.ts new file mode 100644 index 00000000000..66039762549 --- /dev/null +++ b/libs/common/src/platform/enums/multiUserAllowList.enum.ts @@ -0,0 +1,5 @@ +import { NotificationType } from "@bitwarden/common/enums"; + +export const AllowedMultiUserNotificationTypes = new Set([ + NotificationType.AuthRequest, +]); diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts index a23de970247..a70623783dc 100644 --- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts +++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.multiuser.spec.ts @@ -3,9 +3,8 @@ import { BehaviorSubject, bufferCount, firstValueFrom, Subject, ObservedValueOf // 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"; -// TODO: When PM-14943 goes in, uncomment -// 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"; @@ -33,8 +32,7 @@ describe("DefaultServerNotificationsService (multi-user)", () => { let signalRNotificationConnectionService: MockProxy; let authService: MockProxy; let webPushNotificationConnectionService: MockProxy; - // TODO: When PM-14943 goes in, uncomment - // let authRequestAnsweringService: MockProxy; + let authRequestAnsweringService: MockProxy; let configService: MockProxy; let activeUserAccount$: BehaviorSubject>; @@ -75,6 +73,10 @@ describe("DefaultServerNotificationsService (multi-user)", () => { getNotificationsUrl: () => "http://test.example.com", } as Environment); environmentConfigurationService.environment$ = environmentConfiguration$ as any; + // Ensure user-scoped environment lookups return the same test environment stream + environmentConfigurationService.getEnvironment$.mockImplementation( + (_userId: UserId) => environmentConfiguration$.asObservable() as any, + ); userLogoutCallback = jest.fn, [LogoutReason, UserId]>(); @@ -123,8 +125,7 @@ describe("DefaultServerNotificationsService (multi-user)", () => { return webPushSupportStatusByUser.get(userId)!.asObservable(); }); - // TODO: When PM-14943 goes in, uncomment - // authRequestAnsweringService = mock(); + authRequestAnsweringService = mock(); configService = mock(); configService.getFeatureFlag$.mockImplementation((flag: FeatureFlag) => { @@ -145,7 +146,7 @@ describe("DefaultServerNotificationsService (multi-user)", () => { signalRNotificationConnectionService, authService, webPushNotificationConnectionService, - // authRequestAnsweringService, + authRequestAnsweringService, configService, ); }); diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts index faf4066c63a..a7b608f5b56 100644 --- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts +++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.spec.ts @@ -1,11 +1,10 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, Subject } from "rxjs"; +import { BehaviorSubject, bufferCount, firstValueFrom, ObservedValueOf, of, 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"; @@ -74,13 +73,7 @@ describe("NotificationsService", () => { configService = mock(); // For these tests, use the active-user implementation (feature flag disabled) - configService.getFeatureFlag$.mockImplementation((flag: FeatureFlag) => { - const flagValueByFlag: Partial> = { - [FeatureFlag.InactiveUserServerNotification]: false, - [FeatureFlag.PushNotificationsWhenLocked]: true, - }; - return new BehaviorSubject(flagValueByFlag[flag] ?? false) as any; - }); + configService.getFeatureFlag$.mockImplementation(() => of(true)); activeAccount = new BehaviorSubject>(null); accountService.activeAccount$ = activeAccount.asObservable(); @@ -93,6 +86,10 @@ describe("NotificationsService", () => { } as Environment); environmentService.environment$ = environment; + // Ensure user-scoped environment lookups return the same test environment stream + environmentService.getEnvironment$.mockImplementation( + (_userId: UserId) => environment.asObservable() as any, + ); authStatusGetter = Matrix.autoMockMethod( authService.authStatusFor$, diff --git a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts index 94b48d7bb8e..f6343bfcdb9 100644 --- a/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts +++ b/libs/common/src/platform/server-notifications/internal/default-server-notifications.service.ts @@ -18,6 +18,7 @@ import { 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 { AllowedMultiUserNotificationTypes } from "@bitwarden/common/platform/enums/multiUserAllowList.enum"; import { AccountService } from "../../../auth/abstractions/account.service"; import { AuthService } from "../../../auth/abstractions/auth.service"; @@ -66,10 +67,12 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer this.notifications$ = this.configService .getFeatureFlag$(FeatureFlag.InactiveUserServerNotification) .pipe( + distinctUntilChanged(), switchMap((inactiveUserServerNotificationEnabled) => { if (inactiveUserServerNotificationEnabled) { return this.accountService.accounts$.pipe( map((accounts) => Object.keys(accounts) as UserId[]), + distinctUntilChanged(), switchMap((userIds) => { if (userIds.length === 0) { return EMPTY; @@ -110,7 +113,7 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer * @param userId The user id of the user to get the push server notifications for. */ private userNotifications$(userId: UserId) { - return this.environmentService.environment$.pipe( + return this.environmentService.getEnvironment$(userId).pipe( map((env) => env.getNotificationsUrl()), distinctUntilChanged(), switchMap((notificationsUrl) => { @@ -202,15 +205,12 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer this.configService.getFeatureFlag$(FeatureFlag.InactiveUserServerNotification), ) ) { - // Allow-list of notification types that are safe to process for non-active users - const multiUserNotificationTypes = new Set([NotificationType.AuthRequest]); - const activeAccountId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); const isActiveUser = activeAccountId === userId; - if (!isActiveUser && !multiUserNotificationTypes.has(notification.type)) { + if (!isActiveUser && !AllowedMultiUserNotificationTypes.has(notification.type)) { return; } }