All files / src/user user.service.ts

100% Statements 36/36
100% Branches 5/5
100% Functions 4/4
100% Lines 34/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 1152x               2x 2x 2x 2x   2x     2x     2x   9x 9x       3x   3x       2x 1x           1x           1x   2x 1x   1x         4x                 4x         4x 1x           3x   3x 1x             2x 2x           1x         1x       2x         2x 1x     1x      
import {
  Injectable,
  ConflictException,
  UnauthorizedException,
  HttpException,
  InternalServerErrorException,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { JwtService } from '@nestjs/jwt';
import { LoginDto } from './dto/login.dto';
import { GetUserOutputDto } from './dto/get-user.dto';
import { ErrorCode } from '../common/errors/error-code.enum';
 
@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private readonly userRepo: Repository<User>,
    private readonly jwt: JwtService,
  ) {}
 
  async create(dto: CreateUserDto): Promise<User> {
    try {
      // 중복 이메일 검증
      const exists = await this.userRepo.findOne({
        where: { email: dto.email },
      });
 
      if (exists) {
        throw new ConflictException({
          code: ErrorCode.USER_EMAIL_DUPLICATED,
          message: '이미 가입된 이메일입니다.',
        });
      }
 
      const user = this.userRepo.create({
        email: dto.email,
        password: dto.password,
        role: dto.role,
      });
 
      return this.userRepo.save(user);
    } catch (error) {
      if (error instanceof HttpException) {
        throw error;
      }
      throw new Error('회원가입 도중 알 수 없는 에러가 발생하였습니다.');
    }
  }
 
  async login(dto: LoginDto): Promise<{ accessToken: string }> {
    const { email, password } = dto;
 
    // 유저의 아이디가 존재하는지와 비밀번호가 일치하지 않는지 명확한 답을 내려주지 않는 이유
    // 나의 생각 : 정보 탈취자가 아이디와 비밀번호를 탈취를 하겠다고 마음을 먹으면 모두 탈취해 갈 수 있는데
    // 이로 인해 사용자 허들만 높아지는 것이 아닌가?
    // 이에 대한 답을 찾아본 결과 완벽한 보안을 설정하기 위함이 아니라 보안 비용을 높이기 위함이다.
    // 모든 정보 탈취자는 전문 공격자가 아니며 대부분의 공격자는 이메일 존재 수집, 무작위 대입 등을 통해 정보를 얻으려고 함
    // 이 때 이메일 리스트를 통해 존재하는 이메일만을 수집할 수 있으며 이 자체가 정보 탈취라 볼 수 있음
    // 이것을 User Enumeration공격이라고 함.
    const user = await this.userRepo.findOne({
      where: { email },
      select: ['id', 'email', 'password', 'role'],
    });
 
    if (!user) {
      throw new UnauthorizedException({
        code: ErrorCode.AUTH_INVALID_CREDENTIALS,
        message: '이메일 또는 비밀번호가 올바르지 않습니다.',
      });
    }
 
    const passwordConfirm = await bcrypt.compare(password, user.password);
 
    if (!passwordConfirm) {
      throw new UnauthorizedException({
        code: ErrorCode.AUTH_INVALID_CREDENTIALS,
        message: '이메일 또는 비밀번호가 올바르지 않습니다.',
      });
    }
 
    let accessToken;
    try {
      accessToken = await this.jwt.signAsync({
        sub: user.id,
        email: user.email,
        role: user.role,
      });
    } catch (error) {
      throw new InternalServerErrorException(
        'JWT 에러 : 환경설정 및 payload값을 확인해주세요.',
      );
    }
 
    return { accessToken };
  }
 
  async getUser(userId: number): Promise<GetUserOutputDto> {
    const user = await this.userRepo.findOne({
      where: { id: userId },
      relations: { subscriptions: true, payments: true },
    });
 
    if (!user) {
      throw new NotFoundException('존재하지 않는 사용자입니다.');
    }
 
    return user;
  }
}