From a44897a539d0702291f885641ce0226062ca1044 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 22 Mar 2025 10:16:31 +0100 Subject: [PATCH] Minor improvements --- apps/api/src/app/auth/auth.service.ts | 8 +-- apps/api/src/app/user/user.controller.ts | 22 ++++++--- apps/api/src/app/user/user.service.ts | 49 ++++++++++--------- .../admin-users/admin-users.component.ts | 18 +++++-- .../components/admin-users/admin-users.html | 2 +- apps/client/src/app/services/data.service.ts | 4 +- 6 files changed, 63 insertions(+), 40 deletions(-) diff --git a/apps/api/src/app/auth/auth.service.ts b/apps/api/src/app/auth/auth.service.ts index edfb22b6f..ceff492a0 100644 --- a/apps/api/src/app/auth/auth.service.ts +++ b/apps/api/src/app/auth/auth.service.ts @@ -20,10 +20,10 @@ export class AuthService { public async validateAnonymousLogin(accessToken: string): Promise { return new Promise(async (resolve, reject) => { try { - const hashedAccessToken = this.userService.createAccessToken( - accessToken, - this.configurationService.get('ACCESS_TOKEN_SALT') - ); + const hashedAccessToken = this.userService.createAccessToken({ + password: accessToken, + salt: this.configurationService.get('ACCESS_TOKEN_SALT') + }); const [user] = await this.userService.users({ where: { accessToken: hashedAccessToken } diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index eacd57c83..868af505b 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -1,6 +1,7 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { AccessTokenResponse, @@ -40,6 +41,7 @@ export class UserController { public constructor( private readonly configurationService: ConfigurationService, private readonly jwtService: JwtService, + private readonly prismaService: PrismaService, private readonly propertyService: PropertyService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly userService: UserService @@ -51,10 +53,10 @@ export class UserController { public async deleteOwnUser( @Body() data: DeleteOwnUserDto ): Promise { - const hashedAccessToken = this.userService.createAccessToken( - data.accessToken, - this.configurationService.get('ACCESS_TOKEN_SALT') - ); + const hashedAccessToken = this.userService.createAccessToken({ + password: data.accessToken, + salt: this.configurationService.get('ACCESS_TOKEN_SALT') + }); const [user] = await this.userService.users({ where: { accessToken: hashedAccessToken, id: this.request.user.id } @@ -89,14 +91,20 @@ export class UserController { }); } - @Post(':id/access-token') @HasPermission(permissions.accessAdminControl) + @Post(':id/access-token') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async generateAccessToken( @Param('id') id: string ): Promise { - const { accessToken } = await this.userService.generateAccessToken({ - userId: id + const { accessToken, hashedAccessToken } = + this.userService.generateAccessToken({ + userId: id + }); + + await this.prismaService.user.update({ + data: { accessToken: hashedAccessToken }, + where: { id } }); return { accessToken }; diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index fc1ebbd9f..e9b8078b1 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -67,13 +67,33 @@ export class UserService { return this.prismaService.user.count(args); } - public createAccessToken(password: string, salt: string): string { + public createAccessToken({ + password, + salt + }: { + password: string; + salt: string; + }): string { const hash = createHmac('sha512', salt); hash.update(password); return hash.digest('hex'); } + public generateAccessToken({ userId }: { userId: string }) { + const accessToken = this.createAccessToken({ + password: userId, + salt: getRandomString(10) + }); + + const hashedAccessToken = this.createAccessToken({ + password: accessToken, + salt: this.configurationService.get('ACCESS_TOKEN_SALT') + }); + + return { accessToken, hashedAccessToken }; + } + public async getUser( { Account, id, permissions, Settings, subscription }: UserWithSettings, aLocale = locale @@ -464,10 +484,15 @@ export class UserService { } if (data.provider === 'ANONYMOUS') { - const { accessToken } = await this.generateAccessToken({ + const { accessToken, hashedAccessToken } = this.generateAccessToken({ userId: user.id }); + await this.prismaService.user.update({ + data: { accessToken: hashedAccessToken }, + where: { id: user.id } + }); + return { ...user, accessToken }; } @@ -573,24 +598,4 @@ export class UserService { return settings; } - - public async generateAccessToken({ - userId - }: { - userId: string; - }): Promise<{ accessToken: string }> { - const accessToken = this.createAccessToken(userId, getRandomString(10)); - - const hashedAccessToken = this.createAccessToken( - accessToken, - this.configurationService.get('ACCESS_TOKEN_SALT') - ); - - await this.prismaService.user.update({ - data: { accessToken: hashedAccessToken }, - where: { id: userId } - }); - - return { accessToken }; - } } diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 06bd8ef1a..e1cd3102c 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,3 +1,4 @@ +import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { getDateFormatString, getEmojiFlag } from '@ghostfolio/common/helper'; import { AdminUsers, InfoItem, User } from '@ghostfolio/common/interfaces'; @@ -29,9 +30,9 @@ import { UserService } from '../../services/user/user.service'; @Component({ selector: 'gf-admin-users', + standalone: false, styleUrls: ['./admin-users.scss'], - templateUrl: './admin-users.html', - standalone: false + templateUrl: './admin-users.html' }) export class AdminUsersComponent implements OnDestroy, OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; @@ -56,6 +57,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit { private dataService: DataService, private impersonationStorageService: ImpersonationStorageService, private notificationService: NotificationService, + private tokenStorageService: TokenStorageService, private userService: UserService ) { this.info = this.dataService.fetchInfo(); @@ -141,14 +143,22 @@ export class AdminUsersComponent implements OnDestroy, OnInit { }); } - public onGenerateAccessToken(aId: string) { + public onGenerateAccessToken(aUserId: string) { this.notificationService.confirm({ confirmFn: () => { this.dataService - .generateAccessToken(aId) + .generateAccessToken(aUserId) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ accessToken }) => { this.notificationService.alert({ + discardFn: () => { + if (aUserId === this.user.id) { + this.tokenStorageService.signOut(); + this.userService.remove(); + + document.location.href = `/${document.documentElement.lang}`; + } + }, message: accessToken, title: $localize`Security token` }); diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index c70bbe0a1..e8725e70c 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -239,7 +239,6 @@ Impersonate User -
} +