mirror of https://github.com/ghostfolio/ghostfolio
21 changed files with 111 additions and 193 deletions
@ -0,0 +1,41 @@ |
|||||
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
||||
|
import { ImpersonationGuard } from '@ghostfolio/api/guards/impersonation.guard'; |
||||
|
import { ScopeGuard } from '@ghostfolio/api/guards/scope.guard'; |
||||
|
import { scopes } from '@ghostfolio/common/scopes'; |
||||
|
|
||||
|
import { GUARDS_METADATA } from '@nestjs/common/constants'; |
||||
|
import { AuthGuard } from '@nestjs/passport'; |
||||
|
|
||||
|
import { REQUIRES_SCOPE_KEY, RequiresScope } from './requires-scope.decorator'; |
||||
|
|
||||
|
class TestController { |
||||
|
@RequiresScope(scopes.portfolioRead) |
||||
|
public getPortfolio() { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
describe('Requires scope', () => { |
||||
|
it('Sets the required scopes', () => { |
||||
|
expect( |
||||
|
Reflect.getMetadata( |
||||
|
REQUIRES_SCOPE_KEY, |
||||
|
TestController.prototype.getPortfolio |
||||
|
) |
||||
|
).toEqual([scopes.portfolioRead]); |
||||
|
}); |
||||
|
|
||||
|
it('Applies the guards in the required order', () => { |
||||
|
expect( |
||||
|
Reflect.getMetadata( |
||||
|
GUARDS_METADATA, |
||||
|
TestController.prototype.getPortfolio |
||||
|
) |
||||
|
).toEqual([ |
||||
|
AuthGuard('jwt'), |
||||
|
HasPermissionGuard, |
||||
|
ImpersonationGuard, |
||||
|
ScopeGuard |
||||
|
]); |
||||
|
}); |
||||
|
}); |
||||
@ -1,12 +1,26 @@ |
|||||
import { SetMetadata } from '@nestjs/common'; |
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
||||
|
import { ImpersonationGuard } from '@ghostfolio/api/guards/impersonation.guard'; |
||||
|
import { ScopeGuard } from '@ghostfolio/api/guards/scope.guard'; |
||||
|
import { Scope } from '@ghostfolio/common/scopes'; |
||||
|
|
||||
|
import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common'; |
||||
|
import { AuthGuard } from '@nestjs/passport'; |
||||
|
|
||||
export const REQUIRES_SCOPE_KEY = 'requires_scope'; |
export const REQUIRES_SCOPE_KEY = 'requires_scope'; |
||||
|
|
||||
/** |
/** |
||||
* Marks a controller or a route which requires the given scopes of the |
* Marks a route which requires the given scopes and applies the guards which |
||||
* impersonation context, which requires the ImpersonationGuard and the |
* resolve the impersonation context and evaluate it, hence the ScopeGuard |
||||
* ScopeGuard to be applied to the route |
* cannot be applied without the ImpersonationGuard preceding it |
||||
*/ |
*/ |
||||
export function RequiresScope(...requiredScopes: string[]) { |
export function RequiresScope(...requiredScopes: Scope[]) { |
||||
return SetMetadata(REQUIRES_SCOPE_KEY, requiredScopes); |
return applyDecorators( |
||||
|
SetMetadata(REQUIRES_SCOPE_KEY, requiredScopes), |
||||
|
UseGuards( |
||||
|
AuthGuard('jwt'), |
||||
|
HasPermissionGuard, |
||||
|
ImpersonationGuard, |
||||
|
ScopeGuard |
||||
|
) |
||||
|
); |
||||
} |
} |
||||
|
|||||
@ -1,5 +1,6 @@ |
|||||
import { Access } from '@ghostfolio/common/interfaces'; |
import { Access } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
export interface CreateOrUpdateAccessDialogParams { |
export interface CreateOrUpdateAccessDialogParams { |
||||
access?: Access; |
// TODO: Include the scopes once the dialog allows to configure them
|
||||
|
access?: Omit<Access, 'scopes'>; |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue