Roles(setMetadata, APP_GUARD)

김종민·2022년 6월 29일
0

Nuber-Server

목록 보기
17/34

들어가기
resolver의 함수들을 role(Owner, User, Client)에 따라,
permission이 되게 하는 guard를 설정하는 방법

1. auth/auth.module.ts

import { Global, Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';

@Global()
@Module({
  providers: [
    {
      provide: APP_GUARD,  ///모든 Resolver에 적용되게 하는 명령어
      useClass: AuthGuard,  ///사용될 guard가 AuthGuard라는 뜻.
    },
  ],
})
export class AuthModule {}

2. auth/role.decorator.ts

import { SetMetadata } from '@nestjs/common';
///setMetadata는 모든 Resolver에 적용시킨다는 의미.
import { UserRole } from 'src/users/entities/user.entity';
///user entity의 UserRole을 불러들인다.

export type AllowedRoles = keyof typeof UserRole | 'Any';
///AllowedRoles를 만들어서 UserRole(Owner, Client, Delivery)나 Any의
/// type을 가지게 설정함.

export const Role = (roles: AllowedRoles[]) => SetMetadata('rolse', roles);
///Role이라는 함수를 만듬, AllowedRoles[] 를 받아서 SetMetaData로
///'roles'라는 key로 roles(AllowedRoles[])라는 value를 사용

3. user.entity.ts

export enum UserRole {
  Client = 'Client',
  Owner = 'Owner',
  Delivery = 'Delivery',
}
///enum을 string으로 만들어줌.
///원래는 enum은(0,1,2..)로 DB에 저장되기 때문에, 기존에 DB에 enum이 저장되어
///있으면, 다 지워야 함. 이렇게 바꾸고 나서는 string으로 입력되기 떄문에,
///기존에 DB에 0,1,2...등으로 저장된게 있으면, error가 나옴,,

registerEnumType(UserRole, { name: 'UserRole' });

4.auth.guard.ts

canActivate는 오로지 true, false임. pass, unPass임.

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { GqlExecutionContext } from '@nestjs/graphql';
import { User } from 'src/users/entities/user.entity';
import { AllowedRoles } from './role.decorator';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}
  canActivate(context: ExecutionContext) {
    const roles = this.reflector.get<AllowedRoles>(
      'roles', ===>role.decorator.ts의 setMetadata의 key임..
      context.getHandler(),
    );
    ///Resolver에서 맨 앞의 @Role(['Owner'])으로 deco해주는 부분에서 
    ///''안에 적힌 role을 따 내는 방법. reflector을 사용.
    
    if (!roles) {
      return true;
    }
    ///deco가 없는 resolver는 계속진행시킨다(login, createAccount등등)
    
    const gqlContext = GqlExecutionContext.create(context).getContext();
    const user: User = gqlContext['user'];
    if (!user) {
      return false;
    }
    if (roles.includes('Any')) {
      return true;
    }
    ///일단, @Role([''])이 deco되어 있으면, user가 존재해야 하는 상황임.
    ///그 상황속에서, @Role(['Any'])가 있으면, Owner, Client, Delivery
    ///모두 Resolver에 접근 가능하다는 의미(seeProfile, me, editProfile..)
    
    return roles.includes(user.role);
    ///resolver에서 deco되어 있는 Role에 login한 user의 role이
    ///같은지를 판단해서 true, false를 return함.
    ///createRestaurnt는 오직 Owner만 가능하게..
  }
}

5. restaurant.resolver.ts

example~

  @Mutation(() => CreateRestaurantOutput)
  @Role(['Owner']) **********************************************!@#!@#
  async createRestaurant(
    @AuthUser() authUser: User,
    @Args('input') createRestaurantInput: CreateRestaurantInput,
  ): Promise<CreateRestaurantOutput> {
    return this.restaurantService.createRestaurant(
      authUser,
      createRestaurantInput,
    );
  }

  @Mutation(() => EditRestaurantOutput)
  @Role(['Owner'])*********************!@#!@$#@$@#%@#$!@#!@#
  async editRestaurant(
    @AuthUser() owner: User,
    @Args('input') editRestaurantInput: EditRestaurantInput,
  ): Promise<EditRestaurantOutput> {
    return this.restaurantService.editRestaurant(owner, editRestaurantInput);
  }

이 부분은 처음부터 끝까지 그냥 무지하게 중요하므로
마르고 닳도록 볼것!!!!!!!!!!!!!

Setting roles per handler(핸들러별 역할 설정): @SetMetadata()

예를 들어 CatsController는 다른 route에 대해 다른 권한 체계를 가질 수 있습니다. 일부는 관리자만 사용할 수 있고 다른 일부는 모든 사람에게 공개될 수 있습니다. 유연하고 재사용 가능한 방식으로 role을 route에 사용하려면 어떻게 해야 합니까?
여기서 custom metadata가 작동합니다. Nest는 @SetMetadata() 데코레이터를 통해 라우트 핸들러에 커스텀 메타데이터를 붙이는 기능을 제공합니다.
ex) @SetMetadata('role', Role.Owner)
key - 메타데이터가 저장되는 키를 정의하는 값
value - 키와 연결될 메타데이터

route에서 직접 @SetMetadata()를 사용하는 것은 좋은 습관이 아닙니다. 대신 아래와 같이 자신만의 데코레이터를 만듭니다.

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

https://docs.nestjs.com/guards#setting-roles-per-handler

Global 가드는 모든 컨트롤러와 모든 route handler에 대해 전체 애플리케이션에서 사용됩니다.
dependency injection 관점에서, 모듈 외부에서 등록된 전역 가드(위의 예에서와 같이 useGlobalGuards() 사용)는 dependency injection을 할 수 없습니다. 이는 이것이 모든 모듈의 context 외부에서 수행되기 때문입니다. 이 문제를 해결하기 위해 다음 구성을 사용하여 모든 모듈에서 직접 가드를 설정할 수 있습니다.

app.module.ts

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}

https://docs.nestjs.com/guards#binding-guards

Metadata가 설정되어있지 않으면 public(누구나 접근 가능)
Metadata가 설정되어있으면 private(특정 role만 접근 가능하도록 제한)

Putting it all together
이제 뒤로 돌아가서 이것을 RolesGuard와 연결해 보겠습니다. 현재 사용자에게 할당된 role을 처리 중인 현재 route에 필요한 실제 role과 비교하여 반환 값을 조건부로 만들고 싶습니다.
https://docs.nestjs.com/guards#putting-it-all-together

profile
코딩하는초딩쌤

0개의 댓글