feat: add role decorator
This commit is contained in:
parent
59f57ded54
commit
07e84f910b
@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config'
|
||||
import { PassportModule } from '@teacinema/passport'
|
||||
|
||||
import { AccountModule } from '../modules/account/account.module'
|
||||
import { AuthModule } from '../modules/auth/auth.module'
|
||||
|
||||
import { AppController } from './app.controller'
|
||||
@ -12,6 +13,7 @@ import { getPassportConfig } from './config'
|
||||
imports: [
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
AuthModule,
|
||||
AccountModule,
|
||||
PassportModule.registerAsync({
|
||||
useFactory: getPassportConfig,
|
||||
inject: [ConfigService]
|
||||
|
||||
22
src/modules/account/account.grpc.ts
Normal file
22
src/modules/account/account.grpc.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Inject, Injectable, OnModuleInit } from '@nestjs/common'
|
||||
import type { ClientGrpc } from '@nestjs/microservices'
|
||||
import {
|
||||
AccountServiceClient,
|
||||
GetAccountRequest
|
||||
} from '@teacinema/contracts/gen/account'
|
||||
|
||||
@Injectable()
|
||||
export class AccountClientGrpc implements OnModuleInit {
|
||||
private accountService: AccountServiceClient
|
||||
|
||||
constructor(@Inject('ACCOUNT_PACKAGE') private readonly client: ClientGrpc) {}
|
||||
|
||||
onModuleInit() {
|
||||
this.accountService =
|
||||
this.client.getService<AccountServiceClient>('AccountService')
|
||||
}
|
||||
|
||||
public getAccount(request: GetAccountRequest) {
|
||||
return this.accountService.getAccount(request)
|
||||
}
|
||||
}
|
||||
28
src/modules/account/account.module.ts
Normal file
28
src/modules/account/account.module.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { ClientsModule, Transport } from '@nestjs/microservices'
|
||||
import { PROTO_PATHS } from '@teacinema/contracts'
|
||||
|
||||
import { AccountClientGrpc } from './account.grpc'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ClientsModule.registerAsync([
|
||||
{
|
||||
name: 'ACCOUNT_PACKAGE',
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
transport: Transport.GRPC,
|
||||
options: {
|
||||
package: 'account.v1',
|
||||
protoPath: PROTO_PATHS.ACCOUNT,
|
||||
url: configService.getOrThrow<string>('AUTH_GRPC_URL')
|
||||
}
|
||||
}),
|
||||
inject: [ConfigService]
|
||||
}
|
||||
])
|
||||
],
|
||||
providers: [AccountClientGrpc],
|
||||
exports: [AccountClientGrpc]
|
||||
})
|
||||
export class AccountModule {}
|
||||
@ -3,6 +3,8 @@ import { ConfigService } from '@nestjs/config'
|
||||
import { ClientsModule, Transport } from '@nestjs/microservices'
|
||||
import { PROTO_PATHS } from '@teacinema/contracts'
|
||||
|
||||
import { AccountModule } from '../account/account.module'
|
||||
|
||||
import { AuthController } from './auth.controller'
|
||||
import { AuthClientGrpc } from './auth.grpc'
|
||||
|
||||
@ -21,7 +23,8 @@ import { AuthClientGrpc } from './auth.grpc'
|
||||
}),
|
||||
inject: [ConfigService]
|
||||
}
|
||||
])
|
||||
]),
|
||||
AccountModule
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthClientGrpc]
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './protected.decorator'
|
||||
export * from './current-user.decorator'
|
||||
export * from './roles.decorator'
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
import { applyDecorators, UseGuards } from '@nestjs/common'
|
||||
import { Role } from '@teacinema/contracts/gen/account'
|
||||
|
||||
import { AuthGuard } from '../guards'
|
||||
import { AuthGuard, RolesGuard } from '../guards'
|
||||
|
||||
export const Protected = () => applyDecorators(UseGuards(AuthGuard))
|
||||
import { Roles } from './roles.decorator'
|
||||
|
||||
export const Protected = (...roles: Role[]) => {
|
||||
if (roles.length === 0) {
|
||||
return applyDecorators(UseGuards(AuthGuard))
|
||||
}
|
||||
|
||||
return applyDecorators(Roles(...roles), UseGuards(AuthGuard, RolesGuard))
|
||||
}
|
||||
|
||||
6
src/shared/decorators/roles.decorator.ts
Normal file
6
src/shared/decorators/roles.decorator.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { SetMetadata } from '@nestjs/common'
|
||||
import { Role } from '@teacinema/contracts/gen/account'
|
||||
|
||||
export const ROLES_KEY = 'required_roles'
|
||||
|
||||
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles)
|
||||
@ -1 +1,2 @@
|
||||
export * from './auth.guard'
|
||||
export * from './roles.guard'
|
||||
|
||||
54
src/shared/guards/roles.guard.ts
Normal file
54
src/shared/guards/roles.guard.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException
|
||||
} from '@nestjs/common'
|
||||
import { Reflector } from '@nestjs/core'
|
||||
import { Role } from '@teacinema/contracts/gen/account'
|
||||
import { Request } from 'express'
|
||||
import { lastValueFrom } from 'rxjs'
|
||||
|
||||
import { AccountClientGrpc } from '../../modules/account/account.grpc'
|
||||
import { ROLES_KEY } from '../decorators'
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
public constructor(
|
||||
private readonly reflector: Reflector,
|
||||
private readonly accountClient: AccountClientGrpc
|
||||
) {}
|
||||
|
||||
public async canActivate(context: ExecutionContext) {
|
||||
const required = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass()
|
||||
])
|
||||
|
||||
if (!required || required.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest<Request>()
|
||||
const user = request.user
|
||||
|
||||
if (!user) {
|
||||
throw new ForbiddenException('User context missing')
|
||||
}
|
||||
|
||||
const account = await lastValueFrom(
|
||||
this.accountClient.getAccount({ id: user.id })
|
||||
)
|
||||
|
||||
if (!account) {
|
||||
throw new NotFoundException('Account not found')
|
||||
}
|
||||
|
||||
if (!required.includes(account.role)) {
|
||||
throw new ForbiddenException('Forbidden')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1221,9 +1221,9 @@
|
||||
integrity sha512-L006mySvhRpUlRHf3ONbXA0RG42gbQ9QAnmQuZx4zejHEsWB9702xe18MSYcBilv+lmYkQpiX/g3p8P7suLfqQ==
|
||||
|
||||
"@teacinema/contracts@^1.0.0":
|
||||
version "1.0.5"
|
||||
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.5/contracts-1.0.5.tgz#f6db65f58ba3621e81e886f54d6ec9e674c4785b"
|
||||
integrity sha512-paFa/zVWKzDNuH04EyGPlG981YEX7Xt42gRwpfZwACGa5CCM5lehT7xKU4h9p/l9IebrOtTPDwJdQn/D41o3Gw==
|
||||
version "1.0.6"
|
||||
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.6/contracts-1.0.6.tgz#7aa97d4c3081b791908c90d2a12d771b5fb79bd5"
|
||||
integrity sha512-2IU2S9hWtKeMpn4zRS9pJAaSryIODrRjS1q9QezQOXF6554vtQlMVr0fLVTGhNmXlbRfuje0f6HggApqPDFMaA==
|
||||
dependencies:
|
||||
"@nestjs/microservices" "^11.1.12"
|
||||
protoc "33.4.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user