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 { ConfigModule, ConfigService } from '@nestjs/config'
|
||||||
import { PassportModule } from '@teacinema/passport'
|
import { PassportModule } from '@teacinema/passport'
|
||||||
|
|
||||||
|
import { AccountModule } from '../modules/account/account.module'
|
||||||
import { AuthModule } from '../modules/auth/auth.module'
|
import { AuthModule } from '../modules/auth/auth.module'
|
||||||
|
|
||||||
import { AppController } from './app.controller'
|
import { AppController } from './app.controller'
|
||||||
@ -12,6 +13,7 @@ import { getPassportConfig } from './config'
|
|||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
AccountModule,
|
||||||
PassportModule.registerAsync({
|
PassportModule.registerAsync({
|
||||||
useFactory: getPassportConfig,
|
useFactory: getPassportConfig,
|
||||||
inject: [ConfigService]
|
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 { ClientsModule, Transport } from '@nestjs/microservices'
|
||||||
import { PROTO_PATHS } from '@teacinema/contracts'
|
import { PROTO_PATHS } from '@teacinema/contracts'
|
||||||
|
|
||||||
|
import { AccountModule } from '../account/account.module'
|
||||||
|
|
||||||
import { AuthController } from './auth.controller'
|
import { AuthController } from './auth.controller'
|
||||||
import { AuthClientGrpc } from './auth.grpc'
|
import { AuthClientGrpc } from './auth.grpc'
|
||||||
|
|
||||||
@ -21,7 +23,8 @@ import { AuthClientGrpc } from './auth.grpc'
|
|||||||
}),
|
}),
|
||||||
inject: [ConfigService]
|
inject: [ConfigService]
|
||||||
}
|
}
|
||||||
])
|
]),
|
||||||
|
AccountModule
|
||||||
],
|
],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthClientGrpc]
|
providers: [AuthClientGrpc]
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export * from './protected.decorator'
|
export * from './protected.decorator'
|
||||||
export * from './current-user.decorator'
|
export * from './current-user.decorator'
|
||||||
|
export * from './roles.decorator'
|
||||||
|
|||||||
@ -1,5 +1,14 @@
|
|||||||
import { applyDecorators, UseGuards } from '@nestjs/common'
|
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 './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==
|
integrity sha512-L006mySvhRpUlRHf3ONbXA0RG42gbQ9QAnmQuZx4zejHEsWB9702xe18MSYcBilv+lmYkQpiX/g3p8P7suLfqQ==
|
||||||
|
|
||||||
"@teacinema/contracts@^1.0.0":
|
"@teacinema/contracts@^1.0.0":
|
||||||
version "1.0.5"
|
version "1.0.6"
|
||||||
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.5/contracts-1.0.5.tgz#f6db65f58ba3621e81e886f54d6ec9e674c4785b"
|
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.6/contracts-1.0.6.tgz#7aa97d4c3081b791908c90d2a12d771b5fb79bd5"
|
||||||
integrity sha512-paFa/zVWKzDNuH04EyGPlG981YEX7Xt42gRwpfZwACGa5CCM5lehT7xKU4h9p/l9IebrOtTPDwJdQn/D41o3Gw==
|
integrity sha512-2IU2S9hWtKeMpn4zRS9pJAaSryIODrRjS1q9QezQOXF6554vtQlMVr0fLVTGhNmXlbRfuje0f6HggApqPDFMaA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nestjs/microservices" "^11.1.12"
|
"@nestjs/microservices" "^11.1.12"
|
||||||
protoc "33.4.0"
|
protoc "33.4.0"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user