diff --git a/src/config/jwt.config.js b/src/config/jwt.config.js index e027115..09b50c4 100644 --- a/src/config/jwt.config.js +++ b/src/config/jwt.config.js @@ -23,11 +23,9 @@ export const jwtStrategy = new JwtStrategy( }, }); - //수정 전 - if (!user) return done(null, false); - // if (!user || user.deletedAt) { - // return done(null, false); - // } + if (!user || user.deletedAt) { + return done(null, false); + } return done(null, user); } catch (err) { diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index bf5f0e4..4fbe688 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -1,5 +1,5 @@ import { KakaoAuthService } from "../services/auth.service.js"; -import { UnauthorizedError } from "../errors/custom.error.js"; +import { BadRequestError, UnauthorizedError } from "../errors/custom.error.js"; // import { prisma } from "../db.config.js"; export class AuthController{ @@ -76,38 +76,42 @@ export class AuthController{ } } - // //재가입시 기존 정보 복구 - // async restore(req, res, next){ - // try{ - // const { providerId } = req.body; - - // if (!providerId) {throw new BadRequestError("PROVIDER_ID_REQUIRED","providerId가 필요합니다.");} + //재가입시 기존 정보 복구 + async restore(req, res, next) { + try { + const { token } = req.body; - // const user = await prisma.user.findFirst({ - // where:{ - // provider: "KAKAO", - // providerId, - // deletedAt: { not: null }, - // } - // }); + if (!token) { + throw new BadRequestError("TOKEN_REQUIRED", "복구 토큰이 필요합니다."); + } - // if(!user){ throw new BadRequestError("USER_NOT_FOUND","복구할 탈퇴 계정을 찾을 수 없습니다.");} + const { accessToken, refreshToken } = + await this.kakaoAuthService.restoreKakaoUser(token); - // await prisma.user.update({ - // where:{ id: user.id }, - // data:{ deletedAt: null } - // }); + const isProd = process.env.NODE_ENV === "production"; - // return res.status(200).json({ - // resultType:"SUCCESS", - // message:"계정이 복구되었습니다." - // }); + // refreshToken 쿠키 세팅 + res.cookie("refreshToken", refreshToken, { + httpOnly: true, + secure: isProd, + sameSite: isProd ? "none" : "lax", + path: "/", + maxAge: 1000 * 60 * 60 * 24 * 14, + }); - // }catch(error){ - // next(error); - // } - // } + return res.status(200).json({ + resultType: "SUCCESS", + message: "계정이 복구되었으며 로그인되었습니다.", + data: { + accessToken, + accessTokenExpireIn: 3600, + }, + }); + } catch (error) { + next(error); + } + } } diff --git a/src/routes/auth.route.js b/src/routes/auth.route.js index 75ca278..5b84201 100644 --- a/src/routes/auth.route.js +++ b/src/routes/auth.route.js @@ -52,10 +52,11 @@ router.get( const redirectBaseUrl = REDIRECT_URL_MAP[req.query.state || "prod"]; if (!redirectBaseUrl) { return res.status(500).send("리다이렉트 URL이 설정되지 않았습니다."); } - // //탈퇴 회원 분기 - // if (req.user.withdrawnUser) { - // return res.redirect(`${redirectBaseUrl}/auth/withdrawn`); - // } + // 탈퇴 회원 분기 + if (req.user.withdrawnUser) { + return res.redirect(`${redirectBaseUrl}/login?status=withdrawn&token=${req.user.restoreToken}`); + } + const { refreshToken } = req.user; const isProd = process.env.NODE_ENV === "production"; @@ -102,11 +103,11 @@ router.delete( router.post("/logout", authController.logout.bind(authController)); //Access Token 발급 router.post("/refresh", authController.refresh.bind(authController)); -// //재가입시 복구 -// router.post( -// "/restore", -// // passport.authenticate("jwt", { session: false }), -// authController.restore.bind(authController) -// ); +//재가입시 복구 +router.post( + "/restore", + authController.restore.bind(authController) +); + export default router; \ No newline at end of file diff --git a/src/services/auth.service.js b/src/services/auth.service.js index 04f2c97..6af53fc 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -98,26 +98,23 @@ export class KakaoAuthService { }); let isNewUser = false; + + // 탈퇴 사용자 차단 + if (user && user.deletedAt) { + const restoreToken = crypto.randomUUID(); - //탈퇴 사용자면 자동 복구 - if(user && user.deletedAt){ - user = await prisma.user.update({ - where: { - id: user.id - }, - data: { - deletedAt: null, - }, - }); + await redis.set( + `restore_token:${restoreToken}`, + providerId, + "EX", + 60 * 5 //5분 + ); + + return { + withdrawnUser: true, + restoreToken, + }; } - - // // 탈퇴 사용자 차단 - // if (user && user.deletedAt) { - // return { - // withdrawnUser: true, - // providerId, - // }; - // } //신규 사용자 생성 if (!user) { @@ -196,4 +193,50 @@ export class KakaoAuthService { // 이미 만료된 경우도 로그아웃은 성공 처리 } } -} \ No newline at end of file + + async restoreKakaoUser(restoreToken) { + if (!restoreToken) { + throw new BadRequestError("TOKEN_REQUIRED", "복구 토큰이 필요합니다."); + } + + const providerId = await redis.get(`restore_token:${restoreToken}`); + + if (!providerId) { + throw new BadRequestError("INVALID_TOKEN", "유효하지 않거나 만료된 토큰입니다."); + } + + const user = await prisma.user.findFirst({ + where: { + provider: "KAKAO", + providerId, + deletedAt: { not: null }, + }, + }); + + if (!user) { + throw new BadRequestError("USER_NOT_FOUND", "복구할 사용자가 없습니다."); + } + + // 계정 복구 + const restoredUser = await prisma.user.update({ + where: { id: user.id }, + data: { deletedAt: null }, + }); + + // 1회성 토큰 삭제 + await redis.del(`restore_token:${restoreToken}`); + + // 로그인 토큰 발급 + const accessToken = this.generateAccessToken(restoredUser); + const { refreshToken, tokenId } = this.generateRefreshToken(restoredUser); + + await this.saveRefreshToken(tokenId, restoredUser.id); + + return { + user: restoredUser, + accessToken, + refreshToken, + }; + } +} +