Token 만들기

  • jwt 직접 만들기

    -> npm i jsonwebtoken

    -> npm i @types/jsonwebtoken –only-dev

    • app.module.ts

      • token 을 user한테 지정시, 사용자는 자기 token에 뭐 들어있는지 볼 수 있음

      -> token에는 중요한 개인 정보 넣지 않기(ID정도)

      • 목적: 사용자에게 약간의 json 주기(우리가 json 지정해줘야 함)

      -> SCRET_KEY 추가

      @Module({
        imports: [
          ConfigModule.forRoot({
            isGlobal: true,
            envFilePath: process.env.NODE_ENV == "dev" ? ".env.dev" : ".env.test",
            ignoreEnvFile: process.env.NODE_ENV ==="prod", //production환경일땐 ConfigModule이 환경변수 파일 
            validationSchema: Joi.object({
              NODE_ENV: Joi.string()
                .valid('dev', 'prod')
                .required(), // 환경변수 유효성 검사
              DB_HOST: Joi.string().required(),
              DB_PORT: Joi.string().required(),
              DB_USERNAME: Joi.string().required(),
              DB_PASSWORD: Joi.string().required(),
              DB_NAME: Joi.string().required(),
              SECRET_KEY: Joi.string().required(), //token 지정을 위해 사용하는 privateKey
            }),
      
    • .env.dev

      -> SECRET_KEY는 secret key generater에서 복붙

          DB_HOST=localhost
          DB_PORT=5432
          DB_USERNAME=postgres
          DB_PASSWORD=1234
          DB_NAME=nuber-eats
          SECRET_KEY=7a3MHXMhcOMOtud3rYkmOrrr7Iua31II 
            
      
    • users.module.ts

      import { Module } from '@nestjs/common';
      import { ConfigService } from '@nestjs/config';
      import { TypeOrmModule } from '@nestjs/typeorm';
      import { User } from './entities/user.entity';
      import { UsersResolver } from './users.resolver';
      import { UsersService } from './users.service';
          
      @Module({
          imports: [TypeOrmModule.forFeature([User]), ConfigService], //service는 repository 필요로 하기 떄문에 // ConfigService는 token 생성할 때 import해주려고 users.module안에 configService 추가
          providers:[UsersResolver, UsersService],
      })
      export class UsersModule {}
          
      
    • users.service.ts

      -> token 생성

      -> jsonwebtoken import하기

      import { Injectable } from "@nestjs/common";
      import { InjectRepository } from "@nestjs/typeorm";
      import { Repository } from "typeorm";
      import { createAccountInput } from "./dtos/create-account.dto";
      import { LoginInput } from "./dtos/login.dto";
      import { User } from "./entities/user.entity";
      import * as jwt from 'jsonwebtoken';
      import { ConfigService } from "@nestjs/config";
          
      @Injectable()
          
      export class UsersService{
          constructor(
              @InjectRepository(User) private readonly users: Repository <User>,//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity
              private readonly config: ConfigService // token 파트에서 ConfigService import해서 사용하려고
              ){
                  console.log(this.config.get("SECRET_KEY"))
              }
          
          async createAccount({email, password, role}: createAccountInput
              ): Promise <{ok: boolean, error? :string }>{
              //check new user(that email does not exist)
              try {
                  const exists = await this.users.findOne({email}) //findOne = 주어진 condition(환경)과 일치하는 첫 번째 entity 찾기
                  if(exists){
                      //make error
                      return {ok: false, error: 'There is a uwer with that email already'}; //boolean =false, error ="there~"
                  }
                  await this.users.save(this.users.create({email, password, role})) //없다면 새로운 계정 create & save
                  return {ok: true};
              } catch(e){
                  return {ok: false, error: "Couldn't create account"};
              }
              // create user & hash the password
                  
          }
          
          async login({
              email, 
              password
          }: LoginInput): Promise<{ok: boolean; error?:string, token?: string}> {
          
              //make a JWT and give it to the user
              try{
                  // find the user with the email
                  const user = await this.users.findOne({ email });
                  if(!user){ //user가 존재하지 않는다면
                      return {
                          ok:false,
                          error: 'User not found',
                      }
                  }
                  //check if the password is correct
                  //비밀번호를 hash 한후 데이터베이스에 있는 hash된 비번과 같은지 확인
                  const passwordCorrect = await user.checkPassword(password); //여기의 user와 위의 const user와는 다름.. 전자는 entity 
                  if (!passwordCorrect){
                      return{
                          ok:false,
                          error:"Wrong password"
                      }
                  }
                  const token =jwt.sign({id:user.id},this.config.get('SECRET_KEY'))//지정(sign)하기 & sign()안에는 무엇을 넣어 주고 싶은지(여기선 user ID)
                  return{
                      ok:true,
                      token: 'llalaalalala',
                  }
              }catch(error){
                  return{
                      ok: false,
                      error,
                  }
              }
                  
          }
      }
      

      -> json web token 목적: 우리만이 유효한 인증을 할 수 있게 하는것(정보의 진위여부가 중요)

token module 만들기

  • module 종류

    1. ‘static module’

      ex) UsersModule

      ​ JwtModule

      ​ -> 어떤 설정도 안돼있음

    2. ‘dynamic module’

      ex) GraphQLModule

      -> 설정이 적용되어 있는 module

    => dynamic module은 결과적으로 static module이 됨

    dynamic module 만들기 -> 옵션설정 -> 리턴 값으로 설정한 옵션들이 존재하는 정적인 모듈

  • nest g mo jwt

  1. module안에 .forRoot 구현

    • jwt.module.ts

      import { DynamicModule, Module } from '@nestjs/common';
           
      @Module({})
      export class JwtModule {
          static forRoot(): DynamicModule{ //DynamicModule은 module을 반환해주는 module
              return {
                  module:JwtModule,
                                  //module이 service를 export할 수 있도록
              }
          }
      }
      
  2. module이 service를 export 할 수 있도록 JwtSevice 만들기

    -> nest g s jwt

    • jwt.module.ts

      import { DynamicModule, Module } from '@nestjs/common';
      import { JwtService } from './jwt.service';
           
      @Module({})
      export class JwtModule {
          static forRoot(): DynamicModule{ //DynamicModule은 module을 반환해주는 module
              return {
                  module:JwtModule,
                  exports: [JwtService],
                  providers: [JwtService],
              }
          }
      }
      
    • users.module.ts

      -> JwtService를 import

      import { Module } from '@nestjs/common';
      import { ConfigService } from '@nestjs/config';
      import { TypeOrmModule } from '@nestjs/typeorm';
      import { JwtService } from 'src/jwt/jwt.service';
      import { User } from './entities/user.entity';
      import { UsersResolver } from './users.resolver';
      import { UsersService } from './users.service';
           
      @Module({
          imports: [TypeOrmModule.forFeature([User]), ConfigService, JwtService], //service는 repository 필요로 하기 떄문에 // ConfigService는 token 생성할 때 import해주려고 users.module안에 configService 추가
          providers:[UsersResolver, UsersService],
      })
      export class UsersModule {}
      
    • user.service.ts

      export class UsersService{
          constructor(
              @InjectRepository(User) private readonly users: Repository <User>,//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity
              private readonly config: ConfigService, // token 파트에서 ConfigService import해서 사용하려고
              private readonly jwtService: JwtService //nestjs는 클래스 타입만 보고 import 알아서 찾아줌
              ){
                  console.log(this.config.get("SECRET_KEY"))
              }
      
    • jwt.module.ts

      import { DynamicModule, Global, Module } from '@nestjs/common';
      import { JwtService } from './jwt.service';
           
      @Module({})
      @Global()
      export class JwtModule {
          static forRoot(): DynamicModule{ //DynamicModule은 module을 반환해주는 module
              return {
                  module:JwtModule,
                  exports: [JwtService],
                  providers: [JwtService],
              };
          }
      }
           
           
      

      ※Global로 설정된 module은 imports에 넣어줄 필요 없음

2.3 Global Module & non-Global Module

module에 config 옵션 추가하기

  • jwt 안에 interfaces 폴더 생성 & jwt-module-options.interface.ts

    export interface JwtModuleOptions{
        privateKey:string;
    }//원하는 옵션 추가
    
  • jwt.module.ts

    -> option으로 JwtModuleOptions타입 추가

    import { DynamicModule, Global, Module } from '@nestjs/common';
    import { JwtModuleOptions } from './interfaces/jwt-module-options.interface';
    import { JwtService } from './jwt.service';
      
    @Module({})
    @Global()
    export class JwtModule {
        static forRoot(options: JwtModuleOptions): DynamicModule{ //DynamicModule은 module을 반환해주는 module
            return {
                module:JwtModule,
                exports: [JwtService],
                providers: [JwtService],
            };
        }
    }
      
    
  • app.module.ts

    -> JwtModule에 옵션 넘겨주기

    import { Module } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { GraphQLModule } from '@nestjs/graphql';
    import {TypeOrmModule} from "@nestjs/typeorm";
    import * as Joi from 'joi'; //타입스크립트나 NestJS로 되어있지 않을때 패키지 import
    import { Restaurant } from './restaurants/entities/restaurant.entity';
    import { RestaurantsModule } from './restaurants/restaurants.module';
    import { UsersModule } from './users/users.module';
    import { CommonModule } from './common/common.module';
    import { User } from './users/entities/user.entity';
    import { JwtModule } from './jwt/jwt.module';
      
      
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          envFilePath: process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.test",
          ignoreEnvFile: process.env.NODE_ENV ==="prod", //production환경일땐 ConfigModule이 환경변수 파일 
          validationSchema: Joi.object({
            NODE_ENV: Joi.string()
              .valid('dev', 'prod')
              .required(), // 환경변수 유효성 검사
            DB_HOST: Joi.string().required(),
            DB_PORT: Joi.string().required(),
            DB_USERNAME: Joi.string().required(),
            DB_PASSWORD: Joi.string().required(),
            DB_NAME: Joi.string().required(),
            PRIVATE_KEY: Joi.string().required(), //token 지정을 위해 사용하는 privateKey
          }),
        }),
        TypeOrmModule.forRoot({
          type: 'postgres',
          host: process.env.DB_HOST,
          port: +process.env.DB_PORT,
          username: process.env.DB_USERNAME,
          password: process.env.DB_PASSWORD,  //localhost인 경우엔 안써도 됨
          database: process.env.DB_NAME,
          synchronize: process.env.NODE_ENV ==="prod", //production이 아니면 true로
          logging: process.env.NODE_ENV ==="prod", //DB에 돌아가는 모든 로그 확인
          entities:[User],
        }),
        GraphQLModule.forRoot({ //dynamic module로 설정이 존재
          autoSchemaFile: true,  //root module 설정
        }),
        UsersModule,
        CommonModule,
        JwtModule.forRoot({
          privateKey: process.env.PRIVATE_KEY,
        }), //JwtModule처럼 static module은 어떠한 설정도 적용되어 있지않음
      ],
      controllers: [],
      providers: [],
    })
    export class AppModule {}
      
    
  • Jwt.module.ts

    -> option을 JwtService로 내보내기 => providers 옵션 사용!

    import { DynamicModule, Global, Module } from '@nestjs/common';
    import { JwtModuleOptions } from './interfaces/jwt-module-options.interface';
    import { JwtService } from './jwt.service';
      
    @Module({})
    @Global()
    export class JwtModule {
        static forRoot(options: JwtModuleOptions): DynamicModule{ //DynamicModule은 module을 반환해주는 module
            return {
                module:JwtModule,
                providers: [{
                    provide: "BANANAS", //class 대신 value로 대체
                    useValue: options, //BANANAS라는 이름의 provider로, value가 option
                    },
                    JwtService,
                ],
                exports: [JwtService], 
                 //원하는 class를 provide할 수 있게 해줌
                // provide:JwtService, useClass:JwtService 함축한것
            };
        }
    }
      
    
  • jwt.service.ts

    => module에서 무언가를 service로 inject할 수 있음

    import { Global, Inject, Injectable } from '@nestjs/common';
    import { JwtModuleOptions } from './interfaces/jwt-module-options.interface';
      
      
    @Injectable()
    export class JwtService {
        constructor(
            @Inject('BANANAS') private readonly options: JwtModuleOptions){}
          
        hello(){
            console.log('hello')
        }
    }
      
    

providers안에 필요한거 넣고 inject해서 요청

  • jwt에 jwt.constants.ts 생성 & jwt.interface생성

  • jwt.constants.ts

    export const CONFIG_OPTIONS = "CONFIG_OPTIONS";
      
    
  • jwt.service.ts

    -> BANANAS 변경

    import { Global, Inject, Injectable } from '@nestjs/common';
    import { CONFIG_OPTIONS } from './jwt.constants';
    import { JwtModuleOptions } from './jwt.interfaces';
      
      
    @Injectable()
    export class JwtService {
        constructor(
            @Inject(CONFIG_OPTIONS) private readonly options: JwtModuleOptions){
                console.log(options);
            }
          
        hello(){
            console.log('hello')
        }
    }
      
    
  • jwt.module.ts

    import { DynamicModule, Global, Module } from '@nestjs/common';
    import { CONFIG_OPTIONS } from './jwt.constants';
    import { JwtModuleOptions } from './jwt.interfaces';
    import { JwtService } from './jwt.service';
      
    @Module({})
    @Global()
    export class JwtModule {
        static forRoot(options: JwtModuleOptions): DynamicModule{ //DynamicModule은 module을 반환해주는 module
            return {
                module:JwtModule,
                providers: [{
                    provide: CONFIG_OPTIONS, //class 대신 value로 대체
                    useValue: options, //BANANAS라는 이름의 provider로, value가 option
                    },
                    JwtService,
                ],
                exports: [JwtService], 
                 //원하는 class를 provide할 수 있게 해줌
                // provide:JwtService, useClass:JwtService 함축한것
            };
        }
    }
      
    

2.4

image

image

  • jwt.service.ts

    -> sign할 때 JwtService 사용하도록

    import { Global, Inject, Injectable } from '@nestjs/common';
    import * as jwt from "jsonwebtoken";
    import { CONFIG_OPTIONS } from './jwt.constants';
    import { JwtModuleOptions } from './jwt.interfaces';
      
      
    @Injectable()
    export class JwtService {
        constructor(
            @Inject(CONFIG_OPTIONS) private readonly options: JwtModuleOptions){
            }
              
        sign(userId:number): string{ //user ID 만 암호화해주기
            return jwt.sign({id:userId}, this.options.privateKey);
        }
    }
      
    
  • user.service.ts

    import { Injectable } from "@nestjs/common";
    import { InjectRepository } from "@nestjs/typeorm";
    import { Repository } from "typeorm";
    import { CreateAccountInput } from "./dtos/create-account.dto";
    import { LoginInput } from "./dtos/login.dto";
    import { User } from "./entities/user.entity";
    import * as jwt from 'jsonwebtoken';
    import { ConfigService } from "@nestjs/config";
    import { JwtService } from "src/jwt/jwt.service";
      
    @Injectable()
    export class UsersService{
        constructor(
            @InjectRepository(User) private readonly users: Repository <User>,//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity
            private readonly jwtService: JwtService, //nestjs는 클래스 타입만 보고 import 알아서 찾아줌
            ){}
      
        async createAccount({
            email,
            password, 
            role,
        }: CreateAccountInput): Promise <{ok: boolean, error? :string }>{
            //check new user(that email does not exist)
            try {
                const exists = await this.users.findOne({email}) //findOne = 주어진 condition(환경)과 일치하는 첫 번째 entity 찾기
                if(exists){
                    //make error
                    return {ok: false, error: 'There is a uwer with that email already'}; //boolean =false, error ="there~"
                }
                await this.users.save(this.users.create({email, password, role})); //없다면 새로운 계정 create & save
                return {ok: true};
            } catch(e){
                return {ok: false, error: "Couldn't create account"};
            }
            // create user & hash the password
              
        }
      
        async login({
            email, 
            password
        }: LoginInput): Promise<{ok: boolean; error?:string, token?: string}> {
      
            //make a JWT and give it to the user
            try{
                // find the user with the email
                const user = await this.users.findOne({ email });
                if(!user){ //user가 존재하지 않는다면
                    return {
                        ok:false,
                        error: 'User not found',
                    }
                }
                //check if the password is correct
                //비밀번호를 hash 한후 데이터베이스에 있는 hash된 비번과 같은지 확인
                const passwordCorrect = await user.checkPassword(password); //여기의 user와 위의 const user와는 다름.. 전자는 entity 
                if (!passwordCorrect){
                    return{
                        ok:false,
                        error:"Wrong password",
                    };
                }
                const token = this.jwtService.sign(user.id);//이 module을 내 백엔드만으로 특정함
                return{
                    ok:true,
                    token,
                }
            }catch(error){
                return{
                    ok: false,
                    error,
                };
            }
              
        }
    }
    

    => 이건 내 백엔드에서만 특정// 같이 하는거면 공유가능한 jwtModule만들기

