- Added comprehensive validation for user data during registration and updates, including email format, company code existence, user type validation, and password length checks. - Implemented JWT token invalidation for users when their status changes or when roles are updated, ensuring security and compliance with the latest policies. - Introduced a new TokenInvalidationService to manage token versioning and invalidation processes efficiently. - Updated the admin controller to provide detailed error messages and success responses for user status changes and validations. - Enhanced the authentication middleware to check token versions against the database, ensuring that invalidated tokens cannot be used. This commit improves the overall security and user management experience within the application.
76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
// 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;
|
|
}
|
|
}
|
|
}
|