diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index 2b679f34c..4be0c33a3 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -69,13 +69,14 @@ export class UserController { public async deleteOwnUser( @Body() data: DeleteOwnUserDto ): Promise { - const user = await this.validateAccessToken( - data.accessToken, - this.request.user.id - ); + if (this.request.user.provider === 'ANONYMOUS') { + await this.validateAccessToken(data.accessToken, this.request.user.id); + } else { + await this.validateUserHasNoData(this.request.user.id); + } return this.userService.deleteUser({ - id: user.id + id: this.request.user.id }); } @@ -253,4 +254,18 @@ export class UserController { return user; } + + private async validateUserHasNoData(userId: string) { + const [accountsCount, activitiesCount] = await Promise.all([ + this.prismaService.account.count({ where: { userId } }), + this.prismaService.order.count({ where: { userId } }) + ]); + + if (accountsCount > 0 || activitiesCount > 0) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + } } diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 8fb11ad34..228e57a72 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -118,7 +118,7 @@ export class UserService { locale?: string; user: UserWithSettings; }): Promise { - const { id, permissions, settings, subscription } = user; + const { id, permissions, provider, settings, subscription } = user; const [ access, @@ -202,6 +202,7 @@ export class UserService { activitiesCount, id, permissions, + provider, referralPartners, subscription, systemMessage, @@ -503,8 +504,11 @@ export class UserService { let currentPermissions = getPermissions(user.role); - if (user.provider === 'ANONYMOUS') { + if (!hasRole(user, Role.DEMO)) { currentPermissions.push(permissions.deleteOwnUser); + } + + if (user.provider === 'ANONYMOUS') { currentPermissions.push(permissions.updateOwnAccessToken); } diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts index b48adf59d..b984f3ec0 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -86,6 +86,7 @@ export class GfUserAccountSettingsComponent implements OnInit { protected hasPermissionToUpdateViewMode: boolean; protected hasPermissionToUpdateUserSettings: boolean; protected isAccessTokenHidden = true; + protected isAnonymousAuthenticationProvider: boolean; protected readonly isFingerprintSupported = this.doesBrowserSupportAuthn(); protected isLoading = true; protected isWebAuthnEnabled: boolean; @@ -108,6 +109,7 @@ export class GfUserAccountSettingsComponent implements OnInit { 'uk', 'zh' ]; + protected mustRemoveDataBeforeClosingAccount: boolean; protected user: User; private readonly changeDetectorRef = inject(ChangeDetectorRef); @@ -163,6 +165,14 @@ export class GfUserAccountSettingsComponent implements OnInit { permissions.requestOwnUserDeletion ); + this.isAnonymousAuthenticationProvider = + this.user.provider === 'ANONYMOUS'; + + this.mustRemoveDataBeforeClosingAccount = + (!this.isAnonymousAuthenticationProvider || + this.hasPermissionToRequestOwnUserDeletion) && + (this.user.accounts?.length > 0 || this.user.activitiesCount > 0); + this.hasPermissionToUpdateUserSettings = hasPermission( this.user.permissions, permissions.updateUserSettings @@ -225,12 +235,16 @@ export class GfUserAccountSettingsComponent implements OnInit { confirmFn: () => { this.dataService .deleteOwnUser({ - accessToken: this.deleteOwnUserForm.controls.accessToken.value + accessToken: this.isAnonymousAuthenticationProvider + ? this.deleteOwnUserForm.controls.accessToken.value + : undefined }) .pipe( catchError(() => { this.notificationService.alert({ - title: $localize`Oops! Incorrect Security Token.` + title: this.isAnonymousAuthenticationProvider + ? $localize`Oops! Incorrect Security Token.` + : $localize`Oops! Your account could not be closed.` }); return EMPTY; diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.html b/apps/client/src/app/components/user-account-settings/user-account-settings.html index 57d3b54d7..15e11a64e 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.html +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -293,7 +293,26 @@
Danger Zone
- @if (hasPermissionToDeleteOwnUser) { + @if (mustRemoveDataBeforeClosingAccount) { + +
+ For security reasons, please delete all activities and accounts + first before your Ghostfolio account can be closed. +
+ } @else if ( + hasPermissionToDeleteOwnUser && !isAnonymousAuthenticationProvider + ) { + + } @else if (hasPermissionToDeleteOwnUser) {
- } @else if ( - hasPermissionToRequestOwnUserDeletion && - (user?.accounts?.length > 0 || user?.activitiesCount > 0) - ) { - -
- For security reasons, please delete all activities and accounts - first before your Ghostfolio account can be closed. -
} @else if (hasPermissionToRequestOwnUserDeletion) {