From b92685dcd96c01d94641ddfa3f974cda837a3c91 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Tue, 14 Jun 2022 12:13:41 +1000 Subject: [PATCH] [EC-243] Grant premium status when member accepts org invite (#2870) * Rename setCanAccessPremium to setHasPremiumPersonally * Add hasPremiumFromOrganization * Refactor stateService methods * Add getHasPremiumPersonally, deprecate tokenService.getPremium --- .../web/src/app/settings/premium.component.ts | 4 +-- .../src/app/settings/settings.component.ts | 4 +-- .../app/settings/subscription.component.ts | 6 ++-- .../settings/user-subscription.component.ts | 6 ++-- libs/common/src/abstractions/state.service.ts | 5 ++- libs/common/src/abstractions/token.service.ts | 1 - libs/common/src/models/domain/account.ts | 1 + .../src/models/response/profileResponse.ts | 6 ++-- libs/common/src/services/state.service.ts | 34 +++++++++++++++++-- libs/common/src/services/sync.service.ts | 3 +- libs/common/src/services/token.service.ts | 9 ----- 11 files changed, 52 insertions(+), 27 deletions(-) diff --git a/apps/web/src/app/settings/premium.component.ts b/apps/web/src/app/settings/premium.component.ts index 5b9566e5dc..a345f7ddf0 100644 --- a/apps/web/src/app/settings/premium.component.ts +++ b/apps/web/src/app/settings/premium.component.ts @@ -45,8 +45,8 @@ export class PremiumComponent implements OnInit { async ngOnInit() { this.canAccessPremium = await this.stateService.getCanAccessPremium(); - const premium = await this.tokenService.getPremium(); - if (premium) { + const premiumPersonally = await this.stateService.getHasPremiumPersonally(); + if (premiumPersonally) { this.router.navigate(["/settings/subscription/user-subscription"]); return; } diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts index 9756ba41ad..f0559b8dce 100644 --- a/apps/web/src/app/settings/settings.component.ts +++ b/apps/web/src/app/settings/settings.component.ts @@ -51,9 +51,9 @@ export class SettingsComponent implements OnInit, OnDestroy { } async load() { - this.premium = await this.tokenService.getPremium(); + this.premium = await this.stateService.getHasPremiumPersonally(); this.hasFamilySponsorshipAvailable = await this.organizationService.canManageSponsorships(); - const hasPremiumFromOrg = await this.stateService.getCanAccessPremium(); + const hasPremiumFromOrg = await this.stateService.getHasPremiumFromOrganization(); let billing = null; if (!this.selfHosted) { billing = await this.apiService.getUserBillingHistory(); diff --git a/apps/web/src/app/settings/subscription.component.ts b/apps/web/src/app/settings/subscription.component.ts index 48dc67f945..62322eb03b 100644 --- a/apps/web/src/app/settings/subscription.component.ts +++ b/apps/web/src/app/settings/subscription.component.ts @@ -1,7 +1,7 @@ import { Component } from "@angular/core"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { TokenService } from "jslib-common/abstractions/token.service"; +import { StateService } from "jslib-common/abstractions/state.service"; @Component({ selector: "app-subscription", @@ -12,12 +12,12 @@ export class SubscriptionComponent { selfHosted: boolean; constructor( - private tokenService: TokenService, + private stateService: StateService, private platformUtilsService: PlatformUtilsService ) {} async ngOnInit() { - this.hasPremium = await this.tokenService.getPremium(); + this.hasPremium = await this.stateService.getHasPremiumPersonally(); this.selfHosted = this.platformUtilsService.isSelfHost(); } diff --git a/apps/web/src/app/settings/user-subscription.component.ts b/apps/web/src/app/settings/user-subscription.component.ts index ec369ddc7f..a91099d9a4 100644 --- a/apps/web/src/app/settings/user-subscription.component.ts +++ b/apps/web/src/app/settings/user-subscription.component.ts @@ -5,7 +5,7 @@ import { ApiService } from "jslib-common/abstractions/api.service"; import { I18nService } from "jslib-common/abstractions/i18n.service"; import { LogService } from "jslib-common/abstractions/log.service"; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -import { TokenService } from "jslib-common/abstractions/token.service"; +import { StateService } from "jslib-common/abstractions/state.service"; import { SubscriptionResponse } from "jslib-common/models/response/subscriptionResponse"; @Component({ @@ -25,7 +25,7 @@ export class UserSubscriptionComponent implements OnInit { reinstatePromise: Promise; constructor( - private tokenService: TokenService, + private stateService: StateService, private apiService: ApiService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, @@ -45,7 +45,7 @@ export class UserSubscriptionComponent implements OnInit { return; } - if (this.tokenService.getPremium()) { + if (this.stateService.getHasPremiumPersonally()) { this.loading = true; this.sub = await this.apiService.getUserSubscription(); } else { diff --git a/libs/common/src/abstractions/state.service.ts b/libs/common/src/abstractions/state.service.ts index 0cf5060554..ec24884e3c 100644 --- a/libs/common/src/abstractions/state.service.ts +++ b/libs/common/src/abstractions/state.service.ts @@ -58,7 +58,10 @@ export abstract class StateService { getBiometricUnlock: (options?: StorageOptions) => Promise; setBiometricUnlock: (value: boolean, options?: StorageOptions) => Promise; getCanAccessPremium: (options?: StorageOptions) => Promise; - setCanAccessPremium: (value: boolean, options?: StorageOptions) => Promise; + getHasPremiumPersonally: (options?: StorageOptions) => Promise; + setHasPremiumPersonally: (value: boolean, options?: StorageOptions) => Promise; + setHasPremiumFromOrganization: (value: boolean, options?: StorageOptions) => Promise; + getHasPremiumFromOrganization: (options?: StorageOptions) => Promise; getClearClipboard: (options?: StorageOptions) => Promise; setClearClipboard: (value: number, options?: StorageOptions) => Promise; getCollapsedGroupings: (options?: StorageOptions) => Promise; diff --git a/libs/common/src/abstractions/token.service.ts b/libs/common/src/abstractions/token.service.ts index f04704942a..ab60760e73 100644 --- a/libs/common/src/abstractions/token.service.ts +++ b/libs/common/src/abstractions/token.service.ts @@ -26,7 +26,6 @@ export abstract class TokenService { getEmail: () => Promise; getEmailVerified: () => Promise; getName: () => Promise; - getPremium: () => Promise; getIssuer: () => Promise; getIsExternal: () => Promise; } diff --git a/libs/common/src/models/domain/account.ts b/libs/common/src/models/domain/account.ts index e03f9be347..97c854e94f 100644 --- a/libs/common/src/models/domain/account.ts +++ b/libs/common/src/models/domain/account.ts @@ -90,6 +90,7 @@ export class AccountProfile { everBeenUnlocked?: boolean; forcePasswordReset?: boolean; hasPremiumPersonally?: boolean; + hasPremiumFromOrganization?: boolean; lastSync?: string; userId?: string; usesKeyConnector?: boolean; diff --git a/libs/common/src/models/response/profileResponse.ts b/libs/common/src/models/response/profileResponse.ts index 2c3e66fb20..80eb985f44 100644 --- a/libs/common/src/models/response/profileResponse.ts +++ b/libs/common/src/models/response/profileResponse.ts @@ -9,7 +9,8 @@ export class ProfileResponse extends BaseResponse { email: string; emailVerified: boolean; masterPasswordHint: string; - premium: boolean; + premiumPersonally: boolean; + premiumFromOrganization: boolean; culture: string; twoFactorEnabled: boolean; key: string; @@ -28,7 +29,8 @@ export class ProfileResponse extends BaseResponse { this.email = this.getResponseProperty("Email"); this.emailVerified = this.getResponseProperty("EmailVerified"); this.masterPasswordHint = this.getResponseProperty("MasterPasswordHint"); - this.premium = this.getResponseProperty("Premium"); + this.premiumPersonally = this.getResponseProperty("Premium"); + this.premiumFromOrganization = this.getResponseProperty("PremiumFromOrganization"); this.culture = this.getResponseProperty("Culture"); this.twoFactorEnabled = this.getResponseProperty("TwoFactorEnabled"); this.key = this.getResponseProperty("Key"); diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts index 73d4c1b73c..e0e6b1451c 100644 --- a/libs/common/src/services/state.service.ts +++ b/libs/common/src/services/state.service.ts @@ -337,13 +337,41 @@ export class StateService< return false; } + return ( + (await this.getHasPremiumPersonally(options)) || + (await this.getHasPremiumFromOrganization(options)) + ); + } + + async getHasPremiumPersonally(options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); - if (account.profile.hasPremiumPersonally) { + return account?.profile?.hasPremiumPersonally; + } + + async setHasPremiumPersonally(value: boolean, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.profile.hasPremiumPersonally = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getHasPremiumFromOrganization(options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + + if (account.profile?.hasPremiumFromOrganization) { return true; } + // TODO: older server versions won't send the hasPremiumFromOrganization flag, so we're keeping the old logic + // for backwards compatibility. It can be removed after everyone has upgraded. const organizations = await this.getOrganizations(options); if (organizations == null) { return false; @@ -359,11 +387,11 @@ export class StateService< return false; } - async setCanAccessPremium(value: boolean, options?: StorageOptions): Promise { + async setHasPremiumFromOrganization(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); - account.profile.hasPremiumPersonally = value; + account.profile.hasPremiumFromOrganization = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) diff --git a/libs/common/src/services/sync.service.ts b/libs/common/src/services/sync.service.ts index 14a2ed5d2d..c054a211f1 100644 --- a/libs/common/src/services/sync.service.ts +++ b/libs/common/src/services/sync.service.ts @@ -304,7 +304,8 @@ export class SyncService implements SyncServiceAbstraction { await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); await this.stateService.setSecurityStamp(response.securityStamp); await this.stateService.setEmailVerified(response.emailVerified); - await this.stateService.setCanAccessPremium(response.premium); + await this.stateService.setHasPremiumPersonally(response.premiumPersonally); + await this.stateService.setHasPremiumFromOrganization(response.premiumFromOrganization); await this.stateService.setForcePasswordReset(response.forcePasswordReset); await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector); diff --git a/libs/common/src/services/token.service.ts b/libs/common/src/services/token.service.ts index a47cca7c38..0922fa8751 100644 --- a/libs/common/src/services/token.service.ts +++ b/libs/common/src/services/token.service.ts @@ -169,15 +169,6 @@ export class TokenService implements TokenServiceAbstraction { return decoded.name as string; } - async getPremium(): Promise { - const decoded = await this.decodeToken(); - if (typeof decoded.premium === "undefined") { - return false; - } - - return decoded.premium as boolean; - } - async getIssuer(): Promise { const decoded = await this.decodeToken(); if (typeof decoded.iss === "undefined") {