Browse Source

Generate security token for user

pull/4458/head
csehatt741 4 weeks ago
parent
commit
b82e395a98
  1. 15
      apps/api/src/app/user/user.controller.ts
  2. 34
      apps/api/src/app/user/user.service.ts
  3. 32
      apps/client/src/app/components/admin-users/admin-users.component.ts
  4. 9
      apps/client/src/app/components/admin-users/admin-users.html
  5. 8
      apps/client/src/app/services/data.service.ts
  6. 4
      apps/client/src/app/services/user/user.service.ts
  7. 3
      libs/common/src/lib/interfaces/access-token.interface.ts
  8. 2
      libs/common/src/lib/interfaces/index.ts

15
apps/api/src/app/user/user.controller.ts

@ -2,7 +2,7 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorat
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { User, UserSettings } from '@ghostfolio/common/interfaces';
import { AccessToken, User, UserSettings } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
@ -123,6 +123,19 @@ export class UserController {
};
}
@Post(':id/security-token')
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async generateSecurityToken(
@Param('id') id: string
): Promise<AccessToken> {
const accessToken = await this.userService.generateAccessToken({
userId: id
});
return { accessToken };
}
@Put('setting')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async updateUserSetting(@Body() data: UpdateUserSettingDto) {

34
apps/api/src/app/user/user.service.ts

@ -433,7 +433,7 @@ export class UserService {
data.provider = 'ANONYMOUS';
}
let user = await this.prismaService.user.create({
const user = await this.prismaService.user.create({
data: {
...data,
Account: {
@ -464,17 +464,7 @@ export class UserService {
}
if (data.provider === 'ANONYMOUS') {
const accessToken = this.createAccessToken(user.id, getRandomString(10));
const hashedAccessToken = this.createAccessToken(
accessToken,
this.configurationService.get('ACCESS_TOKEN_SALT')
);
user = await this.prismaService.user.update({
data: { accessToken: hashedAccessToken },
where: { id: user.id }
});
const accessToken = await this.generateAccessToken({ userId: user.id });
return { ...user, accessToken };
}
@ -581,4 +571,24 @@ export class UserService {
return settings;
}
public async generateAccessToken({
userId
}: {
userId: string;
}): Promise<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;
}
}

32
apps/client/src/app/components/admin-users/admin-users.component.ts

@ -1,9 +1,3 @@
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { getDateFormatString, getEmojiFlag } from '@ghostfolio/common/helper';
import { AdminUsers, InfoItem, User } from '@ghostfolio/common/interfaces';
@ -26,6 +20,13 @@ import {
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ConfirmationDialogType } from '../../core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '../../core/notification/notification.service';
import { AdminService } from '../../services/admin.service';
import { DataService } from '../../services/data.service';
import { ImpersonationStorageService } from '../../services/impersonation-storage.service';
import { UserService } from '../../services/user/user.service';
@Component({
selector: 'gf-admin-users',
styleUrls: ['./admin-users.scss'],
@ -140,6 +141,25 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
});
}
public onGenerateSecurityToken(aId: string) {
this.notificationService.confirm({
confirmFn: () => {
this.dataService
.generateSecurityToken(aId)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ accessToken }) => {
this.notificationService.prompt({
confirmFn: () => {},
defaultValue: accessToken,
title: $localize`Security token`
});
});
},
confirmType: ConfirmationDialogType.Warn,
title: $localize`Do you really want to generate a new security token for this user?`
});
}
public onImpersonateUser(aId: string) {
if (aId) {
this.impersonationStorageService.setId(aId);

9
apps/client/src/app/components/admin-users/admin-users.html

@ -241,6 +241,15 @@
</button>
<hr class="m-0" />
}
<button
mat-menu-item
(click)="onGenerateSecurityToken(element.id)"
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="key-outline" />
<span i18n>Generate Security Token</span>
</span>
</button>
<button
mat-menu-item
[disabled]="element.id === user?.id"

8
apps/client/src/app/services/data.service.ts

@ -22,6 +22,7 @@ import { PropertyDto } from '@ghostfolio/api/services/property/property.dto';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
Access,
AccessToken,
AccountBalancesResponse,
Accounts,
AiPromptResponse,
@ -324,6 +325,13 @@ export class DataService {
return this.http.delete<any>(`/api/v1/user/${aId}`);
}
public generateSecurityToken(aId: string) {
return this.http.post<AccessToken>(
`/api/v1/user/${aId}/security-token`,
{}
);
}
public fetchAccesses() {
return this.http.get<Access[]>('/api/v1/access');
}

4
apps/client/src/app/services/user/user.service.ts

@ -1,5 +1,3 @@
import { SubscriptionInterstitialDialogParams } from '@ghostfolio/client/components/subscription-interstitial-dialog/interfaces/interfaces';
import { SubscriptionInterstitialDialog } from '@ghostfolio/client/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component';
import { Filter, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -13,6 +11,8 @@ import { Observable, Subject, of } from 'rxjs';
import { throwError } from 'rxjs';
import { catchError, map, takeUntil } from 'rxjs/operators';
import { SubscriptionInterstitialDialogParams } from '../../components/subscription-interstitial-dialog/interfaces/interfaces';
import { SubscriptionInterstitialDialog } from '../../components/subscription-interstitial-dialog/subscription-interstitial-dialog.component';
import { UserStoreActions } from './user-store.actions';
import { UserStoreState } from './user-store.state';

3
libs/common/src/lib/interfaces/access-token.interface.ts

@ -0,0 +1,3 @@
export interface AccessToken {
accessToken: string;
}

2
libs/common/src/lib/interfaces/index.ts

@ -1,3 +1,4 @@
import type { AccessToken } from './access-token.interface';
import type { Access } from './access.interface';
import type { AccountBalance } from './account-balance.interface';
import type { Accounts } from './accounts.interface';
@ -69,6 +70,7 @@ import type { XRayRulesSettings } from './x-ray-rules-settings.interface';
export {
Access,
AccessToken,
AccountBalance,
AccountBalancesResponse,
Accounts,

Loading…
Cancel
Save