diff --git a/CHANGELOG.md b/CHANGELOG.md index 41f4977330..2f3727101b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Restricted the _Restricted view and manage_ permission of the access to share the portfolio to the Model Context Protocol (MCP) (experimental) - Upgraded `prisma` from version `7.9.1` to `7.10.0` ### Fixed - Fixed the loading state of the accounts table on the accounts page +- Fixed the loading state of the holdings table on the portfolio holdings page ## 3.65.0 - 2026-08-31 diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index df6d57c0e9..b55b1c54ef 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -37,7 +37,7 @@ }
diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index b0be5c0c52..8c2bb0353a 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -6,6 +6,7 @@ import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Scope, + canGrantRestrictedWriteAccess, getAccessLevel, getScopesOfAccess, getScopesOfAccessLevel, @@ -125,6 +126,10 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit { return this.hasExperimentalFeatures && this.hasPermissionToEnableMcp; } + public get canGrantRestrictedWriteAccess() { + return canGrantRestrictedWriteAccess({ type: this.accessType }); + } + public get canGrantWriteAccess() { return this.hasExperimentalFeatures; } diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html index 89893e5708..21c0cd3a8b 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -82,7 +82,7 @@ - @if (!isPublicAccess) { + @if (canGrantRestrictedWriteAccess) { = { MCP: [...SCOPES_OF_READ_RESTRICTED_ACCESS, scopes.activityCreate], @@ -72,6 +74,14 @@ const SCOPES_OF_TYPE: Record = { PUBLIC: SCOPES_OF_PUBLIC_ACCESS }; +/** + * Access types which combine a write scope with the restricted read access, + * because their tools change data without exposing the monetary values + */ +export function canGrantRestrictedWriteAccess({ type }: { type: AccessType }) { + return type === 'MCP'; +} + /** * Access level which the scopes of an access grant */ @@ -96,9 +106,16 @@ export function getScopesOfAccess({ }): string[] { const scopesToEvaluate = scopesOfAccess ?? []; + const permitsWriteAccess = + canGrantRestrictedWriteAccess({ type }) || + hasScope(scopesToEvaluate, scopes.portfolioReadValues); + // An unknown scope is dropped return SCOPES_OF_TYPE[type].filter((scope) => { - return scopesToEvaluate.includes(scope); + return ( + scopesToEvaluate.includes(scope) && + (permitsWriteAccess || !SCOPES_OF_WRITE_ACCESS.includes(scope)) + ); }); } diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 7fe21720ef..1bbfa515b1 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -50,7 +50,7 @@ export class GfHoldingsTableComponent { public readonly hasPermissionToOpenDetails = input(true); public readonly hasPermissionToShowQuantities = input(true); public readonly hasPermissionToShowValues = input(true); - public readonly holdings = input.required(); + public readonly holdings = input.required(); public readonly locale = input(getLocale()); public readonly pageSize = model(Number.MAX_SAFE_INTEGER); @@ -89,7 +89,7 @@ export class GfHoldingsTableComponent { // Reactive data update effect(() => { - this.dataSource.data = this.holdings(); + this.dataSource.data = this.holdings() ?? []; }); // Reactive view connection