저장을 습관화

NestJS - 회원 가입 API 개발시 패스워드 암호화하기, mongoose virtual field 사용하기 본문

공부/node.js

NestJS - 회원 가입 API 개발시 패스워드 암호화하기, mongoose virtual field 사용하기

ctrs 2023. 10. 2. 22:07

준비물 - bcrypt, @types/bcrypt

$ npm install bcrypt
$ npm install -D @types/bcrypt

 

서비스 파일

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { CatRequestDto } from './dto/cats.request.dto';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Cat } from './cats.schema';
import * as bcrypt from 'bcrypt';

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) {}

  async signUp(body: CatRequestDto) {
    const { email, name, password } = body;
    const isCatExist = await this.catModel.exists({ email });

    // 이미 존재하는 이메일일 경우 에러처리
    if (isCatExist) {
      throw new UnauthorizedException('해당하는 고양이는 이미 존재합니다.');
      // 위 방법과 아래 방법은 동일한 기능을 한다.
      //   throw new HttpException('해당하는 고양이는 이미 존재합니다.', 403);
    }

    // 패스워드 암호화
    const hashedPassword = await bcrypt.hash(password, 10);

    // DB에 저장
    const cat = await this.catModel.create({
      email: email,
      name: name,
      password: hashedPassword,
    });

    return cat;
  }
}

 

스키마 파일

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { SchemaOptions, Document } from 'mongoose';

const options: SchemaOptions = {
  timestamps: true, // DB에 데이터가 만들어질때 타임스탬프를 찍는다
};

@Schema(options)
export class Cat extends Document {
  @Prop({
    required: true,
    unique: true,
  })
  @IsEmail()
  @IsNotEmpty()
  email: string;

  @Prop({
    required: true,
  })
  @IsString()
  @IsNotEmpty()
  name: string;

  @Prop({
    required: true,
  })
  @IsString()
  @IsNotEmpty()
  password: string;

  @Prop()
  @IsString()
  imgUrl: string;

  readonly readOnlyData: { id: string; email: string; name: string };
}

export const CatSchema = SchemaFactory.createForClass(Cat);

// postman 등에서 회원가입 API를 사용하였을때
// 암호화된 패스워드가 노출되는 것을 막는 방법
// mongoose에서 지원해주는 virtual field를 사용한다.
// virtual field는 실제로 DB에 저장되는 field는 아니지만
// 개발자가 비즈니스 로직에서 사용할 수 있는 field이다.

// 스키마에 virtual 메소드를 사용한다
// readOnlyData: 클라이언트에게 보여줄 데이터만 가상으로 필터링해서 나간다
CatSchema.virtual('readOnlyData').get(function (this: Cat) {
  return {
    id: this.id,
    email: this.email,
    name: this.name,
  };
});

Virtual Filed는 실제로 DB에 저장되는 filed 내용은 아니지만, 개발자가 비즈니스 로직에 사용할 수 있는 filed이다.

 

사용 전

"data": {
        "email": "fourthTest@email.com",
        "name": "fourth",
        "password": "$2b$10$0fWqIwT6Yazo54tPR0zxP.3BQV7oN1lx2YaoJKVd//lwngWMwVJcS",
        "_id": "651abd06a76166673177cdfb",
        "createdAt": "2023-10-02T12:52:22.805Z",
        "updatedAt": "2023-10-02T12:52:22.805Z",
        "__v": 0
    }

body를 통해 전송한 데이터와 함께 DB에 저장된 데이터가 모두 보이게 된다.

패스워드가 암호화 된 상태이지만, 이 조차도 노출되지 않도록 할 수 있다.

 

 

virtual field 사용 후

서비스 파일 수정

// 생략

@Injectable()
export class CatsService {
  constructor(@InjectModel(Cat.name) private readonly catModel: Model<Cat>) {}

  async signUp(body: CatRequestDto) {
    // 생략

    return cat.readOnlyData; // 서비스의 반환값 cat에서 
    // 스키마에서 설정했던 virtual filed 'readOnlyData'을 골라 반환하도록 한다
  }
}

 

"data": {
        "id": "651ac06bd1d8997139ea674b",
        "email": "fifthTest@email.com",
        "name": "fifth"
    }

virtual field에서 지정한 id, email, name만이 반환되어 사용자에게 노출되는 모습을 확인할 수 있다.