Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 91 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.0.0",
"@nestjs/mapped-types": "^2.0.6",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-socket.io": "^10.0.0",
"@nestjs/swagger": "^8.1.0",
"@nestjs/typeorm": "^10.0.2",
"@nestjs/websockets": "^10.0.0",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.1.0-beta.10",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.7",
"dotenv": "^16.4.7",
Expand All @@ -43,6 +45,7 @@
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"socket.io": "^4.7.1",
"swagger-ui-express": "^5.0.1",
"typeorm": "^0.3.20"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { ApiTags } from '@nestjs/swagger';

@ApiTags('healthCheck')
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { env } from 'process';
import { UserModule } from 'src/user/user.module';
import { AuthModule } from './auth/auth.module';
import { RedisModule } from './redis/redis.module';
import { GameRoomModule } from './gameRoom/gameRoom.module';

@Module({
imports: [
UserModule,
AuthModule,
RedisModule,
GameRoomModule,
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.MYSQL_HOST || 'mysql',
Expand Down
12 changes: 8 additions & 4 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ import { Response } from 'express';
import { Request } from 'express';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/user.service';
import LoginUserDto from './auth.dto';
import { JwtAuthGuard } from './auth.guard';
import LoginUserDto from './dto/auth.dto';
import { RedisAuthGuard } from './auth.guard';
import { RedisService } from 'src/redis/redis.service';


import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
@ApiTags('auth')

@Controller('auth')
export class AuthController {
constructor(
Expand All @@ -38,7 +42,7 @@ export class AuthController {
throw new HttpException('Invalid credentials', HttpStatus.UNAUTHORIZED);
}

const payload = { userEmail: user.userEmail, sub: user.id };
const payload = { userEmail: user.userEmail, userId: user.id };
const accessToken = this.jwtService.sign(payload, { expiresIn: '2h' });
const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });
await this.redisService.set(`access:${user.userEmail}`, accessToken, 3600);
Expand All @@ -60,7 +64,7 @@ export class AuthController {
};
}

@UseGuards(JwtAuthGuard)
@UseGuards(RedisAuthGuard)
@Get('profile')
getProfile() {
return { message: 'This is a protected route' };
Expand Down
2 changes: 1 addition & 1 deletion src/auth/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
export class RedisAuthGuard extends AuthGuard('jwt') {}
2 changes: 1 addition & 1 deletion src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PassportModule } from '@nestjs/passport';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { UserService } from '../user/user.service';
import { User } from '../user/user.entity';
import { User } from '../user/entities/user.entity';
import { RedisModule } from 'src/redis/redis.module';

@Module({
Expand Down
File renamed without changes.
28 changes: 24 additions & 4 deletions src/auth/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { RedisService } from 'src/redis/redis.service';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
constructor(private readonly redisService: RedisService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET, // 환경변수로 관리 추천
secretOrKey: process.env.JWT_SECRET, // 환경변수로 관리
});
}

async validate(payload: any) {
return { userId: payload.sub, userEmail: payload.userEmail };
const { userEmail, userId, exp } = payload;

if (!userEmail || !userId) {
throw new UnauthorizedException('Invalid token payload');
}

// Redis에서 토큰 확인
const storedToken = await this.redisService.get(`access:${userEmail}`);
if (!storedToken) {
throw new UnauthorizedException('Token not found in Redis');
}

// 만료 시간 확인
const currentTime = Math.floor(Date.now() / 1000);
if (exp && currentTime > exp) {
throw new UnauthorizedException('Token has expired');
}

// 유효한 유저 정보 반환
return { userEmail, userId };
}
}
22 changes: 22 additions & 0 deletions src/filters/http-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const exceptionResponse: any = exception.getResponse();

const errorResponse = {
statusCode: status,
timestamp: new Date().toISOString(),
path: ctx.getRequest<Request>().url,
...(typeof exceptionResponse === 'string' ? { message: exceptionResponse } : exceptionResponse),
};

response.status(status).json(errorResponse);
}
}
1 change: 1 addition & 0 deletions src/gameRoom/dto/createGameRoom.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class CreateGameRoomDto {}
8 changes: 8 additions & 0 deletions src/gameRoom/dto/gameRoom.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import { IsString, IsNotEmpty } from 'class-validator';

export class GameroomDto {
@IsString()
@IsNotEmpty()
name: string;
}
4 changes: 4 additions & 0 deletions src/gameRoom/dto/updateGameRoom.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateGameRoomDto } from './createGameRoom.dto';

export class UpdateGameRoomDto extends PartialType(CreateGameRoomDto) {}
24 changes: 24 additions & 0 deletions src/gameRoom/entities/gameRoom.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
} from 'typeorm';

@Entity()
export class GameRoom {
@PrimaryGeneratedColumn()
id: number;

@Column()
roomName: string;

@Column()
maxPlayers: number;

@Column({ default: 0 }) // 초기값 설정
currentCount: number;

@CreateDateColumn()
createdAt: Date;
}
Loading
Loading