Nest.js

[Nest.js] 회원가입과 로그인 구현 2

Hoo_Dev 2023. 1. 27. 15:48

유저 이름에 유니크한 값 주기(중복 금지)

두가지 방법이 있다.

  1. 레포지토리에서 findOne 메소드를 이용해서 이미 같은 유저 이름을 가진 아이디가 있는지 확인하고, 없다면 데이터를 저장하는 방법. 이 방법은 DB처리를 두 번 해줘야 함.
  2. DB레벨에서 만약 같은 이름을 가진 유저가 있따면 에러를 던져줌

2번째 방법으로 구현.

 

user.entity.ts 파일로 가서

import {
  BaseEntity,
  Column,
  Entity,
  PrimaryGeneratedColumn,
  Unique,
} from 'typeorm';

@Entity()
@Unique(['username'])
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  password: string;
}

@Unique([’username’]) 을 사용하여 유니크한 값을 갖게 한다.

위와 같이 처리 할 때 서버는 이미 가지고 있는 아이디를 또 가입하게 된다면 500에러를 띄우고 메세지로는 “Internal server error” 메세지를 날려준다.

하지만 좀 더 명확한 메세지를 보내는 것이 더 좋을 것 같다. 그렇다면 어떻게?

try catch 구문을 통해 에러를 확실하게 명시해준다.

import {
  ConflictException,
  InternalServerErrorException,
} from '@nestjs/common';
import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator';
import { DataSource, Repository } from 'typeorm';
import { AuthCredentialDto } from './dto/auth-credential.dto';
import { User } from './user.entity';

@Injectable()
export class UserRepository extends Repository<User> {
  constructor(private dataSource: DataSource) {
    super(User, dataSource.createEntityManager());
  }

  async createUser(authCredentialDto: AuthCredentialDto): Promise<void> {
    const { username, password } = authCredentialDto;
    const user = this.create({ username, password });
    try {
      await this.save(user);
    } catch (error) {
      if (error.code === '23505') {
        throw new ConflictException('Existing username');
      } else {
        throw new InternalServerErrorException();
      }
    }
  }
}

위에 나오는 error.code 는 중복 된 아이디를 작성하였을 때 발생하는 코드로 조건문을 통해 구분해준다.

비밀번호 암호화 하기

 

bcryptjs 모듈 사용하기

npm install bcryptjs --save

import * as bcrypt from 'bcryptjs'

 

repository에서 작업

import {
  ConflictException,
  InternalServerErrorException,
} from '@nestjs/common';
import { Injectable } from '@nestjs/common/decorators/core/injectable.decorator';
import { DataSource, Repository } from 'typeorm';
import { AuthCredentialDto } from './dto/auth-credential.dto';
import { User } from './user.entity';
import * as bcrypt from 'bcryptjs';

@Injectable()
export class UserRepository extends Repository<User> {
  constructor(private dataSource: DataSource) {
    super(User, dataSource.createEntityManager());
  }

  async createUser(authCredentialDto: AuthCredentialDto): Promise<void> {
    const { username, password } = authCredentialDto;

		// 랜덤한 salt 값을 포함한 해쉬값을 통해 암호화를 진행한다.
    const salt = await bcrypt.genSalt();
    const hashedPassword = await bcrypt.hash(password, salt);

    const user = this.create({ username, password: hashedPassword });

    try {
      await this.save(user);
    } catch (error) {
      if (error.code === '23505') {
        throw new ConflictException('Existing username');
      } else {
        throw new InternalServerErrorException();
      }
    }
  }
}

salt값을 새로 생성하며 해쉬함수에 패스워드와 함께 넣어주면 유니크한 해쉬함수가 저장된다.

 

ex)

id : GHGHgh

password : $2a$10$JQYn2m5zYOaybzYUMw8BteEb3TWEkFYILU8sPyw2JtPhLS37jZnPm

 

 

로그인 기능 구현

auth.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AuthCredentialDto } from './dto/auth-credential.dto';
import { User } from './user.entity';
import { UserRepository } from './user.repository';
import * as bcrypt from 'bcryptjs';
import { UnauthorizedException } from '@nestjs/common/exceptions';

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(UserRepository)
    private userRepository: UserRepository,
  ) {}

  async signUp(authCredentialDto: AuthCredentialDto): Promise<void> {
    return this.userRepository.createUser(authCredentialDto);
  }

  async signIn(authCredentialDto: AuthCredentialDto): Promise<String> {
    const { username, password } = authCredentialDto;
    const user = await this.userRepository.findOneBy({ username });
    if (user && (await bcrypt.compare(password, user.password))) {
      return 'login success';
    } else {
      throw new UnauthorizedException('login failed');
    }
  }
}

조건문을 통해 유저가 존재 한다면 bcrypt의 compare함수를 사용하여 입력한 패스워드와 해쉬화 된 비밀번호를 비교하여 로그인 처리를 해준다.

 

auth.controller.ts

import { Controller, Post, Body, ValidationPipe } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthCredentialDto } from './dto/auth-credential.dto';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Post('/signup')
  signUp(
    @Body(ValidationPipe) authCredentialDto: AuthCredentialDto,
  ): Promise<void> {
    return this.authService.signUp(authCredentialDto);
  }

  @Post('/signin')
  signIn(@Body(ValidationPipe) authCredentialDto: AuthCredentialDto) {
    return this.authService.signIn(authCredentialDto);
  }
}