ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • nestJS password security
    Project using Nest.js/E-commerce App 2021. 9. 3. 16:17

    개요

       Server는 Client 측에서 보내는 모든 data를 신뢰해서는 안 된다. Client에서도 filtering을 하겠지만 hacker가 마음을 먹으면 충분히 Client filter를 우회해서 값을 주입할 수 있다. 그렇기 때문에 server에서도 password 보안 정책을 해줘야만 한다.

    user.entity.ts

    import {
      Field,
      InputType,
      ObjectType,
    } from '@nestjs/graphql';
    import {
      IsEmail,
      IsString,
      MinLength,
    } from 'class-validator';
    import { BeforeInsert, BeforeUpdate, Column, Entity } from 'typeorm';
    
    @InputType('UserInputType', { isAbstract: true })
    @ObjectType()
    @Entity()
    export class User extends CoreEntity {
      @Column({ unique: true })
      @Field((type) => String)
      @IsEmail()
      email: string;
    
      @Column()
      @Field((type) => String)
      @MinLength(8)
      password: string;
      
      @BeforeInsert()
      @BeforeUpdate()
      async hashPassword(): Promise<void> {
        if (this.password) {
          try {
            this.password = await bcrypt.hash(this.password, 10);
          } catch (e) {
            console.error(e);
            throw new InternalServerErrorException();
          }
        }
      }
    
      async checkPassowrd(password: string): Promise<boolean> {
        try {
          const ok = await bcrypt.compare(password, this.password);
          return ok;
        } catch (error) {
          console.error(error);
          throw new InternalServerErrorException();
        }
      }
    }

      class-validator module을 이용해서 유효성 검사를 해준다. @MinLength(8)을 이용해서 비밀번호는 최소 8자리 이상이어야 한다.

      @BeforeInsert와 @BeforeUpdate를 이용해서 계정을 생성하거나 비밀번호를 변경할 경우 plain text로 저장하지 않고 hash 값을 저장한다. 

    create-account.dto.ts

    import { Field, InputType, ObjectType, PickType } from '@nestjs/graphql';
    import { MinLength } from 'class-validator';
    
    import { CoreOutput } from 'src/common/dtos/output.dto';
    import { User } from '../entities/user.entity';
    
    @InputType()
    export class CreateAccountInput extends PickType(User, [
      'email',
      'password',
    ]) {
      @Field((type) => String)
      @MinLength(8)
      verifyPassword: string;
    }
    
    @ObjectType()
    export class CreateAccountOutput extends CoreOutput {}

       DTO에도 @MinLength(8)를 이용해서 verifyPassword도 8자리 이상 받을 수 있도록 한다.

    users.service.ts

      async createAccount({
        email,
        password,
        verifyPassword,
      }: CreateAccountInput): Promise<CreateAccountOutput> {
        try {
          const exists = await this.users.findOne({ email });
          if (exists) {
            return { ok: false, error: 'There is a user with that email already' };
          }
    
          if (password !== verifyPassword) {
            return {
              ok: false,
              error: 'Password does not match',
            };
          }
    
          const regex = new RegExp(
            /(?=.*[!@#$%^&\*\(\)_\+\-=\[\]\{\};\':\"\\\|,\.<>\/\?]+)(?=.*[a-zA-Z]+)(?=.*\d+)/,
          );
    
          const passwordTestPass = regex.test(password);
    
          if (!passwordTestPass) {
            return {
              ok: false,
              error: 'Password must contain special character, string and number',
            };
          }
    
          const user = await this.users.save(
            this.users.create({ email, password }),
          );
    
          return { ok: true };
        } catch (e) {
          return { ok: false, error: "Couldn't create account" };
        }
      }

       사용자가 설정할 비밀번호와 확인 비밀번호(verifyPassword)가 일치하는지 확인한다. 그리고 비밀번호에 문자, 숫자, 특수문자가 들어있는지 확인하는 Regex를 사용한다. 이를 모두 통과할 경우 계정을 생성한다. change-password 같은 경우에서도 똑같고 추가로 current password와 new password가 다른지 확인해야 한다.

    Github link

    Set password security for create-account : https://github.com/zpskek/houpang-backend-v1/commit/b9010278adf64572df81e7a42998f3b1259cfc13

    Set security password for change-password service : https://github.com/zpskek/houpang-backend-v1/commit/30674b50b8499cb1b017095d3b97dbe84826189f

     

    댓글

Designed by Tistory.