[uber-eats] email verification - 1

한재창·2023년 6월 27일
0
post-thumbnail

email verification

One-to-one relations (1:1관계)

일대일 관계는 A가 B의 인스턴스를 하나만 포함하고 B가 A의 인스턴스를 하나만 포함하는 관계입니다. 예를 들어 사용자 및 프로필 엔터티를 보면, 사용자는 하나의 프로필만 가질 수 있으며, 프로필은 하나의 사용자만 가질 수 있습니다.

프로필에 @OneToOne을 추가하고 대상 관계 유형을 프로필로 지정했습니다.
또한 relation의 한쪽에만 설정해야 하는 @JoinColumn() 을 추가했습니다. (@JoinColumn()은 필수로 지정해야 함)
@JoinColumn()을 설정한 쪽의 테이블에는 해당되는 엔터티 테이블에 대한 relation id와 foreign keys가 포함됩니다.
@JoinColumn은 관계의 한 쪽, 즉 데이터베이스 테이블에 foreign key가 있어야 하는 쪽에만 설정해야 합니다.

요약: Verification을 통해 그 안에 User에 접근해서 User의 emailVerified를 false에서 true로 바꿀 것이기 때문에 Verification쪽에 @JoinColumn()을 추가하고 user를 통해 생성한 foreign key인 userId을 추가하도록 한 것이다.

@OneToOne(() => Profile)
@JoinColumn()
profile: Profile;

// 위와 같이 설정시 데이터베이스에는 profile에 대한 foreign key가 생김
profileId | int(11) | FOREIGN KEY

Verification Entity

  1. CoreEntity를 확장받아 Verification class를 만듭니다.
  2. 데이터베이스 테이블을 추가해주기 위해 app.module.ts 파일의 TypeOrmModule의 entities에 Vericication class를 추가합니다.

Creating Verifications

user가 생성될 때 users.service.ts 파일에서 verification을 추가합니다.

  1. users.module.ts 에서 Verification을 immport 합니다.

  2. users.service.ts 에서 UserService class에 Verification Repository를 주입합니다.

  3. verifications에 랜덤한 코드를 code를 만들어줍니다.

  4. 생성된 user를 받아 verifications에 생성후 저장합니다. 또는 업데이트 할 user를 받아 verifications에 생성후 저장합니다.

Verifying User

  1. users.resolver.ts 파일에 users.verified를 false에서 true로 업데이트 시켜줄 Mutation을 만듭니다.

  2. 1번의 Mutation에서 사용할 Input과 Output을 verify-email.dto.ts 파일에 만듭니다.

  3. users.service.ts 파일에서 resolver에 사용할 메서드를 만듭니다. 아래 코드는 verifications에서 넘겨준 code와 같은 값을 찾고 user의 정보도 함께 넘겨줍니다.

  4. verification을 찾았다면 user의 verified를 true로 업데이트 시켜주고 entity와 db에 업데이트 시켜줍니다.

  5. users.resolver.ts 파일에서 Mutation의 Input 값과 Output 값 및 에러처리를 설정합니다.

문제점 및 해결 1

this.users.save(verification.user) 해주는 시점에서 password가 hash된 값을 다시 hash 해버려서 비밀번호가 바뀌는 문제가 발생하였습니다. 따라서 relations['user'] 할 때 user.password는 제외하는 방법으로 해결합니다.

  1. user.entity.ts 파일에서 User 클래스의 password Column에 {select: false} 를 설정합니다.
    이렇게 되면 relations['user'] 할 때 user.password는 제외됩니다.

  2. password를 hash 해줄 때 this.password가 존재하지 않는다면 코드를 실행시키지 않습니다.

@Column({ select: false })
@Field((type) => String)
password: string;

@BeforeInsert()
  @BeforeUpdate()
  async hashPassword(): Promise<void> {
    if (this.password) {
      try {
        this.password = await bcrypt.hash(this.password, 10);
      } catch (e) {
        console.log(e);
        throw new InternalServerErrorException(); // service 파일 내부에서 catch 한다.
      }
    }
  }

문제점 및 해결 2

verification 할 때 비밀번호를 또 hash 하는 문제는 해결하였으나 모든 곳에서 password를 제외하고 user의 정보를 불러오게 되었습니다. password가 필요한 곳에서 select['password']를 해주는 방법으로 해결합니다.

  1. users.service.ts 파일에서 login 메서드에 user를 찾을 때 findOne에 {select : ['id', 'password']} 설정을 해줍니다.

  2. password가 맞는지 확인할 때 password 값이 필요하며, token 값을 확일할 때 id가 필요합니다.

  3. id는 {select: false} 해주지 않았지만 select에 'id'를 넣어준 이유는 findOne 함수에서 select를 명시적으로 해주면 그 값만을 가져오기 때문에 필요한 값들이라면 select해서 불러와야 합니다.

async login({
    email,
    password,
  }: LoginInput): Promise<{ ok: boolean; error?: string; token?: string }> {
    try {
      // 1. email을 가진 User을 찾아라
      const user = await this.users.findOne({
        where: { email },
        select: ['id', 'password'],
      });
      if (!user) {
        return {
          ok: false,
          error: 'User not found',
        };
      }

      // 2. password가 맞는지 확인해라
      const passwordCorrect = await user.checkPassword(password);
      if (!passwordCorrect) {
        return {
          ok: false,
          error: 'Wrong password',
        };
      }

      // 3. JWT를 만들고 User에게 주기
      const token = this.jwtService.sign(user.id);
      return {
        ok: true,
        token,
      };
    } catch (error) {
      return {
        ok: false,
        error,
      };
    }
  }
profile
취준 개발자

0개의 댓글