Browse Source

refactoring of remaining controllers to use the HasPermission Decorator and HasPermissionGuard instead of explict checking

pull/2771/head
Fares Eidi 2 years ago
committed by Thomas Kaul
parent
commit
0f4d90dc40
  1. 15
      apps/api/src/app/access/access.controller.ts
  2. 9
      apps/api/src/app/account-balance/account-balance.controller.ts
  3. 40
      apps/api/src/app/account/account.controller.ts
  4. 205
      apps/api/src/app/admin/admin.controller.ts
  5. 44
      apps/api/src/app/admin/queue/queue.controller.ts
  6. 14
      apps/api/src/app/app.module.ts
  7. 18
      apps/api/src/app/auth-device/auth-device.controller.ts
  8. 31
      apps/api/src/app/benchmark/benchmark.controller.ts
  9. 26
      apps/api/src/app/cache/cache.controller.ts
  10. 6
      apps/api/src/app/import/import.controller.ts
  11. 23
      apps/api/src/app/order/order.controller.ts
  12. 31
      apps/api/src/app/platform/platform.controller.ts
  13. 25
      apps/api/src/app/tag/tag.controller.ts
  14. 19
      apps/api/src/app/user/user.controller.ts
  15. 10
      apps/api/src/guards/has-permission.guard.spec.ts
  16. 7
      apps/api/src/guards/has-permission.guard.ts

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

