// JWT 토큰 무효화 서비스 // user_info.token_version 기반으로 기존 JWT 토큰을 무효화 import { query } from "../database/db"; import { cache } from "../utils/cache"; import { logger } from "../utils/logger"; const TOKEN_VERSION_CACHE_TTL = 2 * 60 * 1000; // 2분 캐시 export class TokenInvalidationService { /** * 캐시 키 생성 */ static cacheKey(userId: string): string { return `token_version:${userId}`; } /** * 단일 사용자의 토큰 무효화 (token_version +1) */ static async invalidateUserTokens(userId: string): Promise { try { await query( `UPDATE user_info SET token_version = COALESCE(token_version, 0) + 1 WHERE user_id = $1`, [userId] ); cache.delete(this.cacheKey(userId)); logger.info(`토큰 무효화: ${userId}`); } catch (error) { logger.error(`토큰 무효화 실패: ${userId}`, { error }); } } /** * 여러 사용자의 토큰 일괄 무효화 */ static async invalidateMultipleUserTokens(userIds: string[]): Promise { if (userIds.length === 0) return; try { const placeholders = userIds.map((_, i) => `$${i + 1}`).join(", "); await query( `UPDATE user_info SET token_version = COALESCE(token_version, 0) + 1 WHERE user_id IN (${placeholders})`, userIds ); userIds.forEach((id) => cache.delete(this.cacheKey(id))); logger.info(`토큰 일괄 무효화: ${userIds.length}명`); } catch (error) { logger.error(`토큰 일괄 무효화 실패`, { error, userIds }); } } /** * 현재 token_version 조회 (캐시 사용) */ static async getUserTokenVersion(userId: string): Promise { const cacheKey = this.cacheKey(userId); const cached = cache.get(cacheKey); if (cached !== null) { return cached; } try { const result = await query<{ token_version: number | null }>( `SELECT token_version FROM user_info WHERE user_id = $1`, [userId] ); const version = result.length > 0 ? (result[0].token_version ?? 0) : 0; cache.set(cacheKey, version, TOKEN_VERSION_CACHE_TTL); return version; } catch (error) { logger.error(`token_version 조회 실패: ${userId}`, { error }); return 0; } } }