diff --git a/BE/package-lock.json b/BE/package-lock.json index cb72a103..f9b6300f 100644 --- a/BE/package-lock.json +++ b/BE/package-lock.json @@ -20,18 +20,22 @@ "@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", + "express": "^4.21.1", "fastify-swagger": "^5.1.1", "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", @@ -1961,6 +1965,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 +3795,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" @@ -5099,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", @@ -8299,6 +8336,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", @@ -8611,6 +8654,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", @@ -8769,6 +8835,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, @@ -10901,6 +10976,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 224dc019..86c3538b 100644 --- a/BE/package.json +++ b/BE/package.json @@ -30,19 +30,23 @@ "@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", + "express": "^4.21.1", "fastify-swagger": "^5.1.1", "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/app.module.ts b/BE/src/app.module.ts index 114ecadf..8a1dab3d 100644 --- a/BE/src/app.module.ts +++ b/BE/src/app.module.ts @@ -5,26 +5,17 @@ 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'; import { SocketModule } from './websocket/socket.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: [User], - 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 aee104f0..684384be 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -3,18 +3,25 @@ import { Post, Get, Body, - Req, ValidationPipe, UseGuards, + Req, + Res, + UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation } from '@nestjs/swagger'; +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 { - constructor(private authService: AuthService) {} + constructor( + private authService: AuthService, + private configService: ConfigService, + ) {} @ApiOperation({ summary: '회원 가입 API' }) @Post('/signup') @@ -24,16 +31,56 @@ 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 }); + return res.status(200).json({ accessToken }); } @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, + ) { + const { accessToken, refreshToken } = + await this.authService.kakaoLoginUser(authCredentialsDto); + + res.cookie('refreshToken', refreshToken, { httpOnly: true }); + res.cookie('isRefreshToken', true, { httpOnly: true }); + return res.status(200).json({ accessToken }); + } + + @ApiOperation({ summary: 'Refresh Token 요청 API' }) + @Get('/refresh') + async refresh(@Req() req: Request, @Res() res: Response) { + if ( + typeof req.cookies.refreshToken !== 'string' || + typeof req.cookies.accessToken !== 'string' + ) { + throw new UnauthorizedException('Invalid refresh token'); + } + + const { refreshToken } = req.cookies; + + const newAccessToken = await this.authService.refreshToken(refreshToken); + + res.cookie('refreshToken', refreshToken, { httpOnly: true }); + res.cookie('isRefreshToken', true, { httpOnly: true }); + return res.status(200).json({ accessToken: newAccessToken }); + } } diff --git a/BE/src/auth/auth.module.ts b/BE/src/auth/auth.module.ts index a3ba6f04..522e5199 100644 --- a/BE/src/auth/auth.module.ts +++ b/BE/src/auth/auth.module.ts @@ -2,25 +2,32 @@ 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 './jwt.strategy'; +import { JwtStrategy } from './strategy/jwt.strategy'; +import { KakaoStrategy } from './strategy/kakao.strategy'; @Module({ imports: [ TypeOrmModule.forFeature([User]), + ConfigModule, PassportModule.register({ defaultStrategy: 'jwt' }), - JwtModule.register({ - secret: 'Juga16', - signOptions: { - expiresIn: 3600, - }, + JwtModule.registerAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + secret: configService.get('JWT_SECRET'), + signOptions: { + expiresIn: configService.get('JWT_ACCESS_EXPIRATION_TIME'), + }, + }), + inject: [ConfigService], }), ], controllers: [AuthController], - providers: [AuthService, UserRepository, JwtStrategy], + providers: [AuthService, UserRepository, JwtStrategy, KakaoStrategy], exports: [JwtStrategy, PassportModule], }) export class AuthModule {} diff --git a/BE/src/auth/auth.service.ts b/BE/src/auth/auth.service.ts index 6f751e1b..2de79cd8 100644 --- a/BE/src/auth/auth.service.ts +++ b/BE/src/auth/auth.service.ts @@ -2,6 +2,7 @@ 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'; @@ -11,6 +12,7 @@ export class AuthService { @InjectRepository(UserRepository) private userRepository: UserRepository, private jwtService: JwtService, + private readonly configService: ConfigService, ) {} async signUp(authCredentialsDto: AuthCredentialsDto): Promise { @@ -19,15 +21,111 @@ 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 } }); if (user && (await bcrypt.compare(password, user.password))) { - const payload = { email }; - const accessToken = this.jwtService.sign(payload); - return { accessToken }; + const { accessToken, refreshToken } = + await this.getJWTToken(authCredentialsDto); + + await this.setCurrentRefreshToken(refreshToken, user.id); + + return { accessToken, refreshToken }; } throw new UnauthorizedException('Please check your login credentials'); } + + async kakaoLoginUser( + authCredentialsDto: AuthCredentialsDto, + ): Promise<{ accessToken: string; refreshToken: string }> { + return this.getJWTToken(authCredentialsDto); + } + + 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.signAsync({ email: authCredentialsDto.email }) + : this.jwtService.signAsync({ kakaoId: authCredentialsDto.kakaoId }); + } + + async generateRefreshToken( + authCredentialsDto: AuthCredentialsDto, + ): Promise { + if (authCredentialsDto.email) { + return this.jwtService.signAsync( + { email: authCredentialsDto.email }, + { + 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) { + 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'), + 10, + ), + ); + + await this.userRepository.update(userId, { + currentRefreshToken, + 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 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 fb1199ed..31bc69b6 100644 --- a/BE/src/auth/dto/auth-credentials.dto.ts +++ b/BE/src/auth/dto/auth-credentials.dto.ts @@ -1,27 +1,41 @@ -import { IsString, Matches, MaxLength, MinLength } from 'class-validator'; +import { + IsString, + Matches, + MaxLength, + MinLength, + 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/jwt.strategy.ts b/BE/src/auth/strategy/jwt.strategy.ts similarity index 73% rename from BE/src/auth/jwt.strategy.ts rename to BE/src/auth/strategy/jwt.strategy.ts index 350d622d..a6393996 100644 --- a/BE/src/auth/jwt.strategy.ts +++ b/BE/src/auth/strategy/jwt.strategy.ts @@ -2,16 +2,18 @@ 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'; +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(), }); } diff --git a/BE/src/auth/strategy/kakao.strategy.ts b/BE/src/auth/strategy/kakao.strategy.ts new file mode 100644 index 00000000..e69805dc --- /dev/null +++ b/BE/src/auth/strategy/kakao.strategy.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +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, + 'kakao', +) { + constructor(private readonly configService: ConfigService) { + const options: KakaoStrategyOptions = { + clientID: configService.get('KAKAO_CLIENT_ID') || '', + clientSecret: '', + callbackURL: `${configService.get('BACKEND_URL') || ''}/auth/kakao`, + }; + + super(options); + } + + validate( + accessToken: string, + refreshToken: string, + profile: KakaoProfile, + done: (error: Error, user?: KakaoUser) => void, + ) { + try { + // eslint-disable-next-line no-underscore-dangle + const kakaoId = profile._json.id; + const user = { + kakaoId, + }; + done(null, user); + } catch (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 cf6b130a..8c574cd4 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/auth-credentials.dto'; @Entity() export class User extends BaseEntity { @@ -16,4 +17,21 @@ export class User extends BaseEntity { @Column({ default: -1 }) kakaoId: number; + + @Column({ default: '' }) + currentRefreshToken: string; + + @Column({ type: 'datetime', nullable: true }) + currentRefreshTokenExpiresAt: Date; + + toAuthCredentialsDto(): AuthCredentialsDto { + if (this.kakaoId === -1) { + return { + email: this.email, + password: this.password, + }; + } + + throw new Error('Cannot convert Kakao user to auth credentials'); + } } diff --git a/BE/src/auth/user.repository.ts b/BE/src/auth/user.repository.ts index 429437e7..442c73f4 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..c10d56ef --- /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/src/main.ts b/BE/src/main.ts index bbc099aa..bc11d55f 100644 --- a/BE/src/main.ts +++ b/BE/src/main.ts @@ -1,5 +1,6 @@ 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'; @@ -14,6 +15,7 @@ async function bootstrap() { optionsSuccessStatus: 204, }); + app.use(cookieParser()); await app.listen(process.env.PORT ?? 3000); } 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; + }; + } +} 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"] } }