@ -1,5 +1,5 @@
import { Access } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
@ -19,6 +19,7 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AccessService } from './access.service';
import { CreateAccessDto } from './create-access.dto';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('access')
export class AccessController {
@ -59,18 +60,10 @@ export class AccessController {
@Post()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.createAccess)
public async createAccess(
@Body() data: CreateAccessDto
): Promise<AccessModel> {
if (
!hasPermission(this.request.user.permissions, permissions.createAccess)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.accessService.createAccess({
alias: data.alias || undefined,
GranteeUser: data.granteeUserId
@ -82,11 +75,11 @@ export class AccessController {
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteAccess)
public async deleteAccess(@Param('id') id: string): Promise<AccessModel> {
const access = await this.accessService.access({ id });
if (
!hasPermission(this.request.user.permissions, permissions.deleteAccess) ||
!access ||
access.userId !== this.request.user.id
) {

9
apps/api/src/app/account-balance/account-balance.controller.ts

@ -10,8 +10,10 @@ import {
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { AccountBalance } from '@prisma/client';
import { permissions } from '@ghostfolio/common/permissions';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AccountBalance } from '@prisma/client';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { AccountBalanceService } from './account-balance.service';
@ -24,6 +26,7 @@ export class AccountBalanceController {
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteAccountBalance)
public async deleteAccountBalance(
@Param('id') id: string
): Promise<AccountBalance> {
@ -32,10 +35,6 @@ export class AccountBalanceController {
});
if (
!hasPermission(
this.request.user.permissions,
permissions.deleteAccountBalance
) ||
!accountBalance ||
accountBalance.userId !== this.request.user.id
) {

40
apps/api/src/app/account/account.controller.ts

@ -7,7 +7,7 @@ import {
AccountBalancesResponse,
Accounts
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions';
import type {
AccountWithValue,
RequestWithUser
@ -35,6 +35,7 @@ import { AccountService } from './account.service';
import { CreateAccountDto } from './create-account.dto';
import { TransferBalanceDto } from './transfer-balance.dto';
import { UpdateAccountDto } from './update-account.dto';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('account')
export class AccountController {
@ -48,16 +49,8 @@ export class AccountController {
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteAccount)
public async deleteAccount(@Param('id') id: string): Promise<AccountModel> {
if (
!hasPermission(this.request.user.permissions, permissions.deleteAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const account = await this.accountService.accountWithOrders(
{
id_userId: {
@ -135,17 +128,10 @@ export class AccountController {
@Post()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.createAccount)
public async createAccount(
@Body() data: CreateAccountDto
): Promise<AccountModel> {
if (
!hasPermission(this.request.user.permissions, permissions.createAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
if (data.platformId) {
const platformId = data.platformId;
@ -174,17 +160,10 @@ export class AccountController {
@Post('transfer-balance')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.updateAccount)
public async transferAccountBalance(
@Body() { accountIdFrom, accountIdTo, balance }: TransferBalanceDto
) {
if (
!hasPermission(this.request.user.permissions, permissions.updateAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const accountsOfUser = await this.accountService.getAccounts(
this.request.user.id
@ -236,15 +215,8 @@ export class AccountController {
@Put(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.updateAccount)
public async update(@Param('id') id: string, @Body() data: UpdateAccountDto) {
if (
!hasPermission(this.request.user.permissions, permissions.updateAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const originalAccount = await this.accountService.account({
id_userId: {

205
apps/api/src/app/admin/admin.controller.ts

@ -47,6 +47,7 @@ import { AdminService } from './admin.service';
import { UpdateAssetProfileDto } from './update-asset-profile.dto';
import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto';
import { UpdateMarketDataDto } from './update-market-data.dto';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('admin')
export class AdminController {
@ -60,56 +61,23 @@ export class AdminController {
@Get()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async getAdminData(): Promise<AdminData> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.get();
}
@Post('gather')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async gather7Days(): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
this.dataGatheringService.gather7Days();
this.dataGatheringService.gather7Days();
}
@Post('gather/max')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async gatherMax(): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
await this.dataGatheringService.addJobsToQueue(
uniqueAssets.map(({ dataSource, symbol }) => {
@ -132,19 +100,8 @@ export class AdminController {
@Post('gather/profile-data')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async gatherProfileData(): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
await this.dataGatheringService.addJobsToQueue(
@ -166,22 +123,11 @@ export class AdminController {
@Post('gather/profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async gatherProfileDataForSymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
await this.dataGatheringService.addJobToQueue({
data: {
dataSource,
@ -197,22 +143,11 @@ export class AdminController {
@Post('gather/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async gatherSymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
this.dataGatheringService.gatherSymbol({ dataSource, symbol });
return;
@ -220,24 +155,13 @@ export class AdminController {
@Post('gather/:dataSource/:symbol/:dateString')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async gatherSymbolForDate(
@Param('dataSource') dataSource: DataSource,
@Param('dateString') dateString: string,
@Param('symbol') symbol: string
): Promise<MarketData> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const date = parseISO(dateString);
const date = parseISO(dateString);
if (!isDate(date)) {
throw new HttpException(
@ -255,6 +179,7 @@ export class AdminController {
@Get('market-data')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async getMarketData(
@Query('assetSubClasses') filterByAssetSubClasses?: string,
@Query('presetId') presetId?: MarketDataPreset,
@ -264,19 +189,7 @@ export class AdminController {
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
@Query('take') take?: number
): Promise<AdminMarketData> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const filters = this.apiService.buildFiltersFromQueryParams({
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAssetSubClasses,
filterBySearchQuery
});
@ -293,45 +206,23 @@ export class AdminController {
@Get('market-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async getMarketDataBySymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<AdminMarketDataDetails> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.getMarketDataBySymbol({ dataSource, symbol });
}
@Post('market-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async updateMarketData(
@Body() data: UpdateBulkMarketDataDto,
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = data.marketData.map(
const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = data.marketData.map(
({ date, marketPrice }) => ({
dataSource,
marketPrice,
@ -351,24 +242,13 @@ export class AdminController {
*/
@Put('market-data/:dataSource/:symbol/:dateString')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async update(
@Param('dataSource') dataSource: DataSource,
@Param('dateString') dateString: string,
@Param('symbol') symbol: string,
@Body() data: UpdateMarketDataDto
) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const date = parseISO(dateString);
return this.marketDataService.updateMarketData({
@ -385,22 +265,12 @@ export class AdminController {
@Post('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async addProfileData(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<SymbolProfile | never> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.addAssetProfile({
dataSource,
symbol,
@ -410,44 +280,22 @@ export class AdminController {
@Delete('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async deleteProfileData(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.deleteProfileData({ dataSource, symbol });
}
@Patch('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async patchAssetProfileData(
@Body() assetProfileData: UpdateAssetProfileDto,
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<EnhancedSymbolProfile> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.patchAssetProfileData({
...assetProfileData,
dataSource,
@ -457,22 +305,11 @@ export class AdminController {
@Put('settings/:key')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async updateProperty(
@Param('key') key: string,
@Body() data: PropertyDto
) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return await this.adminService.putSetting(key, data.value);
}
}

44
apps/api/src/app/admin/queue/queue.controller.ts

@ -1,11 +1,10 @@
import { AdminJobs } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Delete,
Get,
HttpException,
Inject,
Param,
Query,
@ -14,9 +13,9 @@ import {
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { JobStatus } from 'bull';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { QueueService } from './queue.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('admin/queue')
export class QueueController {
@ -27,61 +26,28 @@ export class QueueController {
@Delete('job')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async deleteJobs(
@Query('status') filterByStatus?: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const status = <JobStatus[]>filterByStatus?.split(',') ?? undefined;
return this.queueService.deleteJobs({ status });
}
@Get('job')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async getJobs(
@Query('status') filterByStatus?: string
): Promise<AdminJobs> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const status = <JobStatus[]>filterByStatus?.split(',') ?? undefined;
return this.queueService.getJobs({ status });
}
@Delete('job/:id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async deleteJob(@Param('id') id: string): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.queueService.deleteJob(id);
}
}

14
apps/api/src/app/app.module.ts

@ -41,6 +41,8 @@ import { SubscriptionModule } from './subscription/subscription.module';
import { SymbolModule } from './symbol/symbol.module';
import { TagModule } from './tag/tag.module';
import { UserModule } from './user/user.module';
import { APP_GUARD } from '@nestjs/core';
import { HasPermissionGuard } from '../guards/has-permission.guard';
@Module({
imports: [
@ -91,7 +93,7 @@ import { UserModule } from './user/user.module';
if (SUPPORTED_LANGUAGE_CODES.includes(code)) {
languageCode = code;
}
} catch {}
} catch { }
res.set('Location', `/${languageCode}`);
res.statusCode = StatusCodes.MOVED_PERMANENTLY;
@ -107,6 +109,12 @@ import { UserModule } from './user/user.module';
UserModule
],
controllers: [AppController],
providers: [CronService]
providers: [
CronService,
{
provide: APP_GUARD,
useClass: HasPermissionGuard
}
]
})
export class AppModule {}
export class AppModule { }

18
apps/api/src/app/auth-device/auth-device.controller.ts

@ -1,17 +1,16 @@
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Delete,
HttpException,
Inject,
Param,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Controller('auth-device')
export class AuthDeviceController {
@ -22,19 +21,8 @@ export class AuthDeviceController {
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteAuthDevice)
public async deleteAuthDevice(@Param('id') id: string): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.deleteAuthDevice
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
await this.authDeviceService.deleteAuthDevice({ id });
}
}

31
apps/api/src/app/benchmark/benchmark.controller.ts

@ -5,7 +5,7 @@ import type {
BenchmarkResponse,
UniqueAsset
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
@ -25,6 +25,7 @@ import { DataSource } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { BenchmarkService } from './benchmark.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('benchmark')
export class BenchmarkController {
@ -35,19 +36,8 @@ export class BenchmarkController {
@Post()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async addBenchmark(@Body() { dataSource, symbol }: UniqueAsset) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
try {
const benchmark = await this.benchmarkService.addBenchmark({
dataSource,
@ -62,7 +52,7 @@ export class BenchmarkController {
}
return benchmark;
} catch {
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
@ -72,22 +62,11 @@ export class BenchmarkController {
@Delete(':dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
public async deleteBenchmark(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
try {
const benchmark = await this.benchmarkService.deleteBenchmark({
dataSource,

26
apps/api/src/app/cache/cache.controller.ts

@ -1,39 +1,29 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
HttpException,
Inject,
Post,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Controller('cache')
export class CacheController {
public constructor(
private readonly redisCacheService: RedisCacheService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
private readonly redisCacheService: RedisCacheService,
@Inject(REQUEST) private readonly request: RequestWithUser
) { }
@Post('flush')
@UseGuards(AuthGuard('jwt'))
public async flushCache(): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
@HasPermission(permissions.accessAdminControl) // permission needed
public async flushCache(): Promise<void> {
return this.redisCacheService.reset();
}
}

6
apps/api/src/app/import/import.controller.ts

@ -24,6 +24,7 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { ImportDataDto } from './import-data.dto';
import { ImportService } from './import.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('import')
export class ImportController {
@ -35,6 +36,7 @@ export class ImportController {
@Post()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.createAccount)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async import(
@ -42,10 +44,6 @@ export class ImportController {
@Query('dryRun') isDryRun?: boolean
): Promise<ImportResponse> {
if (
!hasPermission(
this.request.user.permissions,
permissions.createAccount
) ||
!hasPermission(this.request.user.permissions, permissions.createOrder)
) {
throw new HttpException(

23
apps/api/src/app/order/order.controller.ts

@ -32,6 +32,7 @@ import { CreateOrderDto } from './create-order.dto';
import { Activities } from './interfaces/activities.interface';
import { OrderService } from './order.service';
import { UpdateOrderDto } from './update-order.dto';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('order')
export class OrderController {
@ -45,16 +46,8 @@ export class OrderController {
@Delete()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteOrder)
public async deleteOrders(): Promise<number> {
if (
!hasPermission(this.request.user.permissions, permissions.deleteOrder)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.orderService.deleteOrders({
userId: this.request.user.id
});
@ -122,17 +115,9 @@ export class OrderController {
@Post()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.createOrder)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async createOrder(@Body() data: CreateOrderDto): Promise<OrderModel> {
if (
!hasPermission(this.request.user.permissions, permissions.createOrder)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const order = await this.orderService.createOrder({
...data,
date: parseISO(data.date),
@ -172,6 +157,7 @@ export class OrderController {
@Put(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.updateOrder)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async update(@Param('id') id: string, @Body() data: UpdateOrderDto) {
const originalOrder = await this.orderService.order({
@ -179,7 +165,6 @@ export class OrderController {
});
if (
!hasPermission(this.request.user.permissions, permissions.updateOrder) ||
!originalOrder ||
originalOrder.userId !== this.request.user.id
) {

31
apps/api/src/app/platform/platform.controller.ts

@ -20,6 +20,7 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreatePlatformDto } from './create-platform.dto';
import { PlatformService } from './platform.service';
import { UpdatePlatformDto } from './update-platform.dto';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('platform')
export class PlatformController {
@ -36,36 +37,20 @@ export class PlatformController {
@Post()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.createPlatform)
public async createPlatform(
@Body() data: CreatePlatformDto
): Promise<Platform> {
if (
!hasPermission(this.request.user.permissions, permissions.createPlatform)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.platformService.createPlatform(data);
}
@Put(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.updatePlatform)
public async updatePlatform(
@Param('id') id: string,
@Body() data: UpdatePlatformDto
) {
if (
!hasPermission(this.request.user.permissions, permissions.updatePlatform)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const originalPlatform = await this.platformService.getPlatform({
id
});
@ -89,16 +74,8 @@ export class PlatformController {
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deletePlatform)
public async deletePlatform(@Param('id') id: string) {
if (
!hasPermission(this.request.user.permissions, permissions.deletePlatform)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const originalPlatform = await this.platformService.getPlatform({
id
});

25
apps/api/src/app/tag/tag.controller.ts

@ -20,6 +20,7 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreateTagDto } from './create-tag.dto';
import { TagService } from './tag.service';
import { UpdateTagDto } from './update-tag.dto';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('tag')
export class TagController {
@ -36,27 +37,15 @@ export class TagController {
@Post()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.createTag)
public async createTag(@Body() data: CreateTagDto): Promise<Tag> {
if (!hasPermission(this.request.user.permissions, permissions.createTag)) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.tagService.createTag(data);
}
@Put(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.updateTag)
public async updateTag(@Param('id') id: string, @Body() data: UpdateTagDto) {
if (!hasPermission(this.request.user.permissions, permissions.updateTag)) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const originalTag = await this.tagService.getTag({
id
});
@ -80,14 +69,8 @@ export class TagController {
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteTag)
public async deleteTag(@Param('id') id: string) {
if (!hasPermission(this.request.user.permissions, permissions.deleteTag)) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const originalTag = await this.tagService.getTag({
id
});

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

@ -1,6 +1,6 @@
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { User, UserSettings } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
@ -25,6 +25,7 @@ import { size } from 'lodash';
import { UserItem } from './interfaces/user-item.interface';
import { UpdateUserSettingDto } from './update-user-setting.dto';
import { UserService } from './user.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
@Controller('user')
export class UserController {
@ -37,10 +38,9 @@ export class UserController {
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteUser)
public async deleteUser(@Param('id') id: string): Promise<UserModel> {
if (
!hasPermission(this.request.user.permissions, permissions.deleteUser) ||
id === this.request.user.id
if (id === this.request.user.id
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
@ -93,6 +93,7 @@ export class UserController {
@Put('setting')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.updateUserSettings)
public async updateUserSetting(@Body() data: UpdateUserSettingDto) {
if (
size(data) === 1 &&
@ -100,16 +101,6 @@ export class UserController {
this.request.user.role === 'DEMO'
) {
// Allow benchmark or date range change for demo user
} else if (
!hasPermission(
this.request.user.permissions,
permissions.updateUserSettings
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const userSettings: UserSettings = {

10
apps/api/src/guards/has-permission.guard.spec.ts

@ -1,8 +1,6 @@
import { HttpException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host';
import { Test, TestingModule } from '@nestjs/testing';
import { HasPermissionGuard } from './has-permission.guard';
describe('HasPermissionGuard', () => {
@ -10,12 +8,8 @@ describe('HasPermissionGuard', () => {
let reflector: Reflector;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [HasPermissionGuard, Reflector]
}).compile();
guard = module.get<HasPermissionGuard>(HasPermissionGuard);
reflector = module.get<Reflector>(Reflector);
reflector = new Reflector();
guard = new HasPermissionGuard(reflector);
});
function setupReflectorSpy(returnValue: string) {

7
apps/api/src/guards/has-permission.guard.ts

@ -11,9 +11,12 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Injectable()
export class HasPermissionGuard implements CanActivate {
public constructor(private reflector: Reflector) {}
public constructor(private reflector: Reflector) {
}
public canActivate(context: ExecutionContext): boolean {
const { user } = context.switchToHttp().getRequest();
const requiredPermission = this.reflector.get<string>(
HAS_PERMISSION_KEY,
context.getHandler()
@ -23,7 +26,7 @@ export class HasPermissionGuard implements CanActivate {
return true; // No specific permissions required
}
const { user } = context.switchToHttp().getRequest();
if (!user || !hasPermission(user.permissions, requiredPermission)) {
throw new HttpException(

Loading…
Cancel
Save