From 38e8e299d0444617cc20aca13e3bb3c33a08f6b6 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:38:43 +0200 Subject: [PATCH] Fix percentage values in impersonation mode with unrestricted access --- .../src/app/portfolio/portfolio.controller.ts | 10 +++++----- .../redact-values-in-response.interceptor.ts | 2 +- .../account-detail-dialog.component.ts | 11 +++++++++-- .../account-detail-dialog.html | 4 ++-- .../interfaces/interfaces.ts | 2 +- .../pages/accounts/accounts-page.component.ts | 6 ++++-- .../allocations/allocations-page.component.ts | 19 +++++++++++++++---- .../analysis/analysis-page.component.ts | 17 ++++++++++++++++- .../portfolio/analysis/analysis-page.html | 12 +++--------- libs/common/src/lib/permissions.ts | 12 ++++++------ 10 files changed, 62 insertions(+), 33 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index f6e8648d58..644764d736 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -136,7 +136,7 @@ export class PortfolioController { if ( hasReadRestrictedAccessPermission({ impersonationId, - user: this.request.user + accesses: this.request.user?.accessesGet }) || isRestrictedView(this.request.user) ) { @@ -180,7 +180,7 @@ export class PortfolioController { hasDetails === false || hasReadRestrictedAccessPermission({ impersonationId, - user: this.request.user + accesses: this.request.user?.accessesGet }) || isRestrictedView(this.request.user) ) { @@ -374,7 +374,7 @@ export class PortfolioController { if ( hasReadRestrictedAccessPermission({ impersonationId, - user: this.request.user + accesses: this.request.user?.accessesGet }) || isRestrictedView(this.request.user) ) { @@ -503,7 +503,7 @@ export class PortfolioController { if ( hasReadRestrictedAccessPermission({ impersonationId, - user: this.request.user + accesses: this.request.user?.accessesGet }) || isRestrictedView(this.request.user) ) { @@ -578,7 +578,7 @@ export class PortfolioController { if ( hasReadRestrictedAccessPermission({ impersonationId, - user: this.request.user + accesses: this.request.user?.accessesGet }) || isRestrictedView(this.request.user) || this.request.user.settings.settings.viewMode === 'ZEN' diff --git a/apps/api/src/interceptors/redact-values-in-response/redact-values-in-response.interceptor.ts b/apps/api/src/interceptors/redact-values-in-response/redact-values-in-response.interceptor.ts index 60b994cac3..6a95962989 100644 --- a/apps/api/src/interceptors/redact-values-in-response/redact-values-in-response.interceptor.ts +++ b/apps/api/src/interceptors/redact-values-in-response/redact-values-in-response.interceptor.ts @@ -38,7 +38,7 @@ export class RedactValuesInResponseInterceptor implements NestInterceptor< if ( hasReadRestrictedAccessPermission({ impersonationId, - user + accesses: user?.accessesGet }) || isRestrictedView(user) ) { diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index e81b8cf06e..a0350ee6b1 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -14,7 +14,11 @@ import { PortfolioPosition, User } from '@ghostfolio/common/interfaces'; -import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { + hasPermission, + hasReadRestrictedAccessPermission, + permissions +} from '@ghostfolio/common/permissions'; import { GfAccountBalancesComponent } from '@ghostfolio/ui/account-balances'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; @@ -225,7 +229,10 @@ export class GfAccountDetailDialogComponent implements OnInit { protected showValuesInPercentage() { return ( - this.data.hasImpersonationId || this.user?.settings?.isRestrictedView + hasReadRestrictedAccessPermission({ + accesses: this.user?.access, + impersonationId: this.data.impersonationId + }) || this.user?.settings?.isRestrictedView ); } diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html index 485af75009..cb3246c003 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html @@ -158,8 +158,8 @@ [pageSize]="pageSize" [showAccountColumn]="false" [showActions]=" - !data.hasImpersonationId && data.hasPermissionToCreateActivity && + !data.impersonationId && user?.settings?.isExperimentalFeatures && !user?.settings?.isRestrictedView " @@ -183,8 +183,8 @@ [currentBalance]="balance" [locale]="user?.settings?.locale" [showActions]=" - !data.hasImpersonationId && hasPermissionToDeleteAccountBalance && + !data.impersonationId && !user.settings.isRestrictedView " (accountBalanceCreated)="onAddAccountBalance($event)" diff --git a/apps/client/src/app/components/account-detail-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/account-detail-dialog/interfaces/interfaces.ts index 2f80dac363..0e7d04f2c3 100644 --- a/apps/client/src/app/components/account-detail-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/account-detail-dialog/interfaces/interfaces.ts @@ -1,8 +1,8 @@ export interface AccountDetailDialogParams { accountId: string; deviceType: string; - hasImpersonationId: boolean; hasPermissionToCreateActivity: boolean; + impersonationId: string | null; } export interface AccountDetailDialogResult { diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index e45c1d003e..fae8a664c6 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -54,6 +54,7 @@ export class GfAccountsPageComponent implements OnInit { protected hasImpersonationId: boolean; protected hasPermissionToCreateAccount: boolean; protected hasPermissionToUpdateAccount: boolean; + protected impersonationId: string | null; protected totalBalanceInBaseCurrency = 0; protected totalValueInBaseCurrency = 0; protected user: User; @@ -110,6 +111,7 @@ export class GfAccountsPageComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + this.impersonationId = impersonationId; }); this.userService.stateChanged @@ -252,11 +254,11 @@ export class GfAccountsPageComponent implements OnInit { data: { accountId: aAccountId, deviceType: this.deviceType(), - hasImpersonationId: this.hasImpersonationId, hasPermissionToCreateActivity: !this.hasImpersonationId && hasPermission(this.user?.permissions, permissions.createActivity) && - !this.user?.settings?.isRestrictedView + !this.user?.settings?.isRestrictedView, + impersonationId: this.impersonationId }, height: this.deviceType() === 'mobile' ? '98vh' : '80vh', width: this.deviceType() === 'mobile' ? '100vw' : '50rem' diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 0931578cf6..bde6df6cc3 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -17,7 +17,11 @@ import { PortfolioPosition, User } from '@ghostfolio/common/interfaces'; -import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { + hasPermission, + hasReadRestrictedAccessPermission, + permissions +} from '@ghostfolio/common/permissions'; import { MarketAdvanced } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; @@ -97,6 +101,7 @@ export class GfAllocationsPageComponent implements OnInit { | 'name' > & { etfProvider: string; value: number }; }; + protected impersonationId: string | null; protected isLoading = false; protected markets: PortfolioDetails['markets']; protected marketsAdvanced: { @@ -170,6 +175,7 @@ export class GfAllocationsPageComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + this.impersonationId = impersonationId; this.changeDetectorRef.markForCheck(); }); @@ -224,7 +230,12 @@ export class GfAllocationsPageComponent implements OnInit { } protected showValuesInPercentage() { - return this.hasImpersonationId || this.user?.settings?.isRestrictedView; + return ( + hasReadRestrictedAccessPermission({ + accesses: this.user?.access, + impersonationId: this.impersonationId + }) || this.user?.settings?.isRestrictedView + ); } private extractCurrency({ @@ -618,11 +629,11 @@ export class GfAllocationsPageComponent implements OnInit { data: { accountId: aAccountId, deviceType: this.deviceType(), - hasImpersonationId: this.hasImpersonationId, hasPermissionToCreateActivity: !this.hasImpersonationId && hasPermission(this.user?.permissions, permissions.createActivity) && - !this.user?.settings?.isRestrictedView + !this.user?.settings?.isRestrictedView, + impersonationId: this.impersonationId }, height: this.deviceType() === 'mobile' ? '98vh' : '80vh', width: this.deviceType() === 'mobile' ? '100vw' : '50rem' diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index c2d23dbc69..463493f40d 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -16,7 +16,11 @@ import { ToggleOption, User } from '@ghostfolio/common/interfaces'; -import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { + hasPermission, + hasReadRestrictedAccessPermission, + permissions +} from '@ghostfolio/common/permissions'; import type { AiPromptMode, GroupBy } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; @@ -81,6 +85,7 @@ export class GfAnalysisPageComponent implements OnInit { protected readonly dividendTimelineDataLabel = $localize`Dividend`; protected hasImpersonationId: boolean; protected hasPermissionToReadAiPrompt: boolean; + protected impersonationId: string | null; protected investments: InvestmentItem[]; protected readonly investmentTimelineDataLabel = $localize`Invested Capital`; protected investmentsByGroup: InvestmentItem[]; @@ -151,6 +156,7 @@ export class GfAnalysisPageComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + this.impersonationId = impersonationId; this.changeDetectorRef.markForCheck(); }); @@ -241,6 +247,15 @@ export class GfAnalysisPageComponent implements OnInit { }); } + protected showValuesInPercentage() { + return ( + hasReadRestrictedAccessPermission({ + accesses: this.user?.access, + impersonationId: this.impersonationId + }) || this.user?.settings?.isRestrictedView + ); + } + private fetchDividendsAndInvestments() { this.isLoadingDividendTimelineChart = true; this.isLoadingInvestmentTimelineChart = true; diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html index 0fac204f96..82751b8829 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -398,9 +398,7 @@ [benchmarkDataLabel]="portfolioEvolutionDataLabel" [currency]="user?.settings?.baseCurrency" [historicalDataItems]="performanceDataItems" - [isInPercentage]=" - hasImpersonationId || user.settings.isRestrictedView - " + [isInPercentage]="showValuesInPercentage()" [isLoading]="isLoadingInvestmentChart" [locale]="user?.settings?.locale" /> @@ -456,9 +454,7 @@ [benchmarkDataLabel]="investmentTimelineDataLabel" [currency]="user?.settings?.baseCurrency" [groupBy]="mode()" - [isInPercentage]=" - hasImpersonationId || user.settings.isRestrictedView - " + [isInPercentage]="showValuesInPercentage()" [isLoading]="isLoadingInvestmentTimelineChart" [locale]="user?.settings?.locale" [savingsRate]="savingsRate" @@ -493,9 +489,7 @@ [benchmarkDataLabel]="dividendTimelineDataLabel" [currency]="user?.settings?.baseCurrency" [groupBy]="mode()" - [isInPercentage]=" - hasImpersonationId || user.settings.isRestrictedView - " + [isInPercentage]="showValuesInPercentage()" [isLoading]="isLoadingDividendTimelineChart" [locale]="user?.settings?.locale" /> diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index 811ded68c5..5ebc206616 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -1,6 +1,6 @@ import { UserWithSettings } from '@ghostfolio/common/types'; -import { Role } from '@prisma/client'; +import { Access, Role } from '@prisma/client'; export const permissions = { accessAdminControl: 'accessAdminControl', @@ -198,17 +198,17 @@ export function hasPermission( } export function hasReadRestrictedAccessPermission({ - impersonationId, - user + accesses = [], + impersonationId }: { - impersonationId: string; - user: UserWithSettings; + accesses: Pick[]; + impersonationId: string | null; }) { if (!impersonationId) { return false; } - const access = user?.accessesGet?.find(({ id }) => { + const access = accesses?.find(({ id }) => { return id === impersonationId; });