diff --git a/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts b/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts index 8dc3f53b442..4b86c39a983 100644 --- a/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts +++ b/libs/auto-confirm/src/services/default-auto-confirm.service.spec.ts @@ -19,6 +19,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { ProfileOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-organization.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { UserKeyResponse } from "@bitwarden/common/models/response/user-key.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -27,6 +28,8 @@ import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spe import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { newGuid } from "@bitwarden/guid"; +import { AUTO_CONFIRM_STATE, AutoConfirmState } from "../models/auto-confirm-state.model"; + import { DefaultAutomaticUserConfirmationService } from "./default-auto-confirm.service"; describe("DefaultAutomaticUserConfirmationService", () => { @@ -636,4 +639,73 @@ describe("DefaultAutomaticUserConfirmationService", () => { expect(organizationUserApiService.getPendingAutoConfirmUsers).not.toHaveBeenCalled(); }); }); + + describe("initBulkAutoConfirmOnLoginSweep", () => { + let accountsSubject: BehaviorSubject>; + let authStatusSubject: BehaviorSubject; + + const createSweepService = () => + new DefaultAutomaticUserConfirmationService( + apiService, + organizationUserService, + stateProvider, + organizationService, + organizationUserApiService, + policyService, + authService, + accountService, + configService, + ); + + beforeEach(() => { + accountsSubject = new BehaviorSubject({} as Record); + accountService.accounts$ = accountsSubject; + }); + + it("should trigger sweep when a user account appears already in the Unlocked state (fresh login)", () => { + authStatusSubject = new BehaviorSubject(AuthenticationStatus.Unlocked); + authService.authStatusFor$.mockReturnValue(authStatusSubject); + + const svc = createSweepService(); + const spy = jest + .spyOn(svc as any, "bulkAutoConfirmPendingUsers") + .mockResolvedValue(undefined); + + // Account becomes visible in accounts$ for the first time while already Unlocked + accountsSubject.next({ [mockUserId]: {} }); + + expect(spy).toHaveBeenCalledWith(mockUserId); + }); + + it("should trigger sweep when a user transitions from Locked to Unlocked", () => { + authStatusSubject = new BehaviorSubject(AuthenticationStatus.Locked); + authService.authStatusFor$.mockReturnValue(authStatusSubject); + + const svc = createSweepService(); + const spy = jest + .spyOn(svc as any, "bulkAutoConfirmPendingUsers") + .mockResolvedValue(undefined); + + accountsSubject.next({ [mockUserId]: {} }); + expect(spy).not.toHaveBeenCalled(); + + authStatusSubject.next(AuthenticationStatus.Unlocked); + + expect(spy).toHaveBeenCalledWith(mockUserId); + }); + + it("should not trigger sweep when status stays Locked", () => { + authStatusSubject = new BehaviorSubject(AuthenticationStatus.Locked); + authService.authStatusFor$.mockReturnValue(authStatusSubject); + + const svc = createSweepService(); + const spy = jest + .spyOn(svc as any, "bulkAutoConfirmPendingUsers") + .mockResolvedValue(undefined); + + accountsSubject.next({ [mockUserId]: {} }); + + expect(spy).not.toHaveBeenCalled(); + }); + }); }); diff --git a/libs/auto-confirm/src/services/default-auto-confirm.service.ts b/libs/auto-confirm/src/services/default-auto-confirm.service.ts index 4972f909b30..ad417a5adae 100644 --- a/libs/auto-confirm/src/services/default-auto-confirm.service.ts +++ b/libs/auto-confirm/src/services/default-auto-confirm.service.ts @@ -7,6 +7,7 @@ import { merge, Observable, pairwise, + startWith, switchMap, } from "rxjs"; @@ -55,6 +56,7 @@ export class DefaultAutomaticUserConfirmationService implements AutomaticUserCon merge( ...Object.keys(accounts).map((userId) => this.authService.authStatusFor$(userId as UserId).pipe( + startWith(AuthenticationStatus.LoggedOut), distinctUntilChanged(), pairwise(), filter(