mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
111 changed files with 11429 additions and 3528 deletions
@ -0,0 +1,6 @@ |
|||
import { SetMetadata } from '@nestjs/common'; |
|||
export const HAS_PERMISSION_KEY = 'has_permission'; |
|||
|
|||
export function HasPermission(permission: string) { |
|||
return SetMetadata(HAS_PERMISSION_KEY, permission); |
|||
} |
@ -0,0 +1,55 @@ |
|||
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', () => { |
|||
let guard: 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); |
|||
}); |
|||
|
|||
function setupReflectorSpy(returnValue: string) { |
|||
jest.spyOn(reflector, 'get').mockReturnValue(returnValue); |
|||
} |
|||
|
|||
function createMockExecutionContext(permissions: string[]) { |
|||
return new ExecutionContextHost([ |
|||
{ |
|||
user: { |
|||
permissions // Set user permissions based on the argument
|
|||
} |
|||
} |
|||
]); |
|||
} |
|||
|
|||
it('should deny access if the user does not have any permission', () => { |
|||
setupReflectorSpy('required-permission'); |
|||
const noPermissions = createMockExecutionContext([]); |
|||
|
|||
expect(() => guard.canActivate(noPermissions)).toThrow(HttpException); |
|||
}); |
|||
|
|||
it('should deny access if the user has the wrong permission', () => { |
|||
setupReflectorSpy('required-permission'); |
|||
const wrongPermission = createMockExecutionContext(['wrong-permission']); |
|||
|
|||
expect(() => guard.canActivate(wrongPermission)).toThrow(HttpException); |
|||
}); |
|||
|
|||
it('should allow access if the user has the required permission', () => { |
|||
setupReflectorSpy('required-permission'); |
|||
const rightPermission = createMockExecutionContext(['required-permission']); |
|||
|
|||
expect(guard.canActivate(rightPermission)).toBe(true); |
|||
}); |
|||
}); |
@ -0,0 +1,37 @@ |
|||
import { HAS_PERMISSION_KEY } from '@ghostfolio/api/decorators/has-permission.decorator'; |
|||
import { hasPermission } from '@ghostfolio/common/permissions'; |
|||
import { |
|||
CanActivate, |
|||
ExecutionContext, |
|||
HttpException, |
|||
Injectable |
|||
} from '@nestjs/common'; |
|||
import { Reflector } from '@nestjs/core'; |
|||
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
|||
|
|||
@Injectable() |
|||
export class HasPermissionGuard implements CanActivate { |
|||
public constructor(private reflector: Reflector) {} |
|||
|
|||
public canActivate(context: ExecutionContext): boolean { |
|||
const requiredPermission = this.reflector.get<string>( |
|||
HAS_PERMISSION_KEY, |
|||
context.getHandler() |
|||
); |
|||
|
|||
if (!requiredPermission) { |
|||
return true; // No specific permissions required
|
|||
} |
|||
|
|||
const { user } = context.switchToHttp().getRequest(); |
|||
|
|||
if (!user || !hasPermission(user.permissions, requiredPermission)) { |
|||
throw new HttpException( |
|||
getReasonPhrase(StatusCodes.FORBIDDEN), |
|||
StatusCodes.FORBIDDEN |
|||
); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
@ -1,10 +1,11 @@ |
|||
import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service'; |
|||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; |
|||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; |
|||
import { Module } from '@nestjs/common'; |
|||
|
|||
@Module({ |
|||
exports: [AccountBalanceService], |
|||
imports: [PrismaModule], |
|||
imports: [ExchangeRateDataModule, PrismaModule], |
|||
providers: [AccountBalanceService] |
|||
}) |
|||
export class AccountBalanceModule {} |
|||
|
@ -0,0 +1,18 @@ |
|||
import { Component, OnInit } from '@angular/core'; |
|||
import { DataService } from '@ghostfolio/client/services/data.service'; |
|||
|
|||
@Component({ |
|||
selector: 'gf-base-product-page', |
|||
template: '' |
|||
}) |
|||
export class BaseProductPageComponent implements OnInit { |
|||
public price: number; |
|||
|
|||
public constructor(private dataService: DataService) {} |
|||
|
|||
public ngOnInit() { |
|||
const { subscriptions } = this.dataService.fetchInfo(); |
|||
|
|||
this.price = subscriptions?.default?.price; |
|||
} |
|||
} |
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue