@@ -2,6 +2,9 @@ import { Injectable } from '@nestjs/common';
22import { JwtService } from '@nestjs/jwt' ;
33import { Response } from 'express' ;
44import { v4 as uuidv4 } from 'uuid' ;
5+ import { UserRepository } from '../../user/user.repository' ;
6+ import { UserNotFoundException } from '../../exception/user.exception' ;
7+ import { InvalidTokenException } from '../../exception/invalid.exception' ;
58
69const HOUR = 60 * 60 ;
710const DAY = 24 * 60 * 60 ;
@@ -10,21 +13,27 @@ const MS_HALF_YEAR = 6 * 30 * 24 * 60 * 60 * 1000;
1013
1114@Injectable ( )
1215export class TokenService {
13- constructor ( private readonly jwtService : JwtService ) { }
16+ constructor (
17+ private readonly jwtService : JwtService ,
18+ private readonly userRepository : UserRepository ,
19+ ) { }
1420
15- generateAccessToken ( payload : any ) : string {
21+ generateAccessToken ( userId : number ) : string {
22+ const payload = { sub : userId } ;
1623 return this . jwtService . sign ( payload , {
1724 expiresIn : HOUR ,
1825 } ) ;
1926 }
2027
21- generateRefreshToken ( ) : string {
22- const payload = {
23- jti : uuidv4 ( ) ,
24- } ;
25- return this . jwtService . sign ( payload , {
28+ async generateRefreshToken ( userId : number ) : Promise < string > {
29+ const payload = { sub : userId , jti : uuidv4 ( ) } ;
30+ const refreshToken = this . jwtService . sign ( payload , {
2631 expiresIn : FIVE_MONTHS ,
2732 } ) ;
33+
34+ await this . updateRefreshToken ( userId , refreshToken ) ;
35+
36+ return refreshToken ;
2837 }
2938
3039 generateInviteToken ( workspaceId : number , role : string ) : string {
@@ -42,16 +51,23 @@ export class TokenService {
4251 } ) ;
4352 }
4453
45- // 후에 DB 로직 (지금은 refreshToken이 DB로 관리 X)
46- // 추가될 때를 위해 일단 비동기 선언
4754 async refreshAccessToken ( refreshToken : string ) : Promise < string > {
48- // refreshToken을 검증한다
55+ // refreshToken 1차 검증한다
4956 const decoded = this . jwtService . verify ( refreshToken , {
5057 secret : process . env . JWT_SECRET ,
5158 } ) ;
5259
60+ // 검증된 토큰에서 사용자 ID 추출
61+ const userId = decoded . sub ;
62+
63+ // DB에 저장된 refreshToken과 비교
64+ const isValid = await this . compareStoredRefreshToken ( userId , refreshToken ) ;
65+ if ( ! isValid ) {
66+ throw new InvalidTokenException ( ) ;
67+ }
68+
5369 // 새로운 accessToken을 발급한다
54- return this . generateAccessToken ( { sub : decoded . sub } ) ;
70+ return this . generateAccessToken ( decoded . sub ) ;
5571 }
5672
5773 setAccessTokenCookie ( response : Response , accessToken : string ) : void {
@@ -86,4 +102,34 @@ export class TokenService {
86102 sameSite : 'strict' ,
87103 } ) ;
88104 }
105+
106+ private async compareStoredRefreshToken (
107+ id : number ,
108+ refreshToken : string ,
109+ ) : Promise < boolean > {
110+ // 유저를 찾는다.
111+ const user = await this . userRepository . findOneBy ( { id } ) ;
112+
113+ // 유저가 없으면 오류
114+ if ( ! user ) {
115+ throw new UserNotFoundException ( ) ;
116+ }
117+
118+ // DB에 있는 값과 일치하는지 비교한다
119+ return user . refreshToken === refreshToken ;
120+ }
121+
122+ private async updateRefreshToken ( id : number , refreshToken : string ) {
123+ // 유저를 찾는다.
124+ const user = await this . userRepository . findOneBy ( { id } ) ;
125+
126+ // 유저가 없으면 오류
127+ if ( ! user ) {
128+ throw new UserNotFoundException ( ) ;
129+ }
130+
131+ // 유저의 현재 REFRESH TOKEN 갱신
132+ user . refreshToken = refreshToken ;
133+ await this . userRepository . save ( user ) ;
134+ }
89135}
0 commit comments