From 30b8c4201e2256d2b0a21cbc651739dd9f24e191 Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sat, 11 Jan 2025 00:05:21 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[Fix]=20Module=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.module.ts | 1 - src/chat/chat.module.ts | 5 +++-- src/modules/auth/auth.module.ts | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 6cd327c..29b5416 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,6 +14,5 @@ import { ChatModule } from './chat/chat.module'; ChatModule, ], providers: [ChatGateway], - ], }) export class AppModule {} diff --git a/src/chat/chat.module.ts b/src/chat/chat.module.ts index 1e54921..261283e 100644 --- a/src/chat/chat.module.ts +++ b/src/chat/chat.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { ChatService } from './chat.service'; import { PrismaModule } from '@src/prisma/prisma.module'; -import { JwtModule } from '@nestjs/jwt'; +import { AuthModule } from '@modules/auth/auth.module'; // AuthModule 추가 @Module({ - imports: [PrismaModule, JwtModule], + imports: [PrismaModule, AuthModule], providers: [ChatService], + exports: [ChatService], }) export class ChatModule {} diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 7c4d48b..351a8a7 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -1,5 +1,4 @@ import { Module } from '@nestjs/common'; -import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; @@ -28,6 +27,6 @@ import { JwtModule } from '@nestjs/jwt'; GoogleStrategy, // Google OAuth 전략 JwtAuthGuard, ], - exports: [JwtAuthGuard], // 다른 모듈에서 AuthService를 사용할 수 있도록 내보냄 + exports: [JwtAuthGuard, JwtModule], // 다른 모듈에서 AuthService를 사용할 수 있도록 내보냄 }) export class AuthModule {} From 3c11ea6fe668d1cac8dad2f0684efe2828d0c0e9 Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sat, 11 Jan 2025 00:09:35 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[Fix]=20=EC=BF=A0=ED=82=A4=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=8B=9C=EA=B0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/auth.controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index e6da713..101a538 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -43,7 +43,7 @@ export class AuthController { secure: false, sameSite: 'none', path: '/', - maxAge: 7 * 24 * 60 * 60 * 1000, + maxAge: 7 * 24 * 60 * 60, }); return res.status(HttpStatusCodes.OK).json( @@ -78,7 +78,7 @@ export class AuthController { httpOnly: true, secure: true, sameSite: 'strict', // CSRF 방지 - maxAge: 7 * 24 * 60 * 60 * 1000, // 7일 + maxAge: 7 * 24 * 60 * 60, // 7일 }); return new ApiResponse(HttpStatusCodes.OK, 'Google 로그인 성공', { @@ -210,7 +210,7 @@ export class AuthController { secure: false, sameSite: 'none', path: '/', - maxAge: 7 * 24 * 60 * 60 * 1000, + maxAge: 7 * 24 * 60 * 60, }); return res.status(HttpStatusCodes.OK).json( From 19a152aa779677d2963c35b49aa5270b95473047 Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sat, 11 Jan 2025 22:39:12 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[Fix]=20console=EB=AC=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/strategies/jwt.strategy.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts index ac9ef55..f2a9816 100644 --- a/src/modules/auth/strategies/jwt.strategy.ts +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -12,7 +12,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } async validate(payload: any) { - console.log('JWT_payload: ', payload); // req.user에 설정될 사용자 정보 반환 return { user_id: payload.userId, email: payload.email }; } From d5d9954361491b47f74b55429ea630c16d8dd337 Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sat, 11 Jan 2025 22:39:44 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[Feat]=20refresh=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/auth.controller.ts | 259 +++++++++++++--------------- src/modules/auth/auth.service.ts | 54 +++++- 2 files changed, 168 insertions(+), 145 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 101a538..4e290ba 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -8,6 +8,7 @@ import { Put, HttpException, Res, + HttpStatus, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { AuthService } from '@src/modules/auth/auth.service'; @@ -37,28 +38,17 @@ export class AuthController { await this.authService.handleGoogleCallback(code); console.log('accessToken: ' + accessToken); console.log('refreshToken: ' + refreshToken); - // 리프레시 토큰을 HTTP-Only 쿠키로 설정 - res.cookie('refreshToken', refreshToken, { - httpOnly: false, - secure: false, - sameSite: 'none', - path: '/', - maxAge: 7 * 24 * 60 * 60, - }); - - return res.status(HttpStatusCodes.OK).json( - new ApiResponse(HttpStatusCodes.OK, 'Google 로그인 성공', { - user, - accessToken, - refreshToken, - isExistingUser, - }) - ); - // return new ApiResponse(HttpStatusCodes.OK, 'Google 로그인 성공', { - // user, - // accessToken, - // isExistingUser, - // }); + const response = { + message: { + code: 200, + message: 'Google 로그인 성공', + }, + user, + accessToken, + isExistingUser, + }; + console.log(response); + return res.status(HttpStatusCodes.OK).json(response); } @Get('github') @@ -70,22 +60,19 @@ export class AuthController { @Post('github/callback') async githubCallback(@Body('code') code: string, @Res() res: Response) { // Authorization Code 교환 및 사용자 정보 가져오기 - const { user, accessToken, refreshToken, isExistingUser } = + const { user, accessToken, isExistingUser } = await this.authService.handleGithubCallback(code); - - // 리프레시 토큰을 HTTP-Only 쿠키로 설정 - res.cookie('refreshToken', refreshToken, { - httpOnly: true, - secure: true, - sameSite: 'strict', // CSRF 방지 - maxAge: 7 * 24 * 60 * 60, // 7일 - }); - - return new ApiResponse(HttpStatusCodes.OK, 'Google 로그인 성공', { + const response = { + message: { + code: 200, + message: 'Google 로그인 성공', + }, user, accessToken, isExistingUser, - }); + }; + console.log(response); + return res.status(HttpStatusCodes.OK).json(response); } // Role 선택 API @@ -110,126 +97,116 @@ export class AuthController { return res.status(HttpStatusCodes.OK).json(responseBody); } + @Post('refresh') + async refreshAccessToken( + @Body('user_id') userId: number, // user_id는 숫자로 받음 + @Res() res: Response + ) { + if (userId === undefined || userId === null) { + throw new HttpException('User ID is required', HttpStatus.BAD_REQUEST); + } + console.log(userId); + try { + // user_id를 문자열로 변환 + //const userIdStr = String(userId); + + // 리프레쉬 토큰 확인 및 액세스 토큰 재발급 + const newAccessToken = await this.authService.renewAccessToken(userId); + + // 성공 메시지와 새로운 액세스 토큰 반환 + const response = { + message: { + code: 200, + text: 'Access token이 성공적으로 갱신 되었습니다.', + }, + access_token: newAccessToken, + }; + + return res.status(200).json(response); + } catch (error) { + //throw new HttpException(error.message, HttpStatus.UNAUTHORIZED); + console.log(error); + } + } + // @Post('refresh') // async refreshAccessToken(@Req() req: any, @Res() res: Response) { // console.log('Refresh Token API 호출'); // const refreshToken = req.cookies['refreshToken']; - // + // console.log('refreshToken from Cookie: ' + refreshToken); + // // 1. Refresh Token이 없는 경우 예외 처리 // if (!refreshToken) { + // console.error('리프레쉬 토큰 없음'); // throw new HttpException( // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code // ); // } - // //const userId = req.user?.user_id; - // const userId = this.authService.getUserIdFromRefreshToken(refreshToken); - // console.log('user_id: ' + userId); - // const isValid = await this.authService.validateRefreshToken( - // userId, - // refreshToken - // ); - // if (!isValid) { - // const error = ErrorMessages.AUTH.INVALID_REFRESH_TOKEN; - // throw new HttpException(error.text, error.code); - // } + // console.log('요청된 Refresh Token:', refreshToken); + // + // try { + // // 2. Refresh Token에서 User ID 추출 + // const userId = this.authService.getUserIdFromRefreshToken(refreshToken); + // if (!userId) { + // console.error('Refresh Token으로부터 User ID 추출 실패'); + // throw new HttpException( + // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, + // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code + // ); + // } + // console.log('추출된 User ID:', userId); // - // const newAccessToken = this.authService.generateAccessToken(userId); - // const newRefreshToken = this.authService.generateRefreshToken(userId); - // res.cookie('refreshToken', newRefreshToken, { - // httpOnly: true, - // secure: false, - // sameSite: 'none', - // maxAge: 7 * 24 * 60 * 60 * 1000, - // }); - // return res.status(HttpStatusCodes.OK).json( - // new ApiResponse( - // HttpStatusCodes.OK, - // '액세스 토큰이 성공적으로 갱신되었습니다.', - // { - // accessToken: newAccessToken, - // } - // ) - // ); + // // 3. Refresh Token 유효성 검증 + // const isValid = await this.authService.validateRefreshToken( + // userId, + // refreshToken + // ); + // console.log('Refresh Token 유효성 검증 결과:', isValid); + // + // if (!isValid) { + // console.error('Refresh Token이 Redis와 일치하지 않음'); + // throw new HttpException( + // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, + // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code + // ); + // } + // + // // 4. 새로운 Access Token 및 Refresh Token 생성 + // const newAccessToken = this.authService.generateAccessToken(userId); + // const newRefreshToken = this.authService.generateRefreshToken(userId); + // + // console.log('새로운 Access Token:', newAccessToken); + // console.log('새로운 Refresh Token:', newRefreshToken); + // + // // 5. Redis에 새로운 Refresh Token 저장 + // await this.authService.storeRefreshToken(userId, newRefreshToken); + // + // // 6. Refresh Token을 쿠키에 저장 + // res.cookie('refreshToken', refreshToken, { + // httpOnly: false, + // secure: false, + // sameSite: 'none', + // path: '/', + // maxAge: 7 * 24 * 60 * 60, + // }); + // + // return res.status(HttpStatusCodes.OK).json( + // new ApiResponse( + // HttpStatusCodes.OK, + // '액세스 토큰이 성공적으로 갱신되었습니다.', + // { + // accessToken: newAccessToken, + // } + // ) + // ); + // } catch (error) { + // console.error('Refresh Token 갱신 중 오류 발생:', error.message); + // return res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).json({ + // message: 'Internal Server Error', + // }); + // } // } - @Post('refresh') - async refreshAccessToken(@Req() req: any, @Res() res: Response) { - console.log('Refresh Token API 호출'); - const refreshToken = req.cookies['refreshToken']; - console.log('refreshToken from Cookie: ' + refreshToken); - // 1. Refresh Token이 없는 경우 예외 처리 - if (!refreshToken) { - console.error('리프레쉬 토큰 없음'); - throw new HttpException( - ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, - ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code - ); - } - console.log('요청된 Refresh Token:', refreshToken); - - try { - // 2. Refresh Token에서 User ID 추출 - const userId = this.authService.getUserIdFromRefreshToken(refreshToken); - if (!userId) { - console.error('Refresh Token으로부터 User ID 추출 실패'); - throw new HttpException( - ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, - ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code - ); - } - console.log('추출된 User ID:', userId); - - // 3. Refresh Token 유효성 검증 - const isValid = await this.authService.validateRefreshToken( - userId, - refreshToken - ); - console.log('Refresh Token 유효성 검증 결과:', isValid); - - if (!isValid) { - console.error('Refresh Token이 Redis와 일치하지 않음'); - throw new HttpException( - ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, - ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code - ); - } - - // 4. 새로운 Access Token 및 Refresh Token 생성 - const newAccessToken = this.authService.generateAccessToken(userId); - const newRefreshToken = this.authService.generateRefreshToken(userId); - - console.log('새로운 Access Token:', newAccessToken); - console.log('새로운 Refresh Token:', newRefreshToken); - - // 5. Redis에 새로운 Refresh Token 저장 - await this.authService.storeRefreshToken(userId, newRefreshToken); - - // 6. Refresh Token을 쿠키에 저장 - res.cookie('refreshToken', refreshToken, { - httpOnly: false, - secure: false, - sameSite: 'none', - path: '/', - maxAge: 7 * 24 * 60 * 60, - }); - - return res.status(HttpStatusCodes.OK).json( - new ApiResponse( - HttpStatusCodes.OK, - '액세스 토큰이 성공적으로 갱신되었습니다.', - { - accessToken: newAccessToken, - } - ) - ); - } catch (error) { - console.error('Refresh Token 갱신 중 오류 발생:', error.message); - return res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).json({ - message: 'Internal Server Error', - }); - } - } - @Post('logout') async logout(@Req() req: any, @Res() res: Response) { res.clearCookie('refreshToken'); // HTTP-Only 쿠키 삭제 diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index f53f3d8..64a48d4 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -125,7 +125,7 @@ export class AuthService { }); const jwt = this.generateAccessToken(user.id); - const refreshToken = await this.generateRefreshToken(user.id); // 리프레시 토큰 생성 + const refreshToken = this.generateRefreshToken(user.id); // 리프레시 토큰 생성 // Redis에 리프레시 토큰 저장 await this.storeRefreshToken(user.id, refreshToken); @@ -152,10 +152,18 @@ export class AuthService { } // 사용자 찾기 또는 생성 async findOrCreateUser(profile: AuthUserDto) { - return this.prisma.user.upsert({ + // 이메일로 유저 찾기 + const existingUser = await this.prisma.user.findUnique({ where: { email: profile.email }, - update: {}, // 이미 존재하면 아무것도 업데이트하지 않음 - create: { + }); + + if (existingUser) { + return existingUser; // 기존 유저 반환 + } + + // 새 유저 생성 + return this.prisma.user.create({ + data: { email: profile.email, name: profile.name, nickname: profile.nickname, @@ -275,4 +283,42 @@ export class AuthService { }; return result; } + + async renewAccessToken(userId: number): Promise { + const redisKey = `refresh_token:${userId}`; + const refreshToken = await this.redisService.get(redisKey); + + if (!refreshToken) { + console.error(`No refresh token found for Redis key: ${redisKey}`); + throw new Error('Refresh token not found for user'); + } + + console.log(`Retrieved refresh token from Redis: ${refreshToken}`); + + try { + const payload = this.jwtService.verify(refreshToken, { + secret: process.env.REFRESH_TOKEN_SECRET, + algorithms: ['HS256'], // 생성 시와 동일한 알고리즘 + }); + console.log(`Decoded payload:`, payload); + + if (payload.userId !== userId) { + console.error( + `Token userId mismatch. Expected: ${userId}, Got: ${payload.userId}` + ); + throw new Error('Invalid refresh token'); + } + } catch (error) { + console.error(`JWT verification error: ${error.message}`); + throw new Error('Refresh token validation failed'); + } + + const newAccessToken = this.jwtService.sign( + { userId }, + { expiresIn: '15m', secret: process.env.ACCESS_TOKEN_SECRET } + ); + + console.log(`Generated new access token: ${newAccessToken}`); + return newAccessToken; + } } From 8c21f3f05d6487e2f797d6f9ff05fc7e82cbfb8d Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sun, 12 Jan 2025 21:15:22 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[Feat]=20renewAccessToken=20=EA=B3=A0?= =?UTF-8?q?=EB=8F=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/auth.controller.ts | 93 +++-------------------------- 1 file changed, 7 insertions(+), 86 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 4e290ba..27c1546 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -15,7 +15,6 @@ import { AuthService } from '@src/modules/auth/auth.service'; import { JwtAuthGuard } from '@src/modules/auth/guards/jwt-auth.guard'; import { JwtService } from '@nestjs/jwt'; import { ApiResponse } from '@common/dto/response.dto'; -import { ErrorMessages } from '@common/constants/error-messages'; import { HttpStatusCodes } from '@common/constants/http-status-code'; import { Response } from 'express'; @Controller('auth') @@ -36,8 +35,7 @@ export class AuthController { // Authorization Code 교환 및 사용자 정보 가져오기 const { user, accessToken, refreshToken, isExistingUser } = await this.authService.handleGoogleCallback(code); - console.log('accessToken: ' + accessToken); - console.log('refreshToken: ' + refreshToken); + console.log('refreshToken: ', refreshToken); const response = { message: { code: 200, @@ -60,8 +58,9 @@ export class AuthController { @Post('github/callback') async githubCallback(@Body('code') code: string, @Res() res: Response) { // Authorization Code 교환 및 사용자 정보 가져오기 - const { user, accessToken, isExistingUser } = + const { user, accessToken, refreshToken, isExistingUser } = await this.authService.handleGithubCallback(code); + console.log(refreshToken); const response = { message: { code: 200, @@ -75,6 +74,10 @@ export class AuthController { return res.status(HttpStatusCodes.OK).json(response); } + // @Post('login') + // async login( + // @Body() + // ) // Role 선택 API @Put('roleselect') @UseGuards(JwtAuthGuard) @@ -107,9 +110,6 @@ export class AuthController { } console.log(userId); try { - // user_id를 문자열로 변환 - //const userIdStr = String(userId); - // 리프레쉬 토큰 확인 및 액세스 토큰 재발급 const newAccessToken = await this.authService.renewAccessToken(userId); @@ -128,85 +128,6 @@ export class AuthController { console.log(error); } } - - // @Post('refresh') - // async refreshAccessToken(@Req() req: any, @Res() res: Response) { - // console.log('Refresh Token API 호출'); - // const refreshToken = req.cookies['refreshToken']; - // console.log('refreshToken from Cookie: ' + refreshToken); - // // 1. Refresh Token이 없는 경우 예외 처리 - // if (!refreshToken) { - // console.error('리프레쉬 토큰 없음'); - // throw new HttpException( - // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, - // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code - // ); - // } - // console.log('요청된 Refresh Token:', refreshToken); - // - // try { - // // 2. Refresh Token에서 User ID 추출 - // const userId = this.authService.getUserIdFromRefreshToken(refreshToken); - // if (!userId) { - // console.error('Refresh Token으로부터 User ID 추출 실패'); - // throw new HttpException( - // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, - // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code - // ); - // } - // console.log('추출된 User ID:', userId); - // - // // 3. Refresh Token 유효성 검증 - // const isValid = await this.authService.validateRefreshToken( - // userId, - // refreshToken - // ); - // console.log('Refresh Token 유효성 검증 결과:', isValid); - // - // if (!isValid) { - // console.error('Refresh Token이 Redis와 일치하지 않음'); - // throw new HttpException( - // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.text, - // ErrorMessages.AUTH.INVALID_REFRESH_TOKEN.code - // ); - // } - // - // // 4. 새로운 Access Token 및 Refresh Token 생성 - // const newAccessToken = this.authService.generateAccessToken(userId); - // const newRefreshToken = this.authService.generateRefreshToken(userId); - // - // console.log('새로운 Access Token:', newAccessToken); - // console.log('새로운 Refresh Token:', newRefreshToken); - // - // // 5. Redis에 새로운 Refresh Token 저장 - // await this.authService.storeRefreshToken(userId, newRefreshToken); - // - // // 6. Refresh Token을 쿠키에 저장 - // res.cookie('refreshToken', refreshToken, { - // httpOnly: false, - // secure: false, - // sameSite: 'none', - // path: '/', - // maxAge: 7 * 24 * 60 * 60, - // }); - // - // return res.status(HttpStatusCodes.OK).json( - // new ApiResponse( - // HttpStatusCodes.OK, - // '액세스 토큰이 성공적으로 갱신되었습니다.', - // { - // accessToken: newAccessToken, - // } - // ) - // ); - // } catch (error) { - // console.error('Refresh Token 갱신 중 오류 발생:', error.message); - // return res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).json({ - // message: 'Internal Server Error', - // }); - // } - // } - @Post('logout') async logout(@Req() req: any, @Res() res: Response) { res.clearCookie('refreshToken'); // HTTP-Only 쿠키 삭제 From 043be66b17bdbe6d1a2ce3891e52b0bffbc692bb Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sun, 12 Jan 2025 21:15:49 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[Feat]=20renewAccessToken=20=EA=B3=A0?= =?UTF-8?q?=EB=8F=84=ED=99=94,=20Github=20Social=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/auth.service.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 64a48d4..eb74575 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { Injectable, HttpException } from '@nestjs/common'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { RedisService } from '@modules/redis/redis.service'; import { PrismaService } from '@src/prisma/prisma.service'; @@ -124,16 +124,17 @@ export class AuthService { auth_provider: 'github', }); - const jwt = this.generateAccessToken(user.id); + const accessToken = this.generateAccessToken(user.id); const refreshToken = this.generateRefreshToken(user.id); // 리프레시 토큰 생성 // Redis에 리프레시 토큰 저장 await this.storeRefreshToken(user.id, refreshToken); const responseUser = this.filterUserFields(user); + return { user: responseUser, - accessToken: jwt, - refreshToken: refreshToken, // 리프레시 토큰 반환 + accessToken, + refreshToken, isExistingUser, }; } catch (error) { @@ -194,7 +195,7 @@ export class AuthService { console.log(`Access Token 생성: userId=${userId}`); return this.jwtService.sign( { userId }, - { expiresIn: '1m', secret: process.env.ACCESS_TOKEN_SECRET } + { expiresIn: '15m', secret: process.env.ACCESS_TOKEN_SECRET } ); } @@ -309,8 +310,16 @@ export class AuthService { throw new Error('Invalid refresh token'); } } catch (error) { - console.error(`JWT verification error: ${error.message}`); - throw new Error('Refresh token validation failed'); + if (error.name === 'TokenExpiredError') { + throw new HttpException( + 'Refresh token has expired', + HttpStatus.BAD_REQUEST + ); + } + throw new HttpException( + 'Refresh token validation failed', + HttpStatus.UNAUTHORIZED + ); } const newAccessToken = this.jwtService.sign( From e8320aae144d8f2bdfcc9d23a057266bc83c0697 Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sun, 12 Jan 2025 21:22:02 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[Feat]=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/auth.controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 27c1546..2d9fc11 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -129,8 +129,10 @@ export class AuthController { } } @Post('logout') + @UseGuards(JwtAuthGuard) async logout(@Req() req: any, @Res() res: Response) { - res.clearCookie('refreshToken'); // HTTP-Only 쿠키 삭제 + const userId = req.user?.user_id; + await this.authService.deleteRefreshToken(userId); return res .status(HttpStatusCodes.OK) .json(new ApiResponse(HttpStatusCodes.OK, '로그아웃 성공')); From d30b40b7c2fae0bbd96bd4f413c645f813c606f5 Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sun, 12 Jan 2025 22:02:40 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[Feat]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=9D=BC=EB=B0=98=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8,=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/auth.controller.ts | 25 +++++++++++++ src/modules/auth/auth.service.ts | 55 +++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 2d9fc11..0548a01 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -137,4 +137,29 @@ export class AuthController { .status(HttpStatusCodes.OK) .json(new ApiResponse(HttpStatusCodes.OK, '로그아웃 성공')); } + + @Post('signup') + async signup( + @Body() body: { email: string; nickname: string; password: string } + ) { + const result = await this.authService.signup( + body.email, + body.nickname, + body.password + ); + return { + message: 'Signup successful', + user: result, + }; + } + + @Post('login') + async login(@Body() body: { email: string; password: string }) { + const result = await this.authService.login(body.email, body.password); + return { + message: 'Login successful', + user: result.user, + access_token: result.accessToken, + }; + } } diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index eb74575..1848aaf 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -179,6 +179,61 @@ export class AuthService { }); } + // 회원가입 로직 + async signup(email: string, nickname: string, password: string) { + // 이메일 중복 확인 + const existingUser = await this.prisma.user.findUnique({ + where: { email }, + }); + if (existingUser) { + throw new HttpException('Email already exists', HttpStatus.BAD_REQUEST); + } + + // 새로운 사용자 생성 + const newUser = await this.prisma.user.create({ + data: { + email, + name: nickname, + nickname, + password, + auth_provider: 'pad', // 소셜 로그인과 구분 + role: { connect: { id: 1 } }, + status: { connect: { id: 1 } }, + }, + }); + + return { + user_id: newUser.id, + email: newUser.email, + nickname: newUser.nickname, + }; + } + + // 로그인 로직 + async login(email: string, password: string) { + // 사용자 조회 + const user = await this.prisma.user.findUnique({ + where: { email }, + }); + if (!user || user.password !== password) { + throw new HttpException( + 'Invalid email or password', + HttpStatus.UNAUTHORIZED + ); + } + + // 액세스 토큰 및 리프레시 토큰 생성 + const accessToken = this.generateAccessToken(user.id); + const refreshToken = this.generateRefreshToken(user.id); + + // Redis에 리프레시 토큰 저장 + await this.storeRefreshToken(user.id, refreshToken); + const responseUser = this.filterUserFields(user); + return { + user: responseUser, + accessToken, + }; + } private filterUserFields(user: any) { return { user_id: user.id, From 7e5345643c43202c732e994596c09965daa1b73b Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sun, 12 Jan 2025 22:02:55 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[Feat]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20db=20scheme=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d5cdfed..ee7270a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,6 +12,7 @@ model User { email String @unique name String nickname String + password String? auth_provider String profile_url String? role_id Int @@ -25,11 +26,14 @@ model User { created_at DateTime @default(now()) updated_at DateTime @updatedAt ArtistData ArtistData? + Channel_users Channel_users[] FeedComments FeedComment[] FeedLikes FeedLike[] FeedPosts FeedPost[] Followed Follows[] @relation("FollowedUsers") Follows Follows[] @relation("UserFollows") + Message Message[] + Message_status Message_status[] ProgrammerData ProgrammerData? ProjectPosts Project? ProjectPost ProjectPost[] @@ -40,9 +44,6 @@ model User { UserApplyProject UserApplyProject[] UserLinks UserLink[] UserSkills UserSkill[] - Channel_users Channel_users[] - Message Message[] - Message_status Message_status[] @@index([role_id], map: "User_role_id_fkey") @@index([status_id], map: "User_status_id_fkey") @@ -309,7 +310,7 @@ model UserApplyProject { } model Channel { - id String @id + id Int @id @default(autoincrement()) name String @default("default_channel_name") active Boolean @default(true) created_at DateTime @default(now()) @@ -319,23 +320,29 @@ model Channel { model Channel_users { id Int @id @default(autoincrement()) - channel_id String + channel_id Int user_id Int joined_at DateTime @default(now()) channel Channel @relation(fields: [channel_id], references: [id]) user User @relation(fields: [user_id], references: [id]) + + @@index([channel_id], map: "Channel_users_channel_id_fkey") + @@index([user_id], map: "Channel_users_user_id_fkey") } model Message { id Int @id @default(autoincrement()) - channel_id String user_id Int - type String - content String created_at DateTime @default(now()) - user User @relation(fields: [user_id], references: [id]) + channel_id Int + content String + type String channel Channel @relation(fields: [channel_id], references: [id]) + user User @relation(fields: [user_id], references: [id]) message_status Message_status[] + + @@index([channel_id], map: "Message_channel_id_fkey") + @@index([user_id], map: "Message_user_id_fkey") } model Message_status { @@ -343,7 +350,9 @@ model Message_status { message_id Int user_id Int is_read Boolean @default(false) - user User @relation(fields: [user_id], references: [id]) message Message @relation(fields: [message_id], references: [id]) -} + user User @relation(fields: [user_id], references: [id]) + @@index([message_id], map: "Message_status_message_id_fkey") + @@index([user_id], map: "Message_status_user_id_fkey") +} From df97bc05e138e2027a650c5db6a90c295d85c8c8 Mon Sep 17 00:00:00 2001 From: Ss0Mae Date: Sun, 12 Jan 2025 22:26:48 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[Fix]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EB=B3=80=EA=B2=BD,?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EC=9C=A0=ED=9A=A8=EC=9D=BC=20=EC=97=B0?= =?UTF-8?q?=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/auth/auth.controller.ts | 2 +- src/modules/auth/auth.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 0548a01..945ded4 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -64,7 +64,7 @@ export class AuthController { const response = { message: { code: 200, - message: 'Google 로그인 성공', + message: 'Github 로그인 성공', }, user, accessToken, diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 1848aaf..01a067d 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -250,7 +250,7 @@ export class AuthService { console.log(`Access Token 생성: userId=${userId}`); return this.jwtService.sign( { userId }, - { expiresIn: '15m', secret: process.env.ACCESS_TOKEN_SECRET } + { expiresIn: '7d', secret: process.env.ACCESS_TOKEN_SECRET } ); }