포시코딩

[Nest.js] 이메일 인증 시스템 (3). Redis 적용 본문

Node.js

[Nest.js] 이메일 인증 시스템 (3). Redis 적용

포시 2023. 2. 24. 15:59
728x90

개요

https://4sii.tistory.com/437

 

[Nest.js] 이메일 인증 시스템 (2). cache-manager

개요 https://4sii.tistory.com/436 [Nest.js] 이메일 인증 시스템 (1). nodemailer 개요 위와 같은 회원가입 폼에서 이메일 입력 후 인증번호 전송을 누르면 해당 이메일로 랜덤한 6자리 숫자의 인증번호가 보

4sii.tistory.com

이전 글에서 cache-manager를 통해 

이메일과 인증번호를 캐싱한 후 사용자가 이메일로 전달받은 인증번호를 입력 시 

확인하는 로직을 만들어봤는데

 

이번에는 이 과정에서 사용된 cache-manager를 redis를 이용하게 바꿔 볼 예정이다.

 

문제 발생

https://docs.nestjs.com/techniques/caching

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

처음에는 공식 문서를 보고 진행했었는데 

 

store 부분에 계속 빨간 밑줄이 뜨는 에러가 잡히고 있었고

 

확인해보면 공식문서에서 하란대로 했는데 정작 redisStore가 타입에 맞지 않는 상태라고 나오고 있었다.

여기서 막혀서 진짜 수많은 해결 방법을 찾아본 것 같다..

 

@ts-ignore 를 붙이라는 글을 봐서 붙였더니 빨간 밑줄은 사라졌지만

근본적으로 해결된 것이 아니라서 여전히 콘솔에선 에러를 내고 있었다.

 

해결 방법

그렇게 계속 헤매다가

 

 

https://github.com/dabroek/node-cache-manager-redis-store/issues/53

 

Incompatible with NestJS cache store · Issue #53 · dabroek/node-cache-manager-redis-store

V3.0.1 is incompatible with NestJS . Unable to register CacheModule using the RedisStore. "message": "Type 'typeof import(\"cache-test/node_modules/cache-manager-redis-store...

github.com

cache-manager-redis-store의 GitHub issue 탭에서

redisStore를 사용해 CacheModule을 register 할 수 없다는 글이 있었는데

 

어떤 링크에 대해 감사하다는 댓글을 몇몇 사람들이 달고 있는 걸 보았다.

바로 해결에 대한 힌트가 될거란걸 직감하고 들어가봤는데

 

이 사람도 비슷한 문제를 겪었고, 관련 패키지들의 버전을 낮춤으로서 해결했다고 나와있었다.

다른 사람도 해당 방법을 통해 효과를 본 것 같아 바로 진행했다.

 

먼저, 공식 문서에 나온대로 설치했던 redis, cache-manager-redis-store를 지우면서

낮은 버전으로 다시 설치하기위해 기존에 설치했었던 cache-manager도 같이 지운다.

npm uninstall redis
npm uninstall cache-manager-redis-store
npm uninstall cache-manager
npm uninstall @types/cache-manager

 

그리고 위에 나온 버전대로 설치를 진행한다.

npm i cache-manager@4.1.0 cache-manager-redis-store@2.0.0
npm i -D @types/cache-manager@4.0.1

 

그리고 다시 코드로 돌아와보니

host에 빨간 밑줄이 생겼는데, redis 패키지는 어차피 지웠으니 일단 RedisClientOptions 제네릭을 제외해보았다.

 

빨간 밑줄은 사라졌는데 서버 실행 시 콘솔에 아래와 같은 에러가 발생하고 있었다.

 

이에 대해서도 해결방법을 계속 찾아봤는데 

답을 못찾다가 문득, '아까 위에서 땡큐라고 했던 사람이 그냥 땡큐 했을까? 

자기 코드에 적용을 잘 했기 때문에 땡큐라고 한게 아닐까? 그럼 그 코드를 찾으면 되겠네' 라는 생각이 들어

그 사람의 GitHub를 들어가봤다.

 

댓글을 단게 저번주

댓글을 저번주에 달았고, 최근 repo들 중 마침 저번주에 마지막으로 update한 TypeScript로 이루어진게 있는걸 발견했다.

 

역시나 내가 원하던 코드를 발견할 수 있었고, 바로 해당 코드를 적용해보았다.

적용하는 과정에서 이 사람은 env 파일을 활용하기 위해 registerAsync를 쓰며 ConfigModule을 가져다 썼는데

 

나는 당장은 필요하지 않으니 이렇게 바꿔 적용해보았다.

 

app.module.ts

// ...다른 import 생략
import * as redisStore from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.register({  // redis 적용
      isGlobal: true,
      store: redisStore,
      host: 'localhost',
      port: 6379,
    }),
    BoardsModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

에러는 사라졌지만 원래 로직대로 이메일과 인증번호를 저장했을 때

redis에 저장되지 않고 있었는데

 

service에서 아래와 같이 바꿔주니 제대로 동작했다.

users.service.ts

// ...import 생략
@Injectable()
export class UsersService {
  constructor(
    private readonly emailService: EmailService,
    @Inject(CACHE_MANAGER) private readonly cacheManager: Cache
  ) {}

  async sendVerification(email: string) {
    const verifyToken = this.generateRandomNumber();
    await this.cacheManager.set(email, verifyToken, { ttl: 3600 });  // ttl 항목 추가
    await this.sendVerifyToken(email, verifyToken);
  }
  
  async sendVerifyToken(email: string, verifyToken: number) {
    await this.emailService.sendVerifyToken(email, verifyToken);
  }

  async verifyEmail(email:string, verifyToken: number) {
    const cache_verifyToken = await this.cacheManager.get<number>(email);  // get에 대해 제네릭 타입 추가
    if (_.isNil(cache_verifyToken)) {
      throw new NotFoundException('해당 메일로 전송된 인증번호가 없습니다.');
    } else if (cache_verifyToken !== verifyToken) {
      throw new UnauthorizedException('인증번호가 일치하지 않습니다.');
    } else {
      await this.cacheManager.del(email);
    }
  }

  private generateRandomNumber(): number {
    var minm = 100000;
    var maxm = 999999;
    return Math.floor(Math.random() * (maxm - minm + 1)) + minm;
  }
}

주석부분 참고. 

기존의 코드에서

await this.cacheManager.set(email, verifyToken);

이렇게가 아닌

await this.cacheManager.set(email, verifyToken, { ttl: 3600 });

이렇게 등록해야 redis에 제대로 저장되서 확인까지 가능했다.

 

해당 부분도 Ahmed Hdeawy씨의 코드를 참고해 작성되었는데

진짜.. 아흐메드 갓갓

https://github.com/AhmedHdeawy/zid-assignment/blob/master/src/app/products/products.service.ts

 

GitHub - AhmedHdeawy/zid-assignment

Contribute to AhmedHdeawy/zid-assignment development by creating an account on GitHub.

github.com

 

redis-cli에서 확인

인증번호를 보냈을 때

사용자의 수신 이메일 cchoseonghun@gmail.com 이 key로 등록되어 있고

get으로 조회해보니 인증번호를 리턴하는 모습.

 

이렇게 비로소 내가 원하던 결과를 모두 얻을 수 있었다. 

 

마치며

최근 들어서 느끼는건 Nest.js를 시작하고나서부터

궁금한 것들에 대해 검색해서 나오는 결과가 stackoverflow 보다는

관련 GitHub의 issue 탭에서 나온다는 점이다.

 

확실히 만들어진지 얼마 안된 기능일수록 issue에서 바로 만든 사람에게 직접적으로 물어보며

같이 해결하는 모습을 볼 수 있었는데

 

이런 모습을 보며 오픈 소스에 기여하는게 그렇게 어렵지 않을 것 같다는 느낌이 들었다.

 

내가 좀 더 Nest.js에 익숙해서 이렇게 issue에 궁금한걸 단 사람들이나

해당 라이브러리를 쓰면서 발견한 문제에 대해 

어떻게 개선하면 좋을지를 pr 한다면 

그게 바로 오픈소스 기여가 될 것이니 말이다. 

 

이번에는 Ahmed Hdeawy씨를 비롯한 다른 여러 수많은 개발자들의 도움을 받아 

문제를 해결할 수 있었는데

 

나도 언젠가 이들처럼 패키지의 문제를 해결해 다른 개발자들에게 도움을 줄 수 있는

그런 사람이 되고 싶다는 생각이 들며

이번 작업이 나에게 정말 굉장히 큰 동기부여가 된 것 같다.

 

큰 프로젝트 들어가기에 앞서 Nest.js에 대한 기반을 차곡차곡 다지고 있는데

이번주만 해도 새롭게 알게된게 벌써 3가지가 넘는다. 

 

이렇게 배운 기술들을 마음껏 뽐낼 생각을 하니 벌써부터 설레는데 

얼른 다음주가 됐으면 좋겠다.

728x90