2.5 authentication

  • token 정보가 누구인지

  • users.resolver

      
    import { Resolver, Query, Mutation, Args} from "@nestjs/graphql";
    import { CreateAccountInput, CreateAccountOutput } from "./dtos/create-account.dto";
    import { LoginInput, LoginOutput} from "./dtos/login.dto";
    import { User } from "./entities/user.entity";
    import { UsersService } from "./users.service";
      
    @Resolver(of => User)
    export class UsersResolver {
        constructor(
            private readonly usersService: UsersService
        ){}
      
        @Query(returns => Boolean)//graphQL 루트 만들기
        hi(){
            return true;
        }
      
        @Mutation(returns =>CreateAccountOutput)
        async createAccount(
            @Args("input") createAccountInput: CreateAccountInput,
        ): Promise <CreateAccountOutput>{ //createAccountInput이라는 input type만듦
            try{
                return this.usersService.createAccount(createAccountInput);
            } catch(error) {
                //에러 발생시
                return{
                    error,
                    ok: false,
                }
            }
        }
      
        @Mutation(returns => LoginOutput)
        async login(@Args('input') loginInput: LoginInput ): Promise<LoginOutput>{//input Arguments 필요
            try {
                return  this.usersService.login(loginInput) //loginInput 저장
            } catch(error){
                return{
                    ok: false,
                    error,
                };
            }
        }
        @Query(returns => User)
        me(){
                //middleware 구현
        }//로그인한 사람이 누구인지 반환
    }
    

    => token은 HTTP headers를 활용하여 받기

    image

  • jwt.middleware.ts

    import { Injectable, NestMiddleware } from "@nestjs/common";
    import { NextFunction, Request, Response } from "express";
    import { UsersService } from "src/users/users.service";
    import { JwtService } from "./jwt.service";
    
    @Injectable()
    export class JwtMiddleware implements NestMiddleware{
      constructor(
          private readonly jwtService: JwtService,
          private readonly userService: UsersService
          ){} //injectable일때만 inject할 수 있음
      async use(req:Request, res: Response, next:NextFunction){
          if("x-jwt" in req.headers){
              const token =(req.headers["x-jwt"]);
              const decoded = this.jwtService.verify(token.toString());
              if(typeof decoded ==="object" && decoded.hasOwnProperty('id')){
                  try{
                      const user = await this.userService.findById(decoded['id']);
                      req['user'] = user; // HTTP request
                  }
                  catch(e){
    
                    }
               }
           }
           next();
        }
    } //NestMiddleware로 부터 상속 -> interface처럼 행동
    
    

    -> forRoutes()통해서 /graphql 경로에 method가 POST인 경우에만 적용(apply)

  • app.module.ts

    -> 모든 routes에 적용

    import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { GraphQLModule } from '@nestjs/graphql';
    import {TypeOrmModule} from "@nestjs/typeorm";
    import * as Joi from 'joi'; //타입스크립트나 NestJS로 되어있지 않을때 패키지 import
    import { Restaurant } from './restaurants/entities/restaurant.entity';
    import { RestaurantsModule } from './restaurants/restaurants.module';
    import { UsersModule } from './users/users.module';
    import { CommonModule } from './common/common.module';
    import { User } from './users/entities/user.entity';
    import { JwtModule } from './jwt/jwt.module';
    import { JwtMiddleware } from './jwt/jwt.middleware';
      
      
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          envFilePath: process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.test",
          ignoreEnvFile: process.env.NODE_ENV ==="prod", //production환경일땐 ConfigModule이 환경변수 파일 
          validationSchema: Joi.object({
            NODE_ENV: Joi.string()
              .valid('dev', 'prod')
              .required(), // 환경변수 유효성 검사
            DB_HOST: Joi.string().required(),
            DB_PORT: Joi.string().required(),
            DB_USERNAME: Joi.string().required(),
            DB_PASSWORD: Joi.string().required(),
            DB_NAME: Joi.string().required(),
            PRIVATE_KEY: Joi.string().required(), //token 지정을 위해 사용하는 privateKey
          }),
        }),
        TypeOrmModule.forRoot({
          type: 'postgres',
          host: process.env.DB_HOST,
          port: +process.env.DB_PORT,
          username: process.env.DB_USERNAME,
          password: process.env.DB_PASSWORD,  //localhost인 경우엔 안써도 됨
          database: process.env.DB_NAME,
          synchronize: process.env.NODE_ENV ==="prod", //production이 아니면 true로
          logging: process.env.NODE_ENV ==="prod", //DB에 돌아가는 모든 로그 확인
          entities:[User],
        }),
        GraphQLModule.forRoot({ //dynamic module로 설정이 존재
          autoSchemaFile: true,  //root module 설정
        }),
        UsersModule,
        CommonModule,
        JwtModule.forRoot({
          privateKey: process.env.PRIVATE_KEY,
        }), //JwtModule처럼 static module은 어떠한 설정도 적용되어 있지않음
      ],
      controllers: [],
      providers: [],
    })
    export class AppModule implements NestModule{
      configure(consumer:MiddlewareConsumer){
        consumer.apply(JwtMiddleware).forRoutes({ //jwtMiddleware에 넘겨주기
          path:"*",
          method:RequestMethod.ALL,
      });
    }
    }
      
    
  • app.module.ts

    -> 특정경로 제외

    import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { GraphQLModule } from '@nestjs/graphql';
    import {TypeOrmModule} from "@nestjs/typeorm";
    import * as Joi from 'joi'; //타입스크립트나 NestJS로 되어있지 않을때 패키지 import
    import { Restaurant } from './restaurants/entities/restaurant.entity';
    import { RestaurantsModule } from './restaurants/restaurants.module';
    import { UsersModule } from './users/users.module';
    import { CommonModule } from './common/common.module';
    import { User } from './users/entities/user.entity';
    import { JwtModule } from './jwt/jwt.module';
    import { JwtMiddleware } from './jwt/jwt.middleware';
      
      
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          envFilePath: process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.test",
          ignoreEnvFile: process.env.NODE_ENV ==="prod", //production환경일땐 ConfigModule이 환경변수 파일 
          validationSchema: Joi.object({
            NODE_ENV: Joi.string()
              .valid('dev', 'prod')
              .required(), // 환경변수 유효성 검사
            DB_HOST: Joi.string().required(),
            DB_PORT: Joi.string().required(),
            DB_USERNAME: Joi.string().required(),
            DB_PASSWORD: Joi.string().required(),
            DB_NAME: Joi.string().required(),
            PRIVATE_KEY: Joi.string().required(), //token 지정을 위해 사용하는 privateKey
          }),
        }),
        TypeOrmModule.forRoot({
          type: 'postgres',
          host: process.env.DB_HOST,
          port: +process.env.DB_PORT,
          username: process.env.DB_USERNAME,
          password: process.env.DB_PASSWORD,  //localhost인 경우엔 안써도 됨
          database: process.env.DB_NAME,
          synchronize: process.env.NODE_ENV ==="prod", //production이 아니면 true로
          logging: process.env.NODE_ENV ==="prod", //DB에 돌아가는 모든 로그 확인
          entities:[User],
        }),
        GraphQLModule.forRoot({ //dynamic module로 설정이 존재
          autoSchemaFile: true,  //root module 설정
        }),
        UsersModule,
        CommonModule,
        JwtModule.forRoot({
          privateKey: process.env.PRIVATE_KEY,
        }), //JwtModule처럼 static module은 어떠한 설정도 적용되어 있지않음
      ],
      controllers: [],
      providers: [],
    })
    export class AppModule implements NestModule{
      configure(consumer:MiddlewareConsumer){
        consumer.apply(JwtMiddleware).exclude)({
          path:"/api",
          method:RequestMethod.ALL, // /api를 제외하고 적용
        })
            
    }
    }
      
    
  • jwt.middleware

    import { NestMiddleware } from "@nestjs/common";
    import { NextFunction, Request, Response } from "express";
      
    export function jwtMiddleware(req:Request, res:Response, next:NextFunction){
        console.log(req.headers);
        next();
    }
      
    

