diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 143f5d1f6b3..20457273a19 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1477,6 +1477,7 @@ export default class MainBackground { this.billingAccountProfileStateService, this.configService, this.logService, + this.organizationService, this.phishingDataService, messageListener, ); diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts index e33b4b1b4f1..06306986e6e 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { Observable, of } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -15,6 +16,7 @@ describe("PhishingDetectionService", () => { let billingAccountProfileStateService: BillingAccountProfileStateService; let configService: ConfigService; let logService: LogService; + let organizationService: MockProxy; let phishingDataService: MockProxy; let messageListener: MockProxy; @@ -23,6 +25,7 @@ describe("PhishingDetectionService", () => { billingAccountProfileStateService = {} as any; configService = { getFeatureFlag$: jest.fn(() => of(false)) } as any; logService = { info: jest.fn(), debug: jest.fn(), warning: jest.fn(), error: jest.fn() } as any; + organizationService = mock(); phishingDataService = mock(); messageListener = mock({ messages$(_commandDefinition) { @@ -38,6 +41,7 @@ describe("PhishingDetectionService", () => { billingAccountProfileStateService, configService, logService, + organizationService, phishingDataService, messageListener, ); diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts index 4917e740be8..f4866a68299 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts @@ -12,8 +12,10 @@ import { tap, } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { ProductTierType } from "@bitwarden/common/billing/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -54,6 +56,7 @@ export class PhishingDetectionService { billingAccountProfileStateService: BillingAccountProfileStateService, configService: ConfigService, logService: LogService, + organizationService: OrganizationService, phishingDataService: PhishingDataService, messageListener: MessageListener, ) { @@ -118,6 +121,10 @@ export class PhishingDetectionService { .messages$(PHISHING_DETECTION_CANCEL_COMMAND) .pipe(switchMap((message) => BrowserApi.closeTab(message.tabId))); + // The active account only has access if phishing detection is enabled in feature flags + // The active account has access to the phishing detection through one of the following sources + // 1. Personal Premium subscription + // 2. An organization is a Family plan with usePhishingBlocker AND usersGetPremium enabled const activeAccountHasAccess$ = combineLatest([ accountService.activeAccount$, configService.getFeatureFlag$(FeatureFlag.PhishingDetection), @@ -127,9 +134,25 @@ export class PhishingDetectionService { logService.debug("[PhishingDetectionService] No active account."); return of(false); } - return billingAccountProfileStateService - .hasPremiumFromAnySource$(account.id) - .pipe(map((hasPremium) => hasPremium && featureEnabled)); + + return combineLatest([ + billingAccountProfileStateService.hasPremiumPersonally$(account.id), + organizationService.organizations$(account.id), + ]).pipe( + map(([hasPremium, organizations]) => { + // Check if any organization for the user passes requirements for phishing detection + let hasPhishingDetectionInOrg = false; + if (organizations && organizations.length > 0) { + hasPhishingDetectionInOrg = organizations.some( + (org) => + org.usePhishingBlocker && + org.usersGetPremium && + org.productTierType === ProductTierType.Families, + ); + } + return featureEnabled && (hasPremium || hasPhishingDetectionInOrg); + }), + ); }), );