diff --git a/apps/browser/src/auth/background/service-factories/auth-service.factory.ts b/apps/browser/src/auth/background/service-factories/auth-service.factory.ts index fa52ca6231..bc4e621bc6 100644 --- a/apps/browser/src/auth/background/service-factories/auth-service.factory.ts +++ b/apps/browser/src/auth/background/service-factories/auth-service.factory.ts @@ -23,9 +23,12 @@ import { stateServiceFactory, } from "../../../platform/background/service-factories/state-service.factory"; +import { AccountServiceInitOptions, accountServiceFactory } from "./account-service.factory"; + type AuthServiceFactoryOptions = FactoryOptions; export type AuthServiceInitOptions = AuthServiceFactoryOptions & + AccountServiceInitOptions & MessagingServiceInitOptions & CryptoServiceInitOptions & ApiServiceInitOptions & @@ -41,6 +44,7 @@ export function authServiceFactory( opts, async () => new AuthService( + await accountServiceFactory(cache, opts), await messagingServiceFactory(cache, opts), await cryptoServiceFactory(cache, opts), await apiServiceFactory(cache, opts), diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 3e84b7544b..51417c16fb 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -568,6 +568,7 @@ export default class MainBackground { ); this.authService = new AuthService( + this.accountService, backgroundMessagingService, this.cryptoService, this.apiService, diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 7af40b1ebd..5c6423708a 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -494,6 +494,7 @@ export class Main { ); this.authService = new AuthService( + this.accountService, this.messagingService, this.cryptoService, this.apiService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index beda7cbf4f..57a3fe63ab 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -345,6 +345,7 @@ const typesafeProviders: Array = [ provide: AuthServiceAbstraction, useClass: AuthService, deps: [ + AccountServiceAbstraction, MessagingServiceAbstraction, CryptoServiceAbstraction, ApiServiceAbstraction, diff --git a/libs/common/src/auth/abstractions/auth.service.ts b/libs/common/src/auth/abstractions/auth.service.ts index dc51e2fdb0..9e4fd3cd0b 100644 --- a/libs/common/src/auth/abstractions/auth.service.ts +++ b/libs/common/src/auth/abstractions/auth.service.ts @@ -1,6 +1,11 @@ +import { Observable } from "rxjs"; + import { AuthenticationStatus } from "../enums/authentication-status"; export abstract class AuthService { - getAuthStatus: (userId?: string) => Promise; - logOut: (callback: () => void) => void; + /** Authentication status for the active user */ + abstract activeAccountStatus$: Observable; + /** @deprecated use {@link activeAccountStatus$} instead */ + abstract getAuthStatus: (userId?: string) => Promise; + abstract logOut: (callback: () => void) => void; } diff --git a/libs/common/src/auth/services/auth.service.spec.ts b/libs/common/src/auth/services/auth.service.spec.ts new file mode 100644 index 0000000000..dd4daf8cfa --- /dev/null +++ b/libs/common/src/auth/services/auth.service.spec.ts @@ -0,0 +1,61 @@ +import { MockProxy, mock } from "jest-mock-extended"; +import { firstValueFrom } from "rxjs"; + +import { FakeAccountService, mockAccountServiceWith } from "../../../spec"; +import { ApiService } from "../../abstractions/api.service"; +import { CryptoService } from "../../platform/abstractions/crypto.service"; +import { MessagingService } from "../../platform/abstractions/messaging.service"; +import { StateService } from "../../platform/abstractions/state.service"; +import { Utils } from "../../platform/misc/utils"; +import { UserId } from "../../types/guid"; +import { AuthenticationStatus } from "../enums/authentication-status"; + +import { AuthService } from "./auth.service"; + +describe("AuthService", () => { + let sut: AuthService; + + let accountService: FakeAccountService; + let messagingService: MockProxy; + let cryptoService: MockProxy; + let apiService: MockProxy; + let stateService: MockProxy; + + const userId = Utils.newGuid() as UserId; + + beforeEach(() => { + accountService = mockAccountServiceWith(userId); + messagingService = mock(); + cryptoService = mock(); + apiService = mock(); + stateService = mock(); + + sut = new AuthService( + accountService, + messagingService, + cryptoService, + apiService, + stateService, + ); + }); + + describe("activeAccountStatus$", () => { + test.each([ + AuthenticationStatus.LoggedOut, + AuthenticationStatus.Locked, + AuthenticationStatus.Unlocked, + ])( + `should emit %p when activeAccount$ emits an account with %p auth status`, + async (status) => { + accountService.activeAccountSubject.next({ + id: userId, + email: "email", + name: "name", + status, + }); + + expect(await firstValueFrom(sut.activeAccountStatus$)).toEqual(status); + }, + ); + }); +}); diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 14d49956a4..ae5dd30a36 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -1,18 +1,30 @@ +import { Observable, distinctUntilChanged, map, shareReplay } from "rxjs"; + import { ApiService } from "../../abstractions/api.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { MessagingService } from "../../platform/abstractions/messaging.service"; import { StateService } from "../../platform/abstractions/state.service"; import { KeySuffixOptions } from "../../platform/enums"; +import { AccountService } from "../abstractions/account.service"; import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service"; import { AuthenticationStatus } from "../enums/authentication-status"; export class AuthService implements AuthServiceAbstraction { + activeAccountStatus$: Observable; + constructor( + protected accountService: AccountService, protected messagingService: MessagingService, protected cryptoService: CryptoService, protected apiService: ApiService, protected stateService: StateService, - ) {} + ) { + this.activeAccountStatus$ = this.accountService.activeAccount$.pipe( + map((account) => account.status), + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: false }), + ); + } async getAuthStatus(userId?: string): Promise { // If we don't have an access token or userId, we're logged out