-> main.ts에 구현

  • main.ts

    import { ValidationPipe } from '@nestjs/common';
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { jwtMiddleware } from './jwt/jwt.middleware';
      
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      app.useGlobalPipes(
        new ValidationPipe() 
      );
      app.use(jwtMiddleware);
      await app.listen(3000);
    }
    bootstrap();
      
    

2.6

  • jwt.middleware.ts

    -> users repository 가져올거라서 다시 class로

    import { NestMiddleware } from "@nestjs/common";
    import { NextFunction, Request, Response } from "express";
      
    export class JwtMiddleware implements NestMiddleware{
        use(req:Request, res: Response, next:NextFunction){
            console.log(req.headers);
            next();
        }
    } //NestMiddleware로 부터 상속 -> interface처럼 행동
      
    
  • main.ts

    import { ValidationPipe } from '@nestjs/common';
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { JwtMiddleware} from './jwt/jwt.middleware';
      
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      app.useGlobalPipes(
        new ValidationPipe() 
      );
      app.use(JwtMiddleware);
      await app.listen(3000);
    }
    bootstrap();
      
    

=> 이러면 오류남! (app.use에는 function만!)

  • app.module.ts

    import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { GraphQLModule } from '@nestjs/graphql';
    import {TypeOrmModule} from "@nestjs/typeorm";
    import * as Joi from 'joi'; //타입스크립트나 NestJS로 되어있지 않을때 패키지 import
    import { UsersModule } from './users/users.module';
    import { CommonModule } from './common/common.module';
    import { User } from './users/entities/user.entity';
    import { JwtModule } from './jwt/jwt.module';
    import { JwtMiddleware } from './jwt/jwt.middleware';
      
      
      
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          envFilePath: process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.test",
          ignoreEnvFile: process.env.NODE_ENV ==="prod", //production환경일땐 ConfigModule이 환경변수 파일 
          validationSchema: Joi.object({
            NODE_ENV: Joi.string()
              .valid('dev', 'prod')
              .required(), // 환경변수 유효성 검사
            DB_HOST: Joi.string().required(),
            DB_PORT: Joi.string().required(),
            DB_USERNAME: Joi.string().required(),
            DB_PASSWORD: Joi.string().required(),
            DB_NAME: Joi.string().required(),
            PRIVATE_KEY: Joi.string().required(), //token 지정을 위해 사용하는 privateKey
          }),
        }),
        TypeOrmModule.forRoot({
          type: 'postgres',
          host: process.env.DB_HOST,
          port: +process.env.DB_PORT,
          username: process.env.DB_USERNAME,
          password: process.env.DB_PASSWORD,  //localhost인 경우엔 안써도 됨
          database: process.env.DB_NAME,
          synchronize: process.env.NODE_ENV ==="prod", //production이 아니면 true로
          logging: process.env.NODE_ENV ==="prod", //DB에 돌아가는 모든 로그 확인
          entities:[User],
        }),
        GraphQLModule.forRoot({ //dynamic module로 설정이 존재
          autoSchemaFile: true,  //root module 설정
        }),
        UsersModule,
        CommonModule,
        JwtModule.forRoot({
          privateKey: process.env.PRIVATE_KEY,
        }), //JwtModule처럼 static module은 어떠한 설정도 적용되어 있지않음
      ],
      controllers: [],
      providers: [],
    })
    export class AppModule implements NestModule{
      configure(consumer: MiddlewareConsumer){
        consumer
          .apply(JwtMiddleware)
          .forRoutes({path:"/graphql", method: RequestMethod.ALL});
      }
    }
      
      
    
  • main.ts

    import { ValidationPipe } from '@nestjs/common';
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
      
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      app.useGlobalPipes(
        new ValidationPipe() 
      );
      await app.listen(3000);
    }
    bootstrap();
      
    
  • jwt.middleware.ts

    import { NestMiddleware } from "@nestjs/common";
    import { NextFunction, Request, Response } from "express";
      
    export class JwtMiddleware implements NestMiddleware{
        use(req:Request, res: Response, next:NextFunction){
            if("x-jwt" in req.headers){
                console.log(req.headers["x-jwt"]);
            }
            next();
        }
    } //NestMiddleware로 부터 상속 -> interface처럼 행동
      
    

    image

    => token 추출

  • jwt.middleware.ts

    -> verify로 올바른 토큰인지 확인 & 암호해독

    import { Injectable, NestMiddleware } from "@nestjs/common";
    import { NextFunction, Request, Response } from "express";
    import { JwtService } from "./jwt.service";
      
    @Injectable()
    export class JwtMiddleware implements NestMiddleware{
        constructor(private readonly jwtService: JwtService){} //injectable일때만 inject할 수 있음
        use(req:Request, res: Response, next:NextFunction){
            if("x-jwt" in req.headers){
                const token =(req.headers["x-jwt"]);
                const decoded = this.jwtService.verify(token.toString());
                if(typeof decoded ==="object" && decoded.hasOwnProperty('id')){
                    console.log(decoded['id']);
                }
            }
            next();
        }
    } //NestMiddleware로 부터 상속 -> interface처럼 행동
      
    

    => id 출력

    image

  • users.service.ts

    import { Injectable, Query, RequestMethod } from "@nestjs/common";
    import { InjectRepository } from "@nestjs/typeorm";
    import { Repository } from "typeorm";
    import { CreateAccountInput } from "./dtos/create-account.dto";
    import { LoginInput } from "./dtos/login.dto";
    import { User } from "./entities/user.entity";
    import * as jwt from 'jsonwebtoken';
    import { ConfigService } from "@nestjs/config";
    import { JwtService } from "src/jwt/jwt.service";
      
    @Injectable()
    export class UsersService{
        constructor(
            @InjectRepository(User) private readonly users: Repository <User>,//User entity의 InjectRepository 불러오기 & type이 repository이고 repository type은 user enitity
            private readonly jwtService: JwtService, //nestjs는 클래스 타입만 보고 import 알아서 찾아줌
            ){}
      
        async createAccount({
            email,
            password, 
            role,
        }: CreateAccountInput): Promise <{ok: boolean, error? :string }>{
            //check new user(that email does not exist)
            try {
                const exists = await this.users.findOne({email}) //findOne = 주어진 condition(환경)과 일치하는 첫 번째 entity 찾기
                if(exists){
                    //make error
                    return {ok: false, error: 'There is a uwer with that email already'}; //boolean =false, error ="there~"
                }
                await this.users.save(this.users.create({email, password, role})); //없다면 새로운 계정 create & save
                return {ok: true};
            } catch(e){
                return {ok: false, error: "Couldn't create account"};
            }
            // create user & hash the password
              
        }
      
        async login({
            email, 
            password
        }: LoginInput): Promise<{ok: boolean; error?:string, token?: string}> {
      
            //make a JWT and give it to the user
            try{
                // find the user with the email
                const user = await this.users.findOne({ email });
                if(!user){ //user가 존재하지 않는다면
                    return {
                        ok:false,
                        error: 'User not found',
                    }
                }
                //check if the password is correct
                //비밀번호를 hash 한후 데이터베이스에 있는 hash된 비번과 같은지 확인
                const passwordCorrect = await user.checkPassword(password); //여기의 user와 위의 const user와는 다름.. 전자는 entity 
                if (!passwordCorrect){
                    return{
                        ok:false,
                        error:"Wrong password",
                    };
                }
                const token = this.jwtService.sign(user.id);//이 module을 내 백엔드만으로 특정함
                return{
                    ok:true,
                    token,
                }
            }catch(error){
                return{
                    ok: false,
                    error,
                };
            }
              
        }
        async findById(id:number): Promise<User>{
            return this.users.findOne({id});
      
        }
    }
    
  • jwtmiddleware.ts

    import { Injectable, NestMiddleware } from "@nestjs/common";
    import { NextFunction, Request, Response } from "express";
    import { UsersService } from "src/users/users.service";
    import { JwtService } from "./jwt.service";
      
    @Injectable()
    export class JwtMiddleware implements NestMiddleware{
        constructor(
            private readonly jwtService: JwtService,
            private readonly userService: UsersService
            ){} //injectable일때만 inject할 수 있음
        use(req:Request, res: Response, next:NextFunction){
            if("x-jwt" in req.headers){
                const token =(req.headers["x-jwt"]);
                const decoded = this.jwtService.verify(token.toString());
                if(typeof decoded ==="object" && decoded.hasOwnProperty('id')){
                    console.log(decoded['id']);
                }
            }
            next();
        }
    } //NestMiddleware로 부터 상속 -> interface처럼 행동
      
    
  • user.module.ts

    -> UsersService를 export 해주기

    import { Module } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';
    import { TypeOrmModule } from '@nestjs/typeorm';
    import { JwtService } from 'src/jwt/jwt.service';
    import { User } from './entities/user.entity';
    import { UsersResolver } from './users.resolver';
    import { UsersService } from './users.service';
      
    @Module({
        imports: [TypeOrmModule.forFeature([User])], //service는 repository 필요로 하기 떄문에 // ConfigService는 token 생성할 때 import해주려고 users.module안에 configService 추가
        providers:[UsersResolver, UsersService],
        exports:[UsersService]
    })
    export class UsersModule {}
      
    
  • jwt.middleware.ts

    import { Injectable, NestMiddleware } from "@nestjs/common";
    import { NextFunction, Request, Response } from "express";
    import { UsersService } from "src/users/users.service";
    import { JwtService } from "./jwt.service";
      
    @Injectable()
    export class JwtMiddleware implements NestMiddleware{
        constructor(
            private readonly jwtService: JwtService,
            private readonly userService: UsersService
            ){} //injectable일때만 inject할 수 있음
        async use(req:Request, res: Response, next:NextFunction){
            if("x-jwt" in req.headers){
                const token =(req.headers["x-jwt"]);
                const decoded = this.jwtService.verify(token.toString());
                if(typeof decoded ==="object" && decoded.hasOwnProperty('id')){
                    try{
                        const user = await this.userService.findById(decoded['id']);
                        req['user'] = user;
                    }
                    catch(e){
      
                    }
                }
            }
            next();
        }
    } //NestMiddleware로 부터 상속 -> interface처럼 행동
      
    

    -> user 찾기

    image

