diff --git a/apps/backend/src/auth/auth.controller.ts b/apps/backend/src/auth/auth.controller.ts index d575c17..ff37b41 100644 --- a/apps/backend/src/auth/auth.controller.ts +++ b/apps/backend/src/auth/auth.controller.ts @@ -41,13 +41,11 @@ export class AuthController { // 네이버 인증 후 사용자 정보 반환 const user = req.user; - // primary Key인 id 포함 payload 생성, access token 만들기 - const payload = { sub: user.id }; - const accessToken = this.tokenService.generateAccessToken(payload); + // access token 만들기 + const accessToken = this.tokenService.generateAccessToken(user.id); - // access token 만들어서 db에도 저장 - const refreshToken = this.tokenService.generateRefreshToken(); - this.authService.updateRefreshToken(user.id, refreshToken); + // refresh token 만들어서 db에도 저장 + const refreshToken = await this.tokenService.generateRefreshToken(user.id); // 토큰을 쿠키에 담아서 메인 페이지로 리디렉션 this.tokenService.setAccessTokenCookie(res, accessToken); @@ -69,13 +67,11 @@ export class AuthController { /// 카카오 인증 후 사용자 정보 반환 const user = req.user; - // primary Key인 id 포함 payload 생성, access token 만들기 - const payload = { sub: user.id }; - const accessToken = this.tokenService.generateAccessToken(payload); + // access token 만들기 + const accessToken = this.tokenService.generateAccessToken(user.id); - // access token 만들어서 db에도 저장 - const refreshToken = this.tokenService.generateRefreshToken(); - this.authService.updateRefreshToken(user.id, refreshToken); + // refresh token 만들어서 db에도 저장 + const refreshToken = await this.tokenService.generateRefreshToken(user.id); // 토큰을 쿠키에 담아서 메인 페이지로 리디렉션 this.tokenService.setAccessTokenCookie(res, accessToken); diff --git a/apps/backend/src/auth/auth.service.ts b/apps/backend/src/auth/auth.service.ts index 4d9b1ff..404a639 100644 --- a/apps/backend/src/auth/auth.service.ts +++ b/apps/backend/src/auth/auth.service.ts @@ -61,18 +61,4 @@ export class AuthService { // DB에 있는 값과 일치하는지 비교한다 return user.refreshToken === refreshToken; } - - async updateRefreshToken(id: number, refreshToken: string) { - // 유저를 찾는다. - const user = await this.userRepository.findOneBy({ id }); - - // 유저가 없으면 오류 - if (!user) { - throw new UserNotFoundException(); - } - - // 유저의 현재 REFRESH TOKEN 갱신 - user.refreshToken = refreshToken; - await this.userRepository.save(user); - } } diff --git a/apps/backend/src/auth/token/token.module.ts b/apps/backend/src/auth/token/token.module.ts index 0ddbc54..8a13243 100644 --- a/apps/backend/src/auth/token/token.module.ts +++ b/apps/backend/src/auth/token/token.module.ts @@ -2,9 +2,11 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { TokenService } from './token.service'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { UserModule } from '../../user/user.module'; @Module({ imports: [ + UserModule, ConfigModule, // ConfigModule 등록 JwtModule.registerAsync({ imports: [ConfigModule], // ConfigModule에서 환경 변수 로드 diff --git a/apps/backend/src/auth/token/token.service.ts b/apps/backend/src/auth/token/token.service.ts index 802968c..7ece2a3 100644 --- a/apps/backend/src/auth/token/token.service.ts +++ b/apps/backend/src/auth/token/token.service.ts @@ -2,6 +2,9 @@ import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Response } from 'express'; import { v4 as uuidv4 } from 'uuid'; +import { UserRepository } from '../../user/user.repository'; +import { UserNotFoundException } from '../../exception/user.exception'; +import { InvalidTokenException } from '../../exception/invalid.exception'; const HOUR = 60 * 60; const DAY = 24 * 60 * 60; @@ -10,21 +13,27 @@ const MS_HALF_YEAR = 6 * 30 * 24 * 60 * 60 * 1000; @Injectable() export class TokenService { - constructor(private readonly jwtService: JwtService) {} + constructor( + private readonly jwtService: JwtService, + private readonly userRepository: UserRepository, + ) {} - generateAccessToken(payload: any): string { + generateAccessToken(userId: number): string { + const payload = { sub: userId }; return this.jwtService.sign(payload, { expiresIn: HOUR, }); } - generateRefreshToken(): string { - const payload = { - jti: uuidv4(), - }; - return this.jwtService.sign(payload, { + async generateRefreshToken(userId: number): Promise { + const payload = { sub: userId, jti: uuidv4() }; + const refreshToken = this.jwtService.sign(payload, { expiresIn: FIVE_MONTHS, }); + + await this.updateRefreshToken(userId, refreshToken); + + return refreshToken; } generateInviteToken(workspaceId: number, role: string): string { @@ -42,16 +51,23 @@ export class TokenService { }); } - // 후에 DB 로직 (지금은 refreshToken이 DB로 관리 X) - // 추가될 때를 위해 일단 비동기 선언 async refreshAccessToken(refreshToken: string): Promise { - // refreshToken을 검증한다 + // refreshToken 1차 검증한다 const decoded = this.jwtService.verify(refreshToken, { secret: process.env.JWT_SECRET, }); + // 검증된 토큰에서 사용자 ID 추출 + const userId = decoded.sub; + + // DB에 저장된 refreshToken과 비교 + const isValid = await this.compareStoredRefreshToken(userId, refreshToken); + if (!isValid) { + throw new InvalidTokenException(); + } + // 새로운 accessToken을 발급한다 - return this.generateAccessToken({ sub: decoded.sub }); + return this.generateAccessToken(decoded.sub); } setAccessTokenCookie(response: Response, accessToken: string): void { @@ -86,4 +102,34 @@ export class TokenService { sameSite: 'strict', }); } + + private async compareStoredRefreshToken( + id: number, + refreshToken: string, + ): Promise { + // 유저를 찾는다. + const user = await this.userRepository.findOneBy({ id }); + + // 유저가 없으면 오류 + if (!user) { + throw new UserNotFoundException(); + } + + // DB에 있는 값과 일치하는지 비교한다 + return user.refreshToken === refreshToken; + } + + private async updateRefreshToken(id: number, refreshToken: string) { + // 유저를 찾는다. + const user = await this.userRepository.findOneBy({ id }); + + // 유저가 없으면 오류 + if (!user) { + throw new UserNotFoundException(); + } + + // 유저의 현재 REFRESH TOKEN 갱신 + user.refreshToken = refreshToken; + await this.userRepository.save(user); + } }