mirror of
https://github.com/bitwarden/browser
synced 2025-12-25 20:53:22 +00:00
[PM-14366] Deprecated active user state from billing state service (#12273)
* Updated billing state provider to not rely on ActiveUserStateProvider * Updated usages * Resolved browser build * Resolved web build * Resolved CLI build * resolved desktop build * Update apps/cli/src/tools/send/commands/create.command.ts Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com> * Move subscription visibility logic from component to service * Resolved unit test failures. Using existing userIds where present * Simplified activeUserId access * Resolved typescript strict errors * Resolved broken unit test * Resolved ts strict error --------- Co-authored-by: ✨ Audrey ✨ <ajensen@bitwarden.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { NEVER, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
@@ -47,16 +48,22 @@ export class AttachmentsV2ViewComponent {
|
||||
private keyService: KeyService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private stateProvider: StateProvider,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
this.subscribeToHasPremiumCheck();
|
||||
this.subscribeToOrgKey();
|
||||
}
|
||||
|
||||
subscribeToHasPremiumCheck() {
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe((data) => {
|
||||
this.canAccessPremium = data;
|
||||
this.accountService.activeAccount$
|
||||
.pipe(
|
||||
switchMap((account) =>
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
|
||||
),
|
||||
takeUntilDestroyed(),
|
||||
)
|
||||
.subscribe((hasPremium) => {
|
||||
this.canAccessPremium = hasPremium;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@ import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { CopyClickDirective } from "@bitwarden/angular/directives/copy-click.directive";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
@@ -26,6 +28,17 @@ describe("LoginCredentialsViewComponent", () => {
|
||||
let fixture: ComponentFixture<LoginCredentialsViewComponent>;
|
||||
|
||||
const hasPremiumFromAnySource$ = new BehaviorSubject<boolean>(true);
|
||||
const mockAccount = {
|
||||
id: "test-user-id" as UserId,
|
||||
email: "test@example.com",
|
||||
emailVerified: true,
|
||||
name: "Test User",
|
||||
type: 0,
|
||||
status: 0,
|
||||
kdf: 0,
|
||||
kdfIterations: 0,
|
||||
};
|
||||
const activeAccount$ = new BehaviorSubject(mockAccount);
|
||||
|
||||
const cipher = {
|
||||
id: "cipher-id",
|
||||
@@ -48,8 +61,11 @@ describe("LoginCredentialsViewComponent", () => {
|
||||
providers: [
|
||||
{
|
||||
provide: BillingAccountProfileStateService,
|
||||
useValue: mock<BillingAccountProfileStateService>({ hasPremiumFromAnySource$ }),
|
||||
useValue: mock<BillingAccountProfileStateService>({
|
||||
hasPremiumFromAnySource$: () => hasPremiumFromAnySource$,
|
||||
}),
|
||||
},
|
||||
{ provide: AccountService, useValue: mock<AccountService>({ activeAccount$ }) },
|
||||
{ provide: PremiumUpgradePromptService, useValue: mock<PremiumUpgradePromptService>() },
|
||||
{ provide: EventCollectionService, useValue: mock<EventCollectionService>({ collect }) },
|
||||
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule, DatePipe } from "@angular/common";
|
||||
import { Component, inject, Input } from "@angular/core";
|
||||
import { Observable, shareReplay } from "rxjs";
|
||||
import { Observable, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -50,10 +51,11 @@ type TotpCodeValues = {
|
||||
export class LoginCredentialsViewComponent {
|
||||
@Input() cipher: CipherView;
|
||||
|
||||
isPremium$: Observable<boolean> =
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$.pipe(
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
isPremium$: Observable<boolean> = this.accountService.activeAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
|
||||
),
|
||||
);
|
||||
showPasswordCount: boolean = false;
|
||||
passwordRevealed: boolean = false;
|
||||
totpCodeCopyObj: TotpCodeValues;
|
||||
@@ -64,6 +66,7 @@ export class LoginCredentialsViewComponent {
|
||||
private i18nService: I18nService,
|
||||
private premiumUpgradeService: PremiumUpgradePromptService,
|
||||
private eventCollectionService: EventCollectionService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
get fido2CredentialCreationDateValue(): string {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -22,6 +23,8 @@ describe("CopyCipherFieldService", () => {
|
||||
let totpService: MockProxy<TotpService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let billingAccountProfileStateService: MockProxy<BillingAccountProfileStateService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
const userId = "userId";
|
||||
|
||||
beforeEach(() => {
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
@@ -31,6 +34,9 @@ describe("CopyCipherFieldService", () => {
|
||||
totpService = mock<TotpService>();
|
||||
i18nService = mock<I18nService>();
|
||||
billingAccountProfileStateService = mock<BillingAccountProfileStateService>();
|
||||
accountService = mock<AccountService>();
|
||||
|
||||
accountService.activeAccount$ = of({ id: userId } as Account);
|
||||
|
||||
service = new CopyCipherFieldService(
|
||||
platformUtilsService,
|
||||
@@ -40,6 +46,7 @@ describe("CopyCipherFieldService", () => {
|
||||
totpService,
|
||||
i18nService,
|
||||
billingAccountProfileStateService,
|
||||
accountService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -128,12 +135,15 @@ describe("CopyCipherFieldService", () => {
|
||||
});
|
||||
|
||||
it("should get TOTP code when allowed from premium", async () => {
|
||||
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(true);
|
||||
billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(true));
|
||||
totpService.getCode.mockResolvedValue("123456");
|
||||
const result = await service.copy(valueToCopy, actionType, cipher, skipReprompt);
|
||||
expect(result).toBeTruthy();
|
||||
expect(totpService.getCode).toHaveBeenCalledWith(valueToCopy);
|
||||
expect(platformUtilsService.copyToClipboard).toHaveBeenCalledWith("123456");
|
||||
expect(billingAccountProfileStateService.hasPremiumFromAnySource$).toHaveBeenCalledWith(
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
it("should get TOTP code when allowed from organization", async () => {
|
||||
@@ -146,11 +156,14 @@ describe("CopyCipherFieldService", () => {
|
||||
});
|
||||
|
||||
it("should return early when the user is not allowed to use TOTP", async () => {
|
||||
billingAccountProfileStateService.hasPremiumFromAnySource$ = of(false);
|
||||
billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||
const result = await service.copy(valueToCopy, actionType, cipher, skipReprompt);
|
||||
expect(result).toBeFalsy();
|
||||
expect(totpService.getCode).not.toHaveBeenCalled();
|
||||
expect(platformUtilsService.copyToClipboard).not.toHaveBeenCalled();
|
||||
expect(billingAccountProfileStateService.hasPremiumFromAnySource$).toHaveBeenCalledWith(
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return early when TOTP is not set", async () => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -87,6 +88,7 @@ export class CopyCipherFieldService {
|
||||
private totpService: TotpService,
|
||||
private i18nService: I18nService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -148,10 +150,16 @@ export class CopyCipherFieldService {
|
||||
* Determines if TOTP generation is allowed for a cipher and user.
|
||||
*/
|
||||
async totpAllowed(cipher: CipherView): Promise<boolean> {
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
if (!activeAccount?.id) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
(cipher?.login?.hasTotp ?? false) &&
|
||||
(cipher.organizationUseTotp ||
|
||||
(await firstValueFrom(this.billingAccountProfileStateService.hasPremiumFromAnySource$)))
|
||||
(await firstValueFrom(
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeAccount.id),
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user