Files
vexplor/backend-node/src/services/tokenInvalidationService.ts

76 lines
2.3 KiB
TypeScript
Raw Normal View History

// 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<void> {
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<void> {
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<number> {
const cacheKey = this.cacheKey(userId);
const cached = cache.get<number>(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;
}
}
}