From 3c9cba1e862bf4415669f6b3084aa4e47fcb2c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A4=80=ED=9D=A0?= Date: Thu, 18 Aug 2022 17:55:17 +0900 Subject: [PATCH] =?UTF-8?q?1=EB=B2=88=EC=A7=B8=20=EC=88=98=EC=A0=95..?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 4 +++- src/auth/auth.controller.ts | 1 + src/auth/auth.service.ts | 5 +++- src/auth/decorators/has-roles.decorator.ts | 4 ++++ src/auth/guards/jwt.strategy.ts | 2 +- src/auth/guards/local.strategy.ts | 3 ++- src/auth/guards/role.enum.ts | 4 ++++ src/auth/guards/roles.guard.spec.ts | 7 ++++++ src/auth/guards/roles.guard.ts | 27 ++++++++++++++++++++++ src/todo/todo.controller.ts | 15 +++++++++++- 10 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 src/auth/decorators/has-roles.decorator.ts create mode 100644 src/auth/guards/role.enum.ts create mode 100644 src/auth/guards/roles.guard.spec.ts create mode 100644 src/auth/guards/roles.guard.ts diff --git a/.env b/.env index 71e3837..c7a4f91 100644 --- a/.env +++ b/.env @@ -6,6 +6,8 @@ DATABASE_URL="mysql://root:@localhost:3306/test" JWT_SECURITY_KEY = "security_key" -JWT_EXPIRE_MAX = "60s" +JWT_EXPIRE_MAX = "600s" +AUTH_USERNAME_FIELD="email" + DEFAULT_TABLE_PER_PAGE = 20 DEFAULT_TABLE_PAGE = 0 diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index af11c0c..8c525ec 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -27,6 +27,7 @@ export class AuthController { @UseGuards(JwtAuthGuard) @Get('islogin') getProfile(@Request() req) { + //console.log(req) return req.user } } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 03201b6..1730bcb 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -15,18 +15,21 @@ export class AuthService { if (user && user.password === password) { const { password, ...result } = user // result는 password 를 제외한 user의 모든 정보를 포함한다. + //console.log(result) return result } return null } async login(user: any) { + //console.log(user) const payload = { id: user.id, email: user.email, name: user.name, - role: user.role + roles: [user.role] } + // console.log(payload) return { access_token: this.jwtService.sign(payload) } } } diff --git a/src/auth/decorators/has-roles.decorator.ts b/src/auth/decorators/has-roles.decorator.ts new file mode 100644 index 0000000..ed5ce67 --- /dev/null +++ b/src/auth/decorators/has-roles.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common' +import { Role } from '../guards/role.enum' + +export const HasRoles = (...roles: Role[]) => SetMetadata('has-roles', roles) diff --git a/src/auth/guards/jwt.strategy.ts b/src/auth/guards/jwt.strategy.ts index 7e809c0..bb86db9 100644 --- a/src/auth/guards/jwt.strategy.ts +++ b/src/auth/guards/jwt.strategy.ts @@ -18,7 +18,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { id: payload.id, email: payload.email, name: payload.name, - role: payload.role + roles: payload.roles } } } diff --git a/src/auth/guards/local.strategy.ts b/src/auth/guards/local.strategy.ts index d17d5bf..496d116 100644 --- a/src/auth/guards/local.strategy.ts +++ b/src/auth/guards/local.strategy.ts @@ -2,13 +2,14 @@ import { Strategy } from 'passport-local' import { PassportStrategy } from '@nestjs/passport' import { Injectable, UnauthorizedException } from '@nestjs/common' import { AuthService } from 'src/auth/auth.service' +import { env } from 'process' @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { //super() //If you want to check user authenticate with custom column like 'email', try pass it. - super({ usernameField: 'email' }) + super({ usernameField: env.AUTH_USERNAME_FIELD }) } async validate(email: string, password: string): Promise { diff --git a/src/auth/guards/role.enum.ts b/src/auth/guards/role.enum.ts new file mode 100644 index 0000000..bec8f48 --- /dev/null +++ b/src/auth/guards/role.enum.ts @@ -0,0 +1,4 @@ +export enum Role { + USER = 'USER', + ADMIN = 'ADMIN' +} diff --git a/src/auth/guards/roles.guard.spec.ts b/src/auth/guards/roles.guard.spec.ts new file mode 100644 index 0000000..96c1a77 --- /dev/null +++ b/src/auth/guards/roles.guard.spec.ts @@ -0,0 +1,7 @@ +import { RolesGuard } from './roles.guard'; + +describe('RolesGuard', () => { + it('should be defined', () => { + expect(new RolesGuard()).toBeDefined(); + }); +}); diff --git a/src/auth/guards/roles.guard.ts b/src/auth/guards/roles.guard.ts new file mode 100644 index 0000000..7e39da4 --- /dev/null +++ b/src/auth/guards/roles.guard.ts @@ -0,0 +1,27 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { Observable } from 'rxjs' +import { Role } from './role.enum' + +//참고: https://shpota.com/2022/07/16/role-based-authorization-with-jwt-using-nestjs.html +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate( + context: ExecutionContext + ): boolean | Promise | Observable { + const requiredRoles = this.reflector.getAllAndOverride( + 'has-roles', + [context.getHandler(), context.getClass()] + ) + if (!requiredRoles) { + return true + } + const { user } = context.switchToHttp().getRequest() + //console.log(requiredRoles) + //console.log(user) + return requiredRoles.some((role) => user?.roles?.includes(role)) + //return true + } +} diff --git a/src/todo/todo.controller.ts b/src/todo/todo.controller.ts index 1d14a3b..4419bc7 100644 --- a/src/todo/todo.controller.ts +++ b/src/todo/todo.controller.ts @@ -6,10 +6,15 @@ import { Param, Post, Put, - Query + Query, + UseGuards } from '@nestjs/common' import { Todo } from '@prisma/client' import { env } from 'process' +import { HasRoles } from 'src/auth/decorators/has-roles.decorator' +import { JwtAuthGuard } from 'src/auth/guards/jwt.authguard' +import { Role } from 'src/auth/guards/role.enum' +import { RolesGuard } from 'src/auth/guards/roles.guard' import { TodoDTO } from './dtos/todo.dto' import { TodoService } from './todo.service' @@ -47,21 +52,29 @@ export class TodoController { return this.todoService.fetchAll(sql) } + @HasRoles(Role.USER) + @UseGuards(JwtAuthGuard, RolesGuard) @Get(':id') async fetchOne(@Param('id') id: number): Promise { return this.todoService.fetchOne(id) } + @HasRoles(Role.USER) + @UseGuards(JwtAuthGuard, RolesGuard) @Delete(':id') async delete(@Param('id') id: number): Promise { return this.todoService.remove(id) } + @HasRoles(Role.USER) + @UseGuards(JwtAuthGuard, RolesGuard) @Post() async add(@Body() data: Todo): Promise { return this.todoService.add(data) } + @HasRoles(Role.USER) + @UseGuards(JwtAuthGuard, RolesGuard) @Put(':id') async update(@Param('id') id: number, @Body() data: TodoDTO): Promise { return this.todoService.update(id, data)