2.7

-> graphql로 request 공유하기 => HTTP request를 graphql resolver에 전달

jwt.middleware의 ruser request는 모든 resolver에서 공유 가능

  • app.module.ts

    import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { GraphQLModule } from '@nestjs/graphql';
    import {TypeOrmModule} from "@nestjs/typeorm";
    import * as Joi from 'joi'; //타입스크립트나 NestJS로 되어있지 않을때 패키지 import
    import { UsersModule } from './users/users.module';
    import { CommonModule } from './common/common.module';
    import { User } from './users/entities/user.entity';
    import { JwtModule } from './jwt/jwt.module';
    import { JwtMiddleware } from './jwt/jwt.middleware';
      
      
      
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          envFilePath: process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.test",
          ignoreEnvFile: process.env.NODE_ENV ==="prod", //production환경일땐 ConfigModule이 환경변수 파일 
          validationSchema: Joi.object({
            NODE_ENV: Joi.string()
              .valid('dev', 'prod')
              .required(), // 환경변수 유효성 검사
            DB_HOST: Joi.string().required(),
            DB_PORT: Joi.string().required(),
            DB_USERNAME: Joi.string().required(),
            DB_PASSWORD: Joi.string().required(),
            DB_NAME: Joi.string().required(),
            PRIVATE_KEY: Joi.string().required(), //token 지정을 위해 사용하는 privateKey
          }),
        }),
        TypeOrmModule.forRoot({
          type: 'postgres',
          host: process.env.DB_HOST,
          port: +process.env.DB_PORT,
          username: process.env.DB_USERNAME,
          password: process.env.DB_PASSWORD,  //localhost인 경우엔 안써도 됨
          database: process.env.DB_NAME,
          synchronize: process.env.NODE_ENV ==="prod", //production이 아니면 true로
          logging: process.env.NODE_ENV ==="prod", //DB에 돌아가는 모든 로그 확인
          entities:[User],
        }),
        GraphQLModule.forRoot({ //dynamic module로 설정이 존재
          autoSchemaFile: true,  //root module 설정
          context: ({req}) => ({user: req["user"]}), //graphql resolver의 context를 통해 공유
        }),
        UsersModule,
        CommonModule,
        JwtModule.forRoot({
          privateKey: process.env.PRIVATE_KEY,
        }), //JwtModule처럼 static module은 어떠한 설정도 적용되어 있지않음
      ],
      controllers: [],
      providers: [],
    })
    export class AppModule implements NestModule{
      configure(consumer: MiddlewareConsumer){
        consumer
          .apply(JwtMiddleware)
          .forRoutes({path:"/graphql", method: RequestMethod.ALL});
      }
    }
      
      
    

    -> graphql resolver의 context를 통해 공유

  • app.module.ts

    import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { GraphQLModule } from '@nestjs/graphql';
    import {TypeOrmModule} from "@nestjs/typeorm";
    import * as Joi from 'joi'; //타입스크립트나 NestJS로 되어있지 않을때 패키지 import
    import { UsersModule } from './users/users.module';
    import { CommonModule } from './common/common.module';
    import { User } from './users/entities/user.entity';
    import { JwtModule } from './jwt/jwt.module';
    import { JwtMiddleware } from './jwt/jwt.middleware';
      
      
      
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          envFilePath: process.env.NODE_ENV === "dev" ? ".env.dev" : ".env.test",
          ignoreEnvFile: process.env.NODE_ENV ==="prod", //production환경일땐 ConfigModule이 환경변수 파일 
          validationSchema: Joi.object({
            NODE_ENV: Joi.string()
              .valid('dev', 'prod')
              .required(), // 환경변수 유효성 검사
            DB_HOST: Joi.string().required(),
            DB_PORT: Joi.string().required(),
            DB_USERNAME: Joi.string().required(),
            DB_PASSWORD: Joi.string().required(),
            DB_NAME: Joi.string().required(),
            PRIVATE_KEY: Joi.string().required(), //token 지정을 위해 사용하는 privateKey
          }),
        }),
        TypeOrmModule.forRoot({
          type: 'postgres',
          host: process.env.DB_HOST,
          port: +process.env.DB_PORT,
          username: process.env.DB_USERNAME,
          password: process.env.DB_PASSWORD,  //localhost인 경우엔 안써도 됨
          database: process.env.DB_NAME,
          synchronize: process.env.NODE_ENV ==="prod", //production이 아니면 true로
          logging: process.env.NODE_ENV ==="prod", //DB에 돌아가는 모든 로그 확인
          entities:[User],
        }),
        GraphQLModule.forRoot({ //dynamic module로 설정이 존재
          autoSchemaFile: true,  //root module 설정
          context: ({req}) => ({user: req["user"]}), //graphql resolver의 context를 통해 공유
        }),
        UsersModule,
        CommonModule,
        JwtModule.forRoot({
          privateKey: process.env.PRIVATE_KEY,
        }), //JwtModule처럼 static module은 어떠한 설정도 적용되어 있지않음
      ],
      controllers: [],
      providers: [],
    })
    export class AppModule implements NestModule{
      configure(consumer: MiddlewareConsumer){
        consumer
          .apply(JwtMiddleware)
          .forRoutes({path:"/graphql", method: RequestMethod.ALL});
      }
    }
      
      
    

    => 터미널에 request정보 image

  1. token 보내기
  2. token이 request로 보내짐
  3. JwtMiddleware가 받음 -> JwtMiddleware가 token 찾고 request user에 넣어줌
  4. request가 GraphQLModule의 context로 들어감 (context는 매 request마다 호출)(-> context함수 호출시 HTTP request property 주어짐)
  • users.resolver.ts

        @Query(returns => User)
        me(@Context() context){
            if(!context.user){
                return; //에러 반환
            }
            else{
                return context.user;
            }
        }//middleware 구현 //로그인한 사람이 누구인지 반환
    }
    

2.8

-> guard로 request 멈추기

  • auth.guard.ts

    -> guard로 request를 다음단계로 진행할 지 결정

    import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
      
    @Injectable()
    export class AuthGuard implements CanActivate{
        canActivate(context: ExecutionContext){
            console.log(context);
            return false; //request 막기
        }
    }// CanActivate는 true를 return 하면 request 진행
    
  • users.resolver.ts

        @Query(returns => User)
        @UseGuards(AuthGuard)
        me()
        {}//middleware 구현 //로그인한 사람이 누구인지 반환
    

image

  • auth.guard.ts

    import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
    import { GqlExecutionContext } from "@nestjs/graphql";
      
    @Injectable()
    export class AuthGuard implements CanActivate{
        canActivate(context: ExecutionContext){
            const gqlContext = GqlExecutionContext.create(context).getContext();
            const user = gqlContext['user'];
            if(!user){
                return false;   
            }
            return true; //request 막기
        }
    }// CanActivate는 true를 return 하면 request 진행
    

=>guard로 endpoint 보호

  • authentication: 누가 자원을 요청하는 지 확인(permission 확인)

2.9

-> login 안됐으면 request 멈추기

  • auth.decorator

    -> 누구인지 확인하는 decorator 만들기

    import { createParamDecorator, ExecutionContext } from "@nestjs/common"
    import { GqlExecutionContext } from "@nestjs/graphql";
      
    export const AuthUser = createParamDecorator(
        (data:unknown, context: ExecutionContext) => {
            const gqlContext = GqlExecutionContext.create(context).getContext();
            const user = gqlContext['user'];
            return user;
        }
    )
    
  • users.resolver.ts

        @Query(returns => User)
        @UseGuards(AuthGuard)
        me(@AuthUser() authUser: User){
            return authUser;
        }//middleware 구현 //로그인한 사람이 누구인지 반환
    }
    

2.10

authentication 작동 순서

  1. header에 token 보내기
  2. header는 http기술 쓰기 때문에 middleware 만듦
  3. middleware는 header에 jwtService.verify() 사용
  4. id찾으면 userService로 해당 id를 가진 user 찾기
  5. user찾으면 request object에 붙여서 보냄
  6. request object가 graphql context안으로 들어감
  7. guard 가 graphql context를 찾음
  8. user 유무에 따라 bool값 반환
  9. guard에 의해 request가 authorize되면 resolver에 decorater 필요
  10. decorator는 graphql context에서 찾은 user를 찾아 return

2.11

  • users.resolver.ts

    -> user의 profile을 볼 수 있는 query 추가

        @UseGuards(AuthGuard)
        @Query(returns => User)
        user(id){ //user id가져오기
            return id;        
        }
    
  • user-profile.dto.ts

    import { ArgsType, Field } from "@nestjs/graphql";
      
    @ArgsType()
    export class UserProfileInput{
        @Field(type=>Number)
        userId: number;
    }
    

    image

image

  • user-profile.dto.ts

    -> 나중에 MutationOutput을 CoreOutput으로 변경하기

    import { ArgsType, Field, ObjectType } from "@nestjs/graphql";
    import { MutationOutput } from "src/common/dtos/output.dto";
    import { User } from "../entities/user.entity";
      
    @ArgsType()
    export class UserProfileInput{
        @Field(type=>Number)
        userId: number;
    }
      
    @ObjectType()
    export class UserProfileOutput extends MutationOutput{
        @Field(type => User, {nullable: true})
        user?: User; //users.resolver.ts에서
    }
    
  • users.resolver.ts

        @UseGuards(AuthGuard)
        @Query(returns => UserProfileOutput)
        async userProfile(@Args()userProfileInput: UserProfileInput): Promise<UserProfileOutput>{ //user id가져오기
            try {
                const user = await this.usersService.findById(userProfileInput.userId)
                if (!user){
                    throw Error(); //user 찾지 못하면 error로
                }
                return{
                    ok:true,
                    user,
                };
            }catch(e){
                return{
                    error:"User Not Found",
                    ok: false,
      
                }
            }
            ;     
        }
    

    image

2.12

  • edit-profile.ts

    -> Mutation의 dto만들기

    import { InputType, ObjectType, PartialType, PickType } from "@nestjs/graphql";
    import { CoreOutput } from "src/common/dtos/output.dto";
    import { User } from "../entities/user.entity";
      
    @ObjectType()
    export class EditProfileOutput extends CoreOutput {}
      
    @InputType()
    export class EditProfileInput extends PartialType(
        PickType(User, ["email","password"]),//user에서 email과 password가지고 class 생성 & PartialType으로 optional하게 
    ){}
    
  • users.service.ts

        async editProfile(userId: number, {email, password}: EditProfileInput){
            return this.users.update(userId, {email, password}) //db에 entity 존재 유무 체크 안함
        }
    
  • users.resolver.ts

        @UseGuards(AuthGuard)
        @Mutation(returns=> EditProfileOutput)
        async editProfile(@AuthUser() authUser: User, @Args('input') editProfileInput: EditProfileInput,
        ): Promise<EditProfileOutput>{
            try{
                await this.usersService.editProfile(authUser.id, editProfileInput);
            }catch(error)
            {
                return  {
                    ok: false,
                    error
                }
            }
      
        }
    

    -> 이것처럼 하면 아이디나 비번 둘 중 하나만 고치면 에러가남..

  • user.service.ts

        async editProfile(userId: number, editProfileInput: EditProfileInput){
            return this.users.update(userId, {...editProfileInput}) //db에 entity 존재 유무 체크 안함
        }
    

    -> ediprofileInput으로 하나만 전송해도 되도록

  • users.resolver.ts

        @UseGuards(AuthGuard)
        @Mutation(returns=> EditProfileOutput)
        async editProfile(@AuthUser() authUser: User, @Args('input') editProfileInput: EditProfileInput,
        ): Promise<EditProfileOutput>{
            try{
                await this.usersService.editProfile(authUser.id, editProfileInput);
                return {
                    ok:true,
                }
            }catch(error)
            {
                return  {
                    ok: false,
                    error
                }
            }
      
        }
    

image

image

=> 근데 패스워드는 edit하면 해쉬가 안됨

  • user.entity.ts

    -> BeforUpdate decorator 사용

      
        @BeforeInsert()//DB에 저장하기 전에 password hash해주기
        @BeforeUpdate()//password 저장하기 전에 hash하도록
        async hashPassword(): Promise<void> {
            try{
            this.password = await bcrypt.hash(this.password, 10); //hash round는 10으로
            } catch(e){
                console.log(e);
                throw new InternalServerErrorException();
            }
        }
    
  • user.service.ts

    -> update는 dv에 query만 보내지 entity를 update하진 않음 -> BeforeUpdate못함

    => save쓰기

        async editProfile(userId: number, {email, password}: EditProfileInput){
            const user = await this.users.findOne(userId);
            if(email){
                user.email = email
            }
            if(password){
                user.password = password
            }
            return this.users.save(user) //db에 entity 존재 유무 체크 안함
    

    image

  • delete도 만들자