diff --git a/docker-compose.yml b/docker-compose.yml index cc00970..07a298a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,7 +30,7 @@ services: TZ: Asia/Seoul PGDATA: /data/postgres healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] + test: ["CMD", "pg_isready", "-U", "wakttu"] volumes: - ./postgres/data:/data/postgres networks: @@ -43,7 +43,7 @@ services: - 6379:6379 env_file: - redis.env - command: redis-server --requirepass "$${PASSWORD}" --include /usr/local/etc/redis/redis.conf + command: /bin/sh -c "redis-server --requirepass $$PASSWORD --include /usr/local/etc/redis/redis.conf" labels: - 'name=redis' - 'mode=standalone' diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index f361a98..4aec5f3 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -19,6 +19,7 @@ import { LocalAuthenticatedGuard } from './local-auth.guard'; import { LoginUserDto } from 'src/user/dto/login-user.dto'; import { ConfigService } from '@nestjs/config'; import { CloudflareGuard } from './cf-auth.guard'; +import { IsJogongGuard } from './jogong-auth.guard'; @ApiTags('Auth') @Controller('auth') @@ -44,6 +45,7 @@ export class AuthController { }, }) @Post('login') + @UseGuards(IsJogongGuard) @UseGuards(LocalAuthenticatedGuard) @UseGuards(IsNotLoginedGuard) async localLogin( @@ -67,6 +69,7 @@ export class AuthController { }, }) @Post('signup') + @UseGuards(IsJogongGuard) @UseGuards(IsNotLoginedGuard) async signup(@Body() user: CreateUserDto): Promise { return await this.authService.signup(user); @@ -105,6 +108,7 @@ export class AuthController { } @Get('wakta') + @UseGuards(IsJogongGuard) async waktaOauth(@Session() session: Record) { const data = await this.authService.waktaOauth(); session.auth = data; @@ -145,6 +149,7 @@ export class AuthController { } @Get('guest') + @UseGuards(IsJogongGuard) async guest(@Session() session) { session.user = await this.authService.guestUser(); return session.user ? { status: 200 } : { status: 400 }; diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 446ce62..0de22d1 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -4,6 +4,7 @@ import { AuthService } from './auth.service'; import { UserModule } from 'src/user/user.module'; import { PassportModule } from '@nestjs/passport'; import { WakgamesModule } from 'src/wakgames/wakgames.module'; +import { StatsModule } from 'src/stats/stats.module'; @Module({ imports: [ @@ -12,6 +13,7 @@ import { WakgamesModule } from 'src/wakgames/wakgames.module'; session: true, }), WakgamesModule, + StatsModule, ], controllers: [AuthController], providers: [AuthService], diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 3f97d51..f489f2c 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -9,12 +9,14 @@ import * as bcrypt from 'bcrypt'; import { Request } from 'express'; import { WakgamesService } from 'src/wakgames/wakgames.service'; import { randomUUID } from 'crypto'; +import { StatsService } from 'src/stats/stats.service'; @Injectable() export class AuthService { constructor( private readonly userService: UserService, private readonly wakgamesService: WakgamesService, + private readonly statsService: StatsService, ) {} async OAuthLogin(user) { @@ -174,6 +176,8 @@ export class AuthService { password: null, }; const newUser = await this.userService.create(user); + await this.userService.achieveAllItems(newUser.id); + await this.statsService.setJogong(newUser.id); return newUser; } catch (error) { if (error instanceof UnauthorizedException) throw error; diff --git a/src/auth/cf-auth.guard.ts b/src/auth/cf-auth.guard.ts index 998bea0..0b772d3 100644 --- a/src/auth/cf-auth.guard.ts +++ b/src/auth/cf-auth.guard.ts @@ -1,11 +1,17 @@ -import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { + Injectable, + CanActivate, + ExecutionContext, + Logger, +} from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as jwt from 'jsonwebtoken'; import * as jwksClient from 'jwks-rsa'; @Injectable() export class CloudflareGuard implements CanActivate { - private client: jwksClient.JwksClient; + private readonly client: jwksClient.JwksClient; + private readonly logger = new Logger(CloudflareGuard.name); constructor(config: ConfigService) { const domain = config.get('CLOUD_DOMAIN'); @@ -23,17 +29,17 @@ export class CloudflareGuard implements CanActivate { const token = authHeader && authHeader.split(' ')[1]; if (!token) { - console.error('JWT 없음: Authorization 헤더가 비어 있습니다.'); + this.logger.error('JWT 없음: Authorization 헤더가 비어 있습니다.'); return false; } try { const decoded = await this.verifyToken(token); - // 검증 성공 시 사용자 정보를 요청 객체에 저장 request.user = decoded; + this.logger.log('JWT 검증 성공'); return true; } catch (error) { - console.error('JWT 검증 실패:', error.message); + this.logger.error('JWT 검증 실패:', error.message); return false; } } @@ -43,11 +49,13 @@ export class CloudflareGuard implements CanActivate { jwt.verify( token, (header, callback) => this.getKey(header, callback), - { algorithms: ['RS256'] }, // Cloudflare는 RS256 알고리즘 사용 + { algorithms: ['RS256'] }, (err, decoded) => { if (err) { + this.logger.error('토큰 검증 오류:', err.message); return reject(err); } + this.logger.log('토큰 검증 완료'); resolve(decoded); }, ); @@ -57,9 +65,11 @@ export class CloudflareGuard implements CanActivate { private getKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) { this.client.getSigningKey(header.kid, (err, key) => { if (err) { + this.logger.error(`키 가져오기 실패: ${err.message}`); return callback(err, null); } const signingKey = key.getPublicKey(); + this.logger.log(`키 가져오기 성공: kid=${header.kid}`); callback(null, signingKey); }); } diff --git a/src/auth/jogong-auth.guard.ts b/src/auth/jogong-auth.guard.ts new file mode 100644 index 0000000..4fda98b --- /dev/null +++ b/src/auth/jogong-auth.guard.ts @@ -0,0 +1,25 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class IsJogongGuard implements CanActivate { + constructor(private readonly configService: ConfigService) {} + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + canActivate(context: ExecutionContext): boolean { + const NODE_ENV = this.configService.get('NODE_ENV'); + + if (NODE_ENV === 'jogong') { + throw new ForbiddenException( + 'Access to this resource is forbidden in the current environment.', + ); + } + + return true; + } +} diff --git a/src/socket/socket.gateway.ts b/src/socket/socket.gateway.ts index 2cec2f3..16db560 100644 --- a/src/socket/socket.gateway.ts +++ b/src/socket/socket.gateway.ts @@ -101,7 +101,7 @@ export class SocketGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { private readonly logger = new Logger(SocketGateway.name); - private readonly MAX_CONNECTIONS = 200; // 최대 연결 수 설정 + private readonly MAX_CONNECTIONS = 50; // 최대 연결 수 설정 private currentConnections = 0; // 현재 연결된 소켓 수 constructor( diff --git a/src/stats/stats.module.ts b/src/stats/stats.module.ts index d12e941..8e59933 100644 --- a/src/stats/stats.module.ts +++ b/src/stats/stats.module.ts @@ -1,9 +1,10 @@ -import { Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { StatsController } from './stats.controller'; import { StatsService } from './stats.service'; - + @Module({ controllers: [StatsController], - providers: [StatsService] -}) -export class StatsModule {} + providers: [StatsService], + exports: [StatsService], +}) +export class StatsModule {} diff --git a/src/stats/stats.service.ts b/src/stats/stats.service.ts index b0e6d0c..0f8f78d 100644 --- a/src/stats/stats.service.ts +++ b/src/stats/stats.service.ts @@ -18,6 +18,10 @@ export class StatsService { async createAchieve(id: string, userId: string) { try { + const check = await this.prisma.achievements.findUnique({ + where: { id_userId: { id, userId } }, + }); + if (check) return null; return await this.prisma.achievements.create({ data: { id, @@ -103,6 +107,39 @@ export class StatsService { } } + async setJogong(userId: string) { + const statsArray = { + 'WOO-1': 98, + 'WOO-2': 9, + 'INE-1': 98, + 'INE-2': 19, + 'JING-1': 98, + 'JING-2': 19, + 'LIL-1': 98, + 'LIL-2': 4, + 'JU-1': 98, + 'JU-2': 4, + 'GO-1': 98, + 'GO-2': 19, + 'VIi-1': 98, + 'VIi-2': 19, + 'GOMEM-1': 48, + 'GOMEM-2': 48, + EXIT: 9, + FILTER: 9, + }; + + for (const [statId, incrementValue] of Object.entries(statsArray)) { + try { + await this.putStat(userId, statId, incrementValue); + } catch (error) { + console.error( + `통계 업데이트 실패 (statId: ${statId}): ${error.message}`, + ); + } + } + } + // 통계 ID로 업적 확인 async checkAchievementsByStatId( tx: any,