From 5d5318b7b62d7f6bba257db237d120a940f5d470 Mon Sep 17 00:00:00 2001 From: jinddings Date: Thu, 7 Nov 2024 16:49:06 +0900 Subject: [PATCH 01/18] =?UTF-8?q?=E2=9E=95=20add=20:=20express=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=EC=9D=98=20=20Request=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=20=EC=9E=AC=EC=A0=95=EC=9D=98(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/types/express.d.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 BE/src/types/express.d.ts diff --git a/BE/src/types/express.d.ts b/BE/src/types/express.d.ts new file mode 100644 index 00000000..9cf89153 --- /dev/null +++ b/BE/src/types/express.d.ts @@ -0,0 +1,11 @@ +import { Request as Req } from 'express'; +import { UUID } from 'crypto'; + +declare module 'express' { + interface Request extends Req { + user: { + kakaoId?: number; + userId?: UUID; + }; + } +} From 0a276f9bb58f07e6266b23bb53c5a621a4ee8b08 Mon Sep 17 00:00:00 2001 From: jinddings Date: Thu, 7 Nov 2024 17:03:40 +0900 Subject: [PATCH 02/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20typeOrmM?= =?UTF-8?q?odule=EC=9D=B4=20=EB=AA=A8=EB=93=A0=20entity=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/app.module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BE/src/app.module.ts b/BE/src/app.module.ts index 36681ad9..1628ef25 100644 --- a/BE/src/app.module.ts +++ b/BE/src/app.module.ts @@ -5,7 +5,6 @@ import { ScheduleModule } from '@nestjs/schedule'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; -import { User } from './auth/user.entity'; import { StockIndexModule } from './stock/index/stock.index.module'; import { SocketService } from './websocket/socket.service'; import { SocketGateway } from './websocket/socket.gateway'; @@ -23,7 +22,7 @@ import { KoreaInvestmentModule } from './koreaInvestment/korea.investment.module username: process.env.DB_USERNAME, password: process.env.DB_PASSWD, database: process.env.DB_DATABASE, - entities: [User], + entities: [__dirname + '/../**/*.entity.{js,ts}'], synchronize: true, }), KoreaInvestmentModule, From 4357a549cee01d89ffca865a492cc095f851a154 Mon Sep 17 00:00:00 2001 From: jinddings Date: Thu, 7 Nov 2024 17:48:16 +0900 Subject: [PATCH 03/18] =?UTF-8?q?=E2=9C=A8=20feat=20:=20refresh=20Token=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/app.module.ts | 12 +---- BE/src/auth/auth.controller.ts | 1 + BE/src/auth/auth.module.ts | 16 +++++-- BE/src/auth/auth.service.ts | 64 +++++++++++++++++++++++++- BE/src/auth/dto/authCredentials.dto.ts | 40 +++++++++++----- BE/src/auth/user.entity.ts | 6 +++ BE/src/auth/user.repository.ts | 13 ++++++ BE/src/configs/typeorm.config.ts | 15 ++++++ BE/tsconfig.json | 3 +- 9 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 BE/src/configs/typeorm.config.ts diff --git a/BE/src/app.module.ts b/BE/src/app.module.ts index 1628ef25..b161c923 100644 --- a/BE/src/app.module.ts +++ b/BE/src/app.module.ts @@ -10,21 +10,13 @@ import { SocketService } from './websocket/socket.service'; import { SocketGateway } from './websocket/socket.gateway'; import { StockTopfiveModule } from './stock/topfive/stock.topfive.module'; import { KoreaInvestmentModule } from './koreaInvestment/korea.investment.module'; +import { typeOrmConfig } from './configs/typeorm.config'; @Module({ imports: [ ScheduleModule.forRoot(), ConfigModule.forRoot(), - TypeOrmModule.forRoot({ - type: 'mysql', // 데이터베이스 타입 - host: process.env.DB_HOST, - port: 3306, - username: process.env.DB_USERNAME, - password: process.env.DB_PASSWD, - database: process.env.DB_DATABASE, - entities: [__dirname + '/../**/*.entity.{js,ts}'], - synchronize: true, - }), + TypeOrmModule.forRoot(typeOrmConfig), KoreaInvestmentModule, AuthModule, StockIndexModule, diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index 46eb0d6a..2bfffe99 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -11,6 +11,7 @@ import { AuthGuard } from '@nestjs/passport'; import { ApiOperation } from '@nestjs/swagger'; import { AuthService } from './auth.service'; import { AuthCredentialsDto } from './dto/authCredentials.dto'; +import { Request } from 'express'; @Controller('auth') export class AuthController { diff --git a/BE/src/auth/auth.module.ts b/BE/src/auth/auth.module.ts index a3ba6f04..dac8c17f 100644 --- a/BE/src/auth/auth.module.ts +++ b/BE/src/auth/auth.module.ts @@ -7,16 +7,22 @@ import { AuthService } from './auth.service'; import { User } from './user.entity'; import { UserRepository } from './user.repository'; import { JwtStrategy } from './jwt.strategy'; +import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ TypeOrmModule.forFeature([User]), + ConfigModule, PassportModule.register({ defaultStrategy: 'jwt' }), - JwtModule.register({ - secret: 'Juga16', - signOptions: { - expiresIn: 3600, - }, + JwtModule.registerAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => ({ + secret: configService.get('JWT_SECRET'), + signOptions: { + expiresIn: configService.get('JWT_ACCESS_EXPIRATION_TIME'), + }, + }), + inject: [ConfigService], }), ], controllers: [AuthController], diff --git a/BE/src/auth/auth.service.ts b/BE/src/auth/auth.service.ts index cbc4a69f..00738ec5 100644 --- a/BE/src/auth/auth.service.ts +++ b/BE/src/auth/auth.service.ts @@ -4,6 +4,7 @@ import { JwtService } from '@nestjs/jwt'; import * as bcrypt from 'bcrypt'; import { UserRepository } from './user.repository'; import { AuthCredentialsDto } from './dto/authCredentials.dto'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { @@ -11,6 +12,7 @@ export class AuthService { @InjectRepository(UserRepository) private userRepository: UserRepository, private jwtService: JwtService, + private readonly configService: ConfigService, ) {} async signUp(authCredentialsDto: AuthCredentialsDto): Promise { @@ -24,10 +26,68 @@ export class AuthService { const user = await this.userRepository.findOne({ where: { email } }); if (user && (await bcrypt.compare(password, user.password))) { - const payload = { email }; - const accessToken = this.jwtService.sign(payload); + const { accessToken, refreshToken } = + await this.getJWTToken(authCredentialsDto); + + this.setCurrentRefreshToken(refreshToken, user.id); + return { accessToken }; } throw new UnauthorizedException('Please check your login credentials'); } + + async getJWTToken(authCredentialsDto: AuthCredentialsDto) { + const accessToken = await this.generateAccessToken(authCredentialsDto); + const refreshToken = await this.generateRefreshToken(authCredentialsDto); + return { accessToken, refreshToken }; + } + + async generateAccessToken( + authCredentialsDto: AuthCredentialsDto, + ): Promise { + return authCredentialsDto.email + ? this.jwtService.sign({ email: authCredentialsDto.email }) + : this.jwtService.sign({ kakaoId: authCredentialsDto.kakaoId }); + } + + async generateRefreshToken( + authCredentialsDto: AuthCredentialsDto, + ): Promise { + if (authCredentialsDto.email) { + return this.jwtService.sign( + { email: authCredentialsDto.email }, + { + secret: this.configService.get('JWT_REFRESH_SECRET'), + expiresIn: this.configService.get( + 'JWT_REFRESH_EXPIRATION_TIME', + ), + }, + ); + } else { + return this.jwtService.sign( + { kakaoId: authCredentialsDto.kakaoId }, + { + secret: this.configService.get('JWT_REFRESH_SECRET'), + expiresIn: this.configService.get( + 'JWT_REFRESH_EXPIRATION_TIME', + ), + }, + ); + } + } + + async setCurrentRefreshToken(refreshToken: string, userId: number) { + const currentDate = new Date(); + const salt = await bcrypt.genSalt(); + const currentRefreshToken = await bcrypt.hash(refreshToken, salt); + const currentRefreshTokenExpiresAt = new Date( + currentDate.getTime() + + parseInt(this.configService.get('JWT_REFRESH_EXPIRATION_TIME')), + ); + + this.userRepository.update(userId, { + currentRefreshToken, + currentRefreshTokenExpiresAt, + }); + } } diff --git a/BE/src/auth/dto/authCredentials.dto.ts b/BE/src/auth/dto/authCredentials.dto.ts index fb1199ed..07332ed8 100644 --- a/BE/src/auth/dto/authCredentials.dto.ts +++ b/BE/src/auth/dto/authCredentials.dto.ts @@ -1,27 +1,43 @@ -import { IsString, Matches, MaxLength, MinLength } from 'class-validator'; +import { + IsString, + Matches, + MaxLength, + MinLength, + IsEmail, + ValidateNested, + IsOptional, +} from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class AuthCredentialsDto { @ApiProperty({ description: '유저 이메일', - minLength: 4, - maxLength: 20, - type: 'string', }) @IsString() - @MinLength(4) - @MaxLength(20) - email: string; + email?: string; @ApiProperty({ description: '유저 비밀번호', - minLength: 4, - maxLength: 20, - type: 'string', }) @IsString() @MinLength(4) @MaxLength(20) - @Matches(/^[a-zA-Z0-9]*$/) - password: string; + @Matches(/^[a-zA-Z0-9]*$/, { + message: '비밀번호는 영문과 숫자만 사용가능합니다', + }) + password?: string; + + @ApiProperty({ + description: '카카오 ID', + }) + @IsString() + @IsOptional() + kakaoId?: string; + + @ApiProperty({ + description: '카카오 액세스 토큰', + }) + @IsString() + @IsOptional() + kakaoAccessToken?: string; } diff --git a/BE/src/auth/user.entity.ts b/BE/src/auth/user.entity.ts index cf6b130a..fbdacb39 100644 --- a/BE/src/auth/user.entity.ts +++ b/BE/src/auth/user.entity.ts @@ -16,4 +16,10 @@ export class User extends BaseEntity { @Column({ default: -1 }) kakaoId: number; + + @Column({ default: '' }) + currentRefreshToken: string; + + @Column({ type: 'datetime', nullable: true }) + currentRefreshTokenExpiresAt: Date; } diff --git a/BE/src/auth/user.repository.ts b/BE/src/auth/user.repository.ts index 0a23f980..ac571fe2 100644 --- a/BE/src/auth/user.repository.ts +++ b/BE/src/auth/user.repository.ts @@ -18,4 +18,17 @@ export class UserRepository extends Repository { const user = this.create({ email, password: hashedPassword }); await this.save(user); } + + async updateUserWithRefreshToken( + id: number, + { + refreshToken, + refreshTokenExpiresAt, + }: { refreshToken: string; refreshTokenExpiresAt: Date }, + ) { + const user = await this.findOne({ where: { id } }); + user.currentRefreshToken = refreshToken; + user.currentRefreshTokenExpiresAt = refreshTokenExpiresAt; + await this.save(user); + } } diff --git a/BE/src/configs/typeorm.config.ts b/BE/src/configs/typeorm.config.ts new file mode 100644 index 00000000..dd42cdbe --- /dev/null +++ b/BE/src/configs/typeorm.config.ts @@ -0,0 +1,15 @@ +import { TypeOrmModuleOptions } from '@nestjs/typeorm'; +import * as dotenv from 'dotenv'; + +dotenv.config(); + +export const typeOrmConfig: TypeOrmModuleOptions = { + type: 'mysql', + host: process.env.DB_HOST, + port: 3306, + username: process.env.DB_USERNAME, + password: process.env.DB_PASSWD, + database: process.env.DB_DATABASE, + entities: [__dirname + '/../**/*.entity{.js,.ts}'], + synchronize: true, +}; diff --git a/BE/tsconfig.json b/BE/tsconfig.json index 95f5641c..52a4d6a7 100644 --- a/BE/tsconfig.json +++ b/BE/tsconfig.json @@ -16,6 +16,7 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "typeRoots": ["node_modules/@types", "./src/types"] } } From a9c95d76a624a605a10a382d464a7e7da9b69de9 Mon Sep 17 00:00:00 2001 From: jinddings Date: Thu, 7 Nov 2024 18:52:23 +0900 Subject: [PATCH 04/18] =?UTF-8?q?=E2=9C=A8=20feat=20:=20Token=20Refresh=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20API=20=EA=B5=AC=ED=98=84(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/package-lock.json | 33 +++++++++++++++++++++++++ BE/package.json | 4 ++- BE/src/auth/auth.controller.ts | 45 +++++++++++++++++++++++++++++++--- BE/src/auth/auth.service.ts | 39 +++++++++++++++++++++++++++-- BE/src/auth/user.entity.ts | 10 ++++++++ BE/src/main.ts | 2 ++ 6 files changed, 126 insertions(+), 7 deletions(-) diff --git a/BE/package-lock.json b/BE/package-lock.json index cb72a103..4f1add7a 100644 --- a/BE/package-lock.json +++ b/BE/package-lock.json @@ -20,11 +20,13 @@ "@nestjs/swagger": "^8.0.1", "@nestjs/typeorm": "^10.0.2", "@nestjs/websockets": "^10.4.7", + "@types/cookie-parser": "^1.4.7", "@types/passport-jwt": "^4.0.1", "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "cookie-parser": "^1.4.7", "cross-env": "^7.0.3", "docker": "^1.0.0", "dotenv": "^16.4.5", @@ -1961,6 +1963,15 @@ "version": "0.4.1", "license": "MIT" }, + "node_modules/@types/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cookiejar": { "version": "2.1.5", "dev": true, @@ -3782,6 +3793,28 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "license": "MIT" diff --git a/BE/package.json b/BE/package.json index 224dc019..8c4cf8f8 100644 --- a/BE/package.json +++ b/BE/package.json @@ -30,12 +30,14 @@ "@nestjs/schedule": "^4.1.1", "@nestjs/swagger": "^8.0.1", "@nestjs/typeorm": "^10.0.2", - "@types/passport-jwt": "^4.0.1", "@nestjs/websockets": "^10.4.7", + "@types/cookie-parser": "^1.4.7", + "@types/passport-jwt": "^4.0.1", "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "cookie-parser": "^1.4.7", "cross-env": "^7.0.3", "docker": "^1.0.0", "dotenv": "^16.4.5", diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index 2bfffe99..b95a78ae 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -3,19 +3,24 @@ import { Post, Get, Body, - Req, ValidationPipe, UseGuards, + Req, + Res, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation } from '@nestjs/swagger'; import { AuthService } from './auth.service'; import { AuthCredentialsDto } from './dto/authCredentials.dto'; -import { Request } from 'express'; +import { Request, Response } from 'express'; +import { ConfigService } from '@nestjs/config'; @Controller('auth') export class AuthController { - constructor(private authService: AuthService) {} + constructor( + private authService: AuthService, + private configService: ConfigService, + ) {} @ApiOperation({ summary: '회원 가입 API' }) @Post('/signup') @@ -24,7 +29,7 @@ export class AuthController { } @ApiOperation({ summary: '로그인 API' }) - @Get('/login') + @Post('/login') loginWithCredentials( @Body(ValidationPipe) authCredentialsDto: AuthCredentialsDto, ) { @@ -37,4 +42,36 @@ export class AuthController { test(@Req() req: Request) { return req; } + + @ApiOperation({ summary: 'Kakao 로그인 API' }) + @Get('/kakao') + async kakaoLogin( + @Body() authCredentialsDto: AuthCredentialsDto, + @Res() res: Response, + ) { + const { accessToken, refreshToken } = + await this.authService.kakaoLoginUser(authCredentialsDto); + + res.cookie('refreshToken', refreshToken, { httpOnly: true }); + res.cookie('accessToken', accessToken, { httpOnly: true }); + res.cookie('isRefreshToken', true, { httpOnly: true }); + return res.redirect(this.configService.get('CLIENT_URL')); + } + + @ApiOperation({ summary: 'Refresh Token 요청 API' }) + @Get('/refresh') + async refresh(@Req() req: Request, @Res() res: Response) { + const refreshToken = req.cookies['refreshToken']; + const accessToken = req.cookies['accessToken']; + + if (!refreshToken || !accessToken) { + return res.status(401).send(); + } + + const newAccessToken = await this.authService.refreshToken(refreshToken); + + res.cookie('accessToken', newAccessToken, { httpOnly: true }); + res.cookie('isRefreshToken', true, { httpOnly: true }); + return res.status(200).send(); + } } diff --git a/BE/src/auth/auth.service.ts b/BE/src/auth/auth.service.ts index 00738ec5..026ff074 100644 --- a/BE/src/auth/auth.service.ts +++ b/BE/src/auth/auth.service.ts @@ -21,7 +21,7 @@ export class AuthService { async loginUser( authCredentialsDto: AuthCredentialsDto, - ): Promise<{ accessToken: string }> { + ): Promise<{ accessToken: string; refreshToken: string }> { const { email, password } = authCredentialsDto; const user = await this.userRepository.findOne({ where: { email } }); @@ -31,11 +31,16 @@ export class AuthService { this.setCurrentRefreshToken(refreshToken, user.id); - return { accessToken }; + return { accessToken, refreshToken }; } throw new UnauthorizedException('Please check your login credentials'); } + async kakaoLoginUser( + authCredentialsDto: AuthCredentialsDto, + ): Promise<{ accessToken: string; refreshToken: string }> { + return await this.getJWTToken(authCredentialsDto); + } async getJWTToken(authCredentialsDto: AuthCredentialsDto) { const accessToken = await this.generateAccessToken(authCredentialsDto); const refreshToken = await this.generateRefreshToken(authCredentialsDto); @@ -90,4 +95,34 @@ export class AuthService { currentRefreshTokenExpiresAt, }); } + + async refreshToken(refreshToken: string): Promise { + try { + const decodedRefreshToken = this.jwtService.verify(refreshToken, { + secret: this.configService.get('JWT_REFRESH_SECRET'), + }); + + const user = decodedRefreshToken.email + ? await this.userRepository.findOne({ + where: { email: decodedRefreshToken.email }, + }) + : await this.userRepository.findOne({ + where: { kakaoId: decodedRefreshToken.kakaoId }, + }); + + const isRefreshTokenMatching = await bcrypt.compare( + refreshToken, + user.currentRefreshToken, + ); + + if (!isRefreshTokenMatching) { + throw new UnauthorizedException('Invalid Token'); + } + + const accessToken = this.generateAccessToken(user.toAuthCredentialsDto()); + return accessToken; + } catch (error) { + throw new UnauthorizedException('Invalid Token'); + } + } } diff --git a/BE/src/auth/user.entity.ts b/BE/src/auth/user.entity.ts index fbdacb39..4dfc6541 100644 --- a/BE/src/auth/user.entity.ts +++ b/BE/src/auth/user.entity.ts @@ -1,4 +1,5 @@ import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { AuthCredentialsDto } from './dto/authCredentials.dto'; @Entity() export class User extends BaseEntity { @@ -22,4 +23,13 @@ export class User extends BaseEntity { @Column({ type: 'datetime', nullable: true }) currentRefreshTokenExpiresAt: Date; + + toAuthCredentialsDto(): AuthCredentialsDto { + if (this.kakaoId === -1) { + return { + email: this.email, + password: this.password, + }; + } + } } diff --git a/BE/src/main.ts b/BE/src/main.ts index bbc099aa..37ceb7cf 100644 --- a/BE/src/main.ts +++ b/BE/src/main.ts @@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core'; import { Logger } from '@nestjs/common'; import { AppModule } from './app.module'; import { setupSwagger } from './util/swagger'; +import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -14,6 +15,7 @@ async function bootstrap() { optionsSuccessStatus: 204, }); + app.use(cookieParser()); await app.listen(process.env.PORT ?? 3000); } From 91607bdd2c058e7f97a5041b5bb2ee1d5ca27418 Mon Sep 17 00:00:00 2001 From: jinddings Date: Thu, 7 Nov 2024 19:05:58 +0900 Subject: [PATCH 05/18] =?UTF-8?q?=E2=9E=95=20add=20:=20kakao=20strategy=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/package-lock.json | 45 ++++++++++++++++++++++ BE/package.json | 1 + BE/src/auth/auth.controller.ts | 3 +- BE/src/auth/auth.module.ts | 5 ++- BE/src/auth/{ => strategy}/jwt.strategy.ts | 4 +- BE/src/auth/strategy/kakao.strategy.ts | 32 +++++++++++++++ 6 files changed, 85 insertions(+), 5 deletions(-) rename BE/src/auth/{ => strategy}/jwt.strategy.ts (88%) create mode 100644 BE/src/auth/strategy/kakao.strategy.ts diff --git a/BE/package-lock.json b/BE/package-lock.json index 4f1add7a..5cdd9bb9 100644 --- a/BE/package-lock.json +++ b/BE/package-lock.json @@ -34,6 +34,7 @@ "mysql2": "^3.11.3", "passport": "^0.7.0", "passport-jwt": "^4.0.1", + "passport-kakao": "^1.0.1", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "socket.io": "^4.8.1", @@ -8332,6 +8333,12 @@ "set-blocking": "^2.0.0" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/oauth-sign": { "version": "0.8.2", "license": "Apache-2.0", @@ -8644,6 +8651,29 @@ "passport-strategy": "^1.0.0" } }, + "node_modules/passport-kakao": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/passport-kakao/-/passport-kakao-1.0.1.tgz", + "integrity": "sha512-uItaYRVrTHL6iGPMnMZvPa/O1GrAdh/V6EMjOHcFlQcVroZ9wgG7BZ5PonMNJCxfHQ3L2QVNRnzhKWUzSsumbw==", + "license": "MIT", + "dependencies": { + "passport-oauth2": "~1.1.2", + "pkginfo": "~0.3.0" + } + }, + "node_modules/passport-oauth2": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.1.2.tgz", + "integrity": "sha512-wpsGtJDHHQUjyc9WcV9FFB0bphFExpmKtzkQrxpH1vnSr6RcWa3ZEGHx/zGKAh2PN7Po9TKYB1fJeOiIBspNPA==", + "dependencies": { + "oauth": "0.9.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -8802,6 +8832,15 @@ "node": ">=8" } }, + "node_modules/pkginfo": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", + "integrity": "sha512-yO5feByMzAp96LtP58wvPKSbaKAi/1C4kV9XpTctr6EepnP6F33RBNOiVrdz9BrPA98U2BMFsTNHo44TWcbQ2A==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "dev": true, @@ -10934,6 +10973,12 @@ "node": ">=8" } }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", + "license": "MIT" + }, "node_modules/unbounded": { "version": "1.3.0", "license": "MIT", diff --git a/BE/package.json b/BE/package.json index 8c4cf8f8..52432b78 100644 --- a/BE/package.json +++ b/BE/package.json @@ -45,6 +45,7 @@ "mysql2": "^3.11.3", "passport": "^0.7.0", "passport-jwt": "^4.0.1", + "passport-kakao": "^1.0.1", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "socket.io": "^4.8.1", diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index b95a78ae..44f31114 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -38,13 +38,14 @@ export class AuthController { @ApiOperation({ summary: 'Token 인증 테스트 API' }) @Get('/test') - @UseGuards(AuthGuard()) + @UseGuards(AuthGuard('jwt')) test(@Req() req: Request) { return req; } @ApiOperation({ summary: 'Kakao 로그인 API' }) @Get('/kakao') + @UseGuards(AuthGuard('kakao')) async kakaoLogin( @Body() authCredentialsDto: AuthCredentialsDto, @Res() res: Response, diff --git a/BE/src/auth/auth.module.ts b/BE/src/auth/auth.module.ts index dac8c17f..87a398ba 100644 --- a/BE/src/auth/auth.module.ts +++ b/BE/src/auth/auth.module.ts @@ -6,8 +6,9 @@ import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { User } from './user.entity'; import { UserRepository } from './user.repository'; -import { JwtStrategy } from './jwt.strategy'; +import { JwtStrategy } from './strategy/jwt.strategy'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { KakaoStrategy } from './strategy/kakao.strategy'; @Module({ imports: [ @@ -26,7 +27,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; }), ], controllers: [AuthController], - providers: [AuthService, UserRepository, JwtStrategy], + providers: [AuthService, UserRepository, JwtStrategy, KakaoStrategy], exports: [JwtStrategy, PassportModule], }) export class AuthModule {} diff --git a/BE/src/auth/jwt.strategy.ts b/BE/src/auth/strategy/jwt.strategy.ts similarity index 88% rename from BE/src/auth/jwt.strategy.ts rename to BE/src/auth/strategy/jwt.strategy.ts index 350d622d..b60a0596 100644 --- a/BE/src/auth/jwt.strategy.ts +++ b/BE/src/auth/strategy/jwt.strategy.ts @@ -2,8 +2,8 @@ import { PassportStrategy } from '@nestjs/passport'; import { InjectRepository } from '@nestjs/typeorm'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { UserRepository } from './user.repository'; -import { User } from './user.entity'; +import { UserRepository } from '../user.repository'; +import { User } from '../user.entity'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { diff --git a/BE/src/auth/strategy/kakao.strategy.ts b/BE/src/auth/strategy/kakao.strategy.ts new file mode 100644 index 00000000..f47b3eeb --- /dev/null +++ b/BE/src/auth/strategy/kakao.strategy.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PassportStrategy } from '@nestjs/passport'; +import { Profile, Strategy } from 'passport-kakao'; + +@Injectable() +export class KakaoStrategy extends PassportStrategy(Strategy) { + constructor(private readonly configService: ConfigService) { + super({ + clientID: configService.get('KAKAO_CLIENT_ID'), + clientSecret: '', + callbackURL: `${configService.get('BACKEND_URL')}/auth/kakao`, + }); + } + + async validate( + accessToken: string, + refreshToken: string, + profile: Profile, + done: (error: any, user?: any, info?: any) => void, + ) { + try { + const { _json } = profile; + const user = { + kakaoId: _json.id, + }; + done(null, user); + } catch (error) { + done(error); + } + } +} From b8720660909c042b2b2ad0f69bad3c6634a98772 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 13:00:24 +0900 Subject: [PATCH 06/18] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20lint=20?= =?UTF-8?q?=EC=97=90=20=EC=9C=84=EB=B0=B0=EB=90=98=EB=8A=94=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/package-lock.json | 3 ++ BE/package.json | 1 + BE/src/app.module.ts | 1 - BE/src/auth/auth.controller.ts | 16 +++++---- BE/src/auth/auth.module.ts | 4 +-- BE/src/auth/auth.service.ts | 41 +++++++++++----------- BE/src/auth/dto/auth-credentials.dto.ts | 2 -- BE/src/auth/strategy/kakao.strategy.ts | 45 +++++++++++++++++++------ BE/src/auth/user.entity.ts | 2 ++ BE/src/configs/typeorm.config.ts | 2 +- BE/src/main.ts | 2 +- 11 files changed, 75 insertions(+), 44 deletions(-) diff --git a/BE/package-lock.json b/BE/package-lock.json index 5cdd9bb9..f9b6300f 100644 --- a/BE/package-lock.json +++ b/BE/package-lock.json @@ -30,6 +30,7 @@ "cross-env": "^7.0.3", "docker": "^1.0.0", "dotenv": "^16.4.5", + "express": "^4.21.1", "fastify-swagger": "^5.1.1", "mysql2": "^3.11.3", "passport": "^0.7.0", @@ -5133,6 +5134,8 @@ }, "node_modules/express": { "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", diff --git a/BE/package.json b/BE/package.json index 52432b78..86c3538b 100644 --- a/BE/package.json +++ b/BE/package.json @@ -41,6 +41,7 @@ "cross-env": "^7.0.3", "docker": "^1.0.0", "dotenv": "^16.4.5", + "express": "^4.21.1", "fastify-swagger": "^5.1.1", "mysql2": "^3.11.3", "passport": "^0.7.0", diff --git a/BE/src/app.module.ts b/BE/src/app.module.ts index 639c4d3d..8a1dab3d 100644 --- a/BE/src/app.module.ts +++ b/BE/src/app.module.ts @@ -5,7 +5,6 @@ import { ScheduleModule } from '@nestjs/schedule'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; -import { User } from './auth/user.entity'; import { StockIndexModule } from './stock/index/stock-index.module'; import { StockTopfiveModule } from './stock/topfive/stock-topfive.module'; import { KoreaInvestmentModule } from './koreaInvestment/korea-investment.module'; diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index d88c7c39..631aa0e2 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -10,10 +10,10 @@ import { } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation } from '@nestjs/swagger'; -import { AuthService } from './auth.service'; -import { AuthCredentialsDto } from './dto/auth-credentials.dto'; import { Request, Response } from 'express'; import { ConfigService } from '@nestjs/config'; +import { AuthService } from './auth.service'; +import { AuthCredentialsDto } from './dto/auth-credentials.dto'; @Controller('auth') export class AuthController { @@ -62,13 +62,15 @@ export class AuthController { @ApiOperation({ summary: 'Refresh Token 요청 API' }) @Get('/refresh') async refresh(@Req() req: Request, @Res() res: Response) { - const refreshToken = req.cookies['refreshToken']; - const accessToken = req.cookies['accessToken']; - - if (!refreshToken || !accessToken) { - return res.status(401).send(); + if ( + typeof req.cookies.refreshToken !== 'string' || + typeof req.cookies.accessToken !== 'string' + ) { + return res.status(400).send(); } + const { refreshToken } = req.cookies; + const newAccessToken = await this.authService.refreshToken(refreshToken); res.cookie('accessToken', newAccessToken, { httpOnly: true }); diff --git a/BE/src/auth/auth.module.ts b/BE/src/auth/auth.module.ts index 87a398ba..522e5199 100644 --- a/BE/src/auth/auth.module.ts +++ b/BE/src/auth/auth.module.ts @@ -2,12 +2,12 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; +import { ConfigModule, ConfigService } from '@nestjs/config'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { User } from './user.entity'; import { UserRepository } from './user.repository'; import { JwtStrategy } from './strategy/jwt.strategy'; -import { ConfigModule, ConfigService } from '@nestjs/config'; import { KakaoStrategy } from './strategy/kakao.strategy'; @Module({ @@ -17,7 +17,7 @@ import { KakaoStrategy } from './strategy/kakao.strategy'; PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.registerAsync({ imports: [ConfigModule], - useFactory: async (configService: ConfigService) => ({ + useFactory: (configService: ConfigService) => ({ secret: configService.get('JWT_SECRET'), signOptions: { expiresIn: configService.get('JWT_ACCESS_EXPIRATION_TIME'), diff --git a/BE/src/auth/auth.service.ts b/BE/src/auth/auth.service.ts index 1b913fdf..2de79cd8 100644 --- a/BE/src/auth/auth.service.ts +++ b/BE/src/auth/auth.service.ts @@ -2,9 +2,9 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { JwtService } from '@nestjs/jwt'; import * as bcrypt from 'bcrypt'; +import { ConfigService } from '@nestjs/config'; import { UserRepository } from './user.repository'; import { AuthCredentialsDto } from './dto/auth-credentials.dto'; -import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { @@ -29,7 +29,7 @@ export class AuthService { const { accessToken, refreshToken } = await this.getJWTToken(authCredentialsDto); - this.setCurrentRefreshToken(refreshToken, user.id); + await this.setCurrentRefreshToken(refreshToken, user.id); return { accessToken, refreshToken }; } @@ -39,8 +39,9 @@ export class AuthService { async kakaoLoginUser( authCredentialsDto: AuthCredentialsDto, ): Promise<{ accessToken: string; refreshToken: string }> { - return await this.getJWTToken(authCredentialsDto); + return this.getJWTToken(authCredentialsDto); } + async getJWTToken(authCredentialsDto: AuthCredentialsDto) { const accessToken = await this.generateAccessToken(authCredentialsDto); const refreshToken = await this.generateRefreshToken(authCredentialsDto); @@ -51,15 +52,15 @@ export class AuthService { authCredentialsDto: AuthCredentialsDto, ): Promise { return authCredentialsDto.email - ? this.jwtService.sign({ email: authCredentialsDto.email }) - : this.jwtService.sign({ kakaoId: authCredentialsDto.kakaoId }); + ? this.jwtService.signAsync({ email: authCredentialsDto.email }) + : this.jwtService.signAsync({ kakaoId: authCredentialsDto.kakaoId }); } async generateRefreshToken( authCredentialsDto: AuthCredentialsDto, ): Promise { if (authCredentialsDto.email) { - return this.jwtService.sign( + return this.jwtService.signAsync( { email: authCredentialsDto.email }, { secret: this.configService.get('JWT_REFRESH_SECRET'), @@ -68,17 +69,16 @@ export class AuthService { ), }, ); - } else { - return this.jwtService.sign( - { kakaoId: authCredentialsDto.kakaoId }, - { - secret: this.configService.get('JWT_REFRESH_SECRET'), - expiresIn: this.configService.get( - 'JWT_REFRESH_EXPIRATION_TIME', - ), - }, - ); } + return this.jwtService.signAsync( + { kakaoId: authCredentialsDto.kakaoId }, + { + secret: this.configService.get('JWT_REFRESH_SECRET'), + expiresIn: this.configService.get( + 'JWT_REFRESH_EXPIRATION_TIME', + ), + }, + ); } async setCurrentRefreshToken(refreshToken: string, userId: number) { @@ -87,10 +87,13 @@ export class AuthService { const currentRefreshToken = await bcrypt.hash(refreshToken, salt); const currentRefreshTokenExpiresAt = new Date( currentDate.getTime() + - parseInt(this.configService.get('JWT_REFRESH_EXPIRATION_TIME')), + parseInt( + this.configService.get('JWT_REFRESH_EXPIRATION_TIME'), + 10, + ), ); - this.userRepository.update(userId, { + await this.userRepository.update(userId, { currentRefreshToken, currentRefreshTokenExpiresAt, }); @@ -120,7 +123,7 @@ export class AuthService { } const accessToken = this.generateAccessToken(user.toAuthCredentialsDto()); - return accessToken; + return await accessToken; } catch (error) { throw new UnauthorizedException('Invalid Token'); } diff --git a/BE/src/auth/dto/auth-credentials.dto.ts b/BE/src/auth/dto/auth-credentials.dto.ts index 07332ed8..31bc69b6 100644 --- a/BE/src/auth/dto/auth-credentials.dto.ts +++ b/BE/src/auth/dto/auth-credentials.dto.ts @@ -3,8 +3,6 @@ import { Matches, MaxLength, MinLength, - IsEmail, - ValidateNested, IsOptional, } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; diff --git a/BE/src/auth/strategy/kakao.strategy.ts b/BE/src/auth/strategy/kakao.strategy.ts index f47b3eeb..e69805dc 100644 --- a/BE/src/auth/strategy/kakao.strategy.ts +++ b/BE/src/auth/strategy/kakao.strategy.ts @@ -3,30 +3,53 @@ import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { Profile, Strategy } from 'passport-kakao'; +interface KakaoStrategyOptions { + clientID: string; + clientSecret: string; + callbackURL: string; +} + +interface KakaoProfile extends Profile { + id: number; + _json: { + id: number; + }; +} + +interface KakaoUser { + kakaoId: number; +} + @Injectable() -export class KakaoStrategy extends PassportStrategy(Strategy) { +export class KakaoStrategy extends PassportStrategy( + Strategy, + 'kakao', +) { constructor(private readonly configService: ConfigService) { - super({ - clientID: configService.get('KAKAO_CLIENT_ID'), + const options: KakaoStrategyOptions = { + clientID: configService.get('KAKAO_CLIENT_ID') || '', clientSecret: '', - callbackURL: `${configService.get('BACKEND_URL')}/auth/kakao`, - }); + callbackURL: `${configService.get('BACKEND_URL') || ''}/auth/kakao`, + }; + + super(options); } - async validate( + validate( accessToken: string, refreshToken: string, - profile: Profile, - done: (error: any, user?: any, info?: any) => void, + profile: KakaoProfile, + done: (error: Error, user?: KakaoUser) => void, ) { try { - const { _json } = profile; + // eslint-disable-next-line no-underscore-dangle + const kakaoId = profile._json.id; const user = { - kakaoId: _json.id, + kakaoId, }; done(null, user); } catch (error) { - done(error); + done(error instanceof Error ? error : new Error(String(error))); } } } diff --git a/BE/src/auth/user.entity.ts b/BE/src/auth/user.entity.ts index e052393a..8c574cd4 100644 --- a/BE/src/auth/user.entity.ts +++ b/BE/src/auth/user.entity.ts @@ -31,5 +31,7 @@ export class User extends BaseEntity { password: this.password, }; } + + throw new Error('Cannot convert Kakao user to auth credentials'); } } diff --git a/BE/src/configs/typeorm.config.ts b/BE/src/configs/typeorm.config.ts index dd42cdbe..c10d56ef 100644 --- a/BE/src/configs/typeorm.config.ts +++ b/BE/src/configs/typeorm.config.ts @@ -10,6 +10,6 @@ export const typeOrmConfig: TypeOrmModuleOptions = { username: process.env.DB_USERNAME, password: process.env.DB_PASSWD, database: process.env.DB_DATABASE, - entities: [__dirname + '/../**/*.entity{.js,.ts}'], + entities: [`${__dirname}/../**/*.entity{.js,.ts}`], synchronize: true, }; diff --git a/BE/src/main.ts b/BE/src/main.ts index 37ceb7cf..bc11d55f 100644 --- a/BE/src/main.ts +++ b/BE/src/main.ts @@ -1,8 +1,8 @@ import { NestFactory } from '@nestjs/core'; import { Logger } from '@nestjs/common'; +import * as cookieParser from 'cookie-parser'; import { AppModule } from './app.module'; import { setupSwagger } from './util/swagger'; -import * as cookieParser from 'cookie-parser'; async function bootstrap() { const app = await NestFactory.create(AppModule); From bbcb40ea467711c6c65c000be71aee0b5b89ff22 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 15:24:03 +0900 Subject: [PATCH 07/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20FE=20lin?= =?UTF-8?q?t=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/TopFive/Nav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FE/src/components/TopFive/Nav.tsx b/FE/src/components/TopFive/Nav.tsx index 941c8249..5fc2bef3 100644 --- a/FE/src/components/TopFive/Nav.tsx +++ b/FE/src/components/TopFive/Nav.tsx @@ -43,7 +43,7 @@ export default function Nav() { key={market} ref={(el) => (buttonRefs.current[index] = el)} onClick={() => handleMarketChange(market)} - className={`relative px-2 py-2`} + className={'relative px-2 py-2'} > {market} From 7f20998350f66ddfe93943cfdc20d0696a766aa7 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 16:39:30 +0900 Subject: [PATCH 08/18] =?UTF-8?q?=F0=9F=94=A7=20fix=20:=20refreshToken,=20?= =?UTF-8?q?accessToken=20response=20=EB=B0=A9=EC=8B=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/auth/auth.controller.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index 631aa0e2..aa3085dc 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -30,10 +30,17 @@ export class AuthController { @ApiOperation({ summary: '로그인 API' }) @Post('/login') - loginWithCredentials( + async loginWithCredentials( @Body(ValidationPipe) authCredentialsDto: AuthCredentialsDto, + @Res() res: Response, ) { - return this.authService.loginUser(authCredentialsDto); + const { accessToken, refreshToken } = + await this.authService.loginUser(authCredentialsDto); + + res.cookie('refreshToken', refreshToken, { httpOnly: true }); + res.cookie('isRefreshToken', true, { httpOnly: true }); + res.json(accessToken); + return res.redirect(this.configService.get('CLIENT_URL')); } @ApiOperation({ summary: 'Token 인증 테스트 API' }) @@ -54,9 +61,8 @@ export class AuthController { await this.authService.kakaoLoginUser(authCredentialsDto); res.cookie('refreshToken', refreshToken, { httpOnly: true }); - res.cookie('accessToken', accessToken, { httpOnly: true }); res.cookie('isRefreshToken', true, { httpOnly: true }); - return res.redirect(this.configService.get('CLIENT_URL')); + return res.status(200).json({ accessToken }); } @ApiOperation({ summary: 'Refresh Token 요청 API' }) From e1dda3fb46ced9779e9ae21029ff906a016c787e Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 16:48:54 +0900 Subject: [PATCH 09/18] =?UTF-8?q?=F0=9F=94=A7=20fix=20:=20jwt=20secret=20.?= =?UTF-8?q?env=EC=97=90=EC=84=9C=20=EC=9D=BD=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20refresh=20API=20=EC=9D=91=EB=8B=B5=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/auth/auth.controller.ts | 10 +++++----- BE/src/auth/strategy/jwt.strategy.ts | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index aa3085dc..684384be 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -7,6 +7,7 @@ import { UseGuards, Req, Res, + UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation } from '@nestjs/swagger'; @@ -39,8 +40,7 @@ export class AuthController { res.cookie('refreshToken', refreshToken, { httpOnly: true }); res.cookie('isRefreshToken', true, { httpOnly: true }); - res.json(accessToken); - return res.redirect(this.configService.get('CLIENT_URL')); + return res.status(200).json({ accessToken }); } @ApiOperation({ summary: 'Token 인증 테스트 API' }) @@ -72,15 +72,15 @@ export class AuthController { typeof req.cookies.refreshToken !== 'string' || typeof req.cookies.accessToken !== 'string' ) { - return res.status(400).send(); + throw new UnauthorizedException('Invalid refresh token'); } const { refreshToken } = req.cookies; const newAccessToken = await this.authService.refreshToken(refreshToken); - res.cookie('accessToken', newAccessToken, { httpOnly: true }); + res.cookie('refreshToken', refreshToken, { httpOnly: true }); res.cookie('isRefreshToken', true, { httpOnly: true }); - return res.status(200).send(); + return res.status(200).json({ accessToken: newAccessToken }); } } diff --git a/BE/src/auth/strategy/jwt.strategy.ts b/BE/src/auth/strategy/jwt.strategy.ts index b60a0596..a6393996 100644 --- a/BE/src/auth/strategy/jwt.strategy.ts +++ b/BE/src/auth/strategy/jwt.strategy.ts @@ -4,14 +4,16 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UserRepository } from '../user.repository'; import { User } from '../user.entity'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor( @InjectRepository(UserRepository) private userRepository: UserRepository, + private readonly configService: ConfigService, ) { super({ - secretOrKey: 'Juga16', + secretOrKey: configService.get('JWT_SECRET'), jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), }); } From ddbbd79b466bd000a3a92e7adea57331180293bd Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 17:04:34 +0900 Subject: [PATCH 10/18] =?UTF-8?q?=F0=9F=94=A7=20fix=20:=20lint=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/app.module.ts | 1 - BE/src/auth/dto/auth-credentials.dto.ts | 17 ----------------- BE/src/auth/strategy/jwt.strategy.ts | 2 +- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/BE/src/app.module.ts b/BE/src/app.module.ts index 639c4d3d..8a1dab3d 100644 --- a/BE/src/app.module.ts +++ b/BE/src/app.module.ts @@ -5,7 +5,6 @@ import { ScheduleModule } from '@nestjs/schedule'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; -import { User } from './auth/user.entity'; import { StockIndexModule } from './stock/index/stock-index.module'; import { StockTopfiveModule } from './stock/topfive/stock-topfive.module'; import { KoreaInvestmentModule } from './koreaInvestment/korea-investment.module'; diff --git a/BE/src/auth/dto/auth-credentials.dto.ts b/BE/src/auth/dto/auth-credentials.dto.ts index de6420bf..31bc69b6 100644 --- a/BE/src/auth/dto/auth-credentials.dto.ts +++ b/BE/src/auth/dto/auth-credentials.dto.ts @@ -5,7 +5,6 @@ import { MinLength, IsOptional, } from 'class-validator'; -import { IsString, Matches, MaxLength, MinLength } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class AuthCredentialsDto { @@ -17,20 +16,6 @@ export class AuthCredentialsDto { @ApiProperty({ description: '유저 비밀번호', - minLength: 4, - maxLength: 20, - type: 'string', - }) - @IsString() - @MinLength(4) - @MaxLength(20) - email: string; - - @ApiProperty({ - description: '유저 비밀번호', - minLength: 4, - maxLength: 20, - type: 'string', }) @IsString() @MinLength(4) @@ -53,6 +38,4 @@ export class AuthCredentialsDto { @IsString() @IsOptional() kakaoAccessToken?: string; - @Matches(/^[a-zA-Z0-9]*$/) - password: string; } diff --git a/BE/src/auth/strategy/jwt.strategy.ts b/BE/src/auth/strategy/jwt.strategy.ts index a6393996..e6e85d05 100644 --- a/BE/src/auth/strategy/jwt.strategy.ts +++ b/BE/src/auth/strategy/jwt.strategy.ts @@ -2,9 +2,9 @@ import { PassportStrategy } from '@nestjs/passport'; import { InjectRepository } from '@nestjs/typeorm'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { UserRepository } from '../user.repository'; import { User } from '../user.entity'; -import { ConfigService } from '@nestjs/config'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { From 14aa4eb65dc69baea6b74d8765579c3f34276378 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 17:22:38 +0900 Subject: [PATCH 11/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20deploy?= =?UTF-8?q?=20alpha=20=EB=94=94=EB=B2=84=EA=B7=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deply-alpha.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deply-alpha.yml b/.github/workflows/deply-alpha.yml index 155e349b..b6151cb5 100644 --- a/.github/workflows/deply-alpha.yml +++ b/.github/workflows/deply-alpha.yml @@ -24,6 +24,10 @@ jobs: steps: - uses: actions/checkout@v3 + - name: debug matrix + run: | + echo "Matrix app directory: ${{matrix.app.dir}}" + - name: Set up Node.js uses: actions/setup-node@v4 with: From 3f0992717581cd43931f7e8cd0beb12f1f55c73e Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 17:28:33 +0900 Subject: [PATCH 12/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20npm=20in?= =?UTF-8?q?stall=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deply-alpha.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deply-alpha.yml b/.github/workflows/deply-alpha.yml index b6151cb5..deda0f34 100644 --- a/.github/workflows/deply-alpha.yml +++ b/.github/workflows/deply-alpha.yml @@ -42,8 +42,9 @@ jobs: - name: Install dependencies working-directory: ./${{matrix.app.dir}} - run: npm ci - + run: | + npm cache clean --force + npm install - name: Run tests working-directory: ./${{matrix.app.dir}} run: npm test From 5061b7fecc236d6076c8e9ba55efa1170ec29d11 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 17:37:02 +0900 Subject: [PATCH 13/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20action?= =?UTF-8?q?=20=EB=B2=84=EC=A0=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deply-alpha.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deply-alpha.yml b/.github/workflows/deply-alpha.yml index deda0f34..908a4fc5 100644 --- a/.github/workflows/deply-alpha.yml +++ b/.github/workflows/deply-alpha.yml @@ -22,11 +22,7 @@ jobs: ] steps: - - uses: actions/checkout@v3 - - - name: debug matrix - run: | - echo "Matrix app directory: ${{matrix.app.dir}}" + - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 @@ -42,9 +38,8 @@ jobs: - name: Install dependencies working-directory: ./${{matrix.app.dir}} - run: | - npm cache clean --force - npm install + run: npm ci + - name: Run tests working-directory: ./${{matrix.app.dir}} run: npm test From 2cf4769cdb1cf4866b6c205b6ffb962323e14424 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 17:38:54 +0900 Subject: [PATCH 14/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20audit=20?= =?UTF-8?q?=EB=AC=B4=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deply-alpha.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deply-alpha.yml b/.github/workflows/deply-alpha.yml index 908a4fc5..5eebd458 100644 --- a/.github/workflows/deply-alpha.yml +++ b/.github/workflows/deply-alpha.yml @@ -38,7 +38,7 @@ jobs: - name: Install dependencies working-directory: ./${{matrix.app.dir}} - run: npm ci + run: npm ci --no-audit - name: Run tests working-directory: ./${{matrix.app.dir}} From 053c7a9555f71df7d2d8b14054e8bb6222b2f976 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 17:40:42 +0900 Subject: [PATCH 15/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20continue?= =?UTF-8?q?=20on=20error=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deply-alpha.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deply-alpha.yml b/.github/workflows/deply-alpha.yml index 5eebd458..353b766c 100644 --- a/.github/workflows/deply-alpha.yml +++ b/.github/workflows/deply-alpha.yml @@ -38,6 +38,7 @@ jobs: - name: Install dependencies working-directory: ./${{matrix.app.dir}} + continue-on-error: true run: npm ci --no-audit - name: Run tests From b933cb77c8d668c3f8a14bf285a8e9fd6e47ddef Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 17:44:33 +0900 Subject: [PATCH 16/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20npm=20ru?= =?UTF-8?q?n=20test=20BE=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=A7=8C=20=EC=8B=A4=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deply-alpha.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deply-alpha.yml b/.github/workflows/deply-alpha.yml index 353b766c..cab94eb5 100644 --- a/.github/workflows/deply-alpha.yml +++ b/.github/workflows/deply-alpha.yml @@ -39,9 +39,10 @@ jobs: - name: Install dependencies working-directory: ./${{matrix.app.dir}} continue-on-error: true - run: npm ci --no-audit + run: npm ci - name: Run tests + if: ${{ matrix.app.name == 'be' }} # BE일 때만 실행 working-directory: ./${{matrix.app.dir}} run: npm test env: From c3ccb20bd4c55b352097416f0dcd0e02010ed9d3 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 18:10:31 +0900 Subject: [PATCH 17/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20key=20se?= =?UTF-8?q?cret=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deply-alpha.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deply-alpha.yml b/.github/workflows/deply-alpha.yml index cab94eb5..51bffea9 100644 --- a/.github/workflows/deply-alpha.yml +++ b/.github/workflows/deply-alpha.yml @@ -42,7 +42,7 @@ jobs: run: npm ci - name: Run tests - if: ${{ matrix.app.name == 'be' }} # BE일 때만 실행 + if: ${{ matrix.app.name == 'be' }} working-directory: ./${{matrix.app.dir}} run: npm test env: @@ -99,7 +99,7 @@ jobs: with: host: ${{ secrets.NCP_ALPHA_SERVER_HOST }} username: ${{ secrets.NCP_ALPHA_SERVER_USERNAME }} - key: ${{ secrets.NCP_ALPHA_SERVER_SSH_KEY }} + key: ${{ secrets.NCP_SERVER_SSH_KEY }} port: 22 script: | docker system prune -af From 0cf7bca47202d9e0dec8e84558d5fa95be0b9de3 Mon Sep 17 00:00:00 2001 From: jinddings Date: Mon, 11 Nov 2024 18:16:00 +0900 Subject: [PATCH 18/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore=20:=20?= =?UTF-8?q?=EB=8F=99=EC=8B=9C=EC=97=90=20=EB=91=90=20=EA=B0=80=EC=A7=80=20?= =?UTF-8?q?job=20=EC=9D=B4=20=EC=8B=A4=ED=96=89=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deply-alpha.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deply-alpha.yml b/.github/workflows/deply-alpha.yml index 51bffea9..ec6ec9ce 100644 --- a/.github/workflows/deply-alpha.yml +++ b/.github/workflows/deply-alpha.yml @@ -14,6 +14,7 @@ jobs: build-and-deploy: runs-on: ubuntu-latest strategy: + max-parallel: 1 matrix: app: [