Enhance user management and token invalidation features

- 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.
This commit is contained in:
kjs
2026-03-25 18:47:50 +09:00
parent 782ebb1b33
commit 70e040db39
12 changed files with 573 additions and 36 deletions

View File

@@ -5,6 +5,7 @@ import { Request, Response, NextFunction } from "express";
import { JwtUtils } from "../utils/jwtUtils";
import { AuthenticatedRequest, PersonBean } from "../types/auth";
import { logger } from "../utils/logger";
import { TokenInvalidationService } from "../services/tokenInvalidationService";
// AuthenticatedRequest 타입을 다른 모듈에서 사용할 수 있도록 re-export
export { AuthenticatedRequest } from "../types/auth";
@@ -22,11 +23,11 @@ declare global {
* JWT 토큰 검증 미들웨어
* 기존 세션 방식과 동일한 효과를 제공
*/
export const authenticateToken = (
export const authenticateToken = async (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
): void => {
): Promise<void> => {
try {
// Authorization 헤더에서 토큰 추출
const authHeader = req.get("Authorization");
@@ -46,6 +47,25 @@ export const authenticateToken = (
// JWT 토큰 검증 및 사용자 정보 추출
const userInfo: PersonBean = JwtUtils.verifyToken(token);
// token_version 검증 (JWT payload vs DB)
const decoded = JwtUtils.decodeToken(token);
const tokenVersion = decoded?.tokenVersion;
// tokenVersion이 undefined면 구버전 토큰이므로 통과 (하위 호환)
if (tokenVersion !== undefined) {
const dbVersion = await TokenInvalidationService.getUserTokenVersion(userInfo.userId);
if (tokenVersion !== dbVersion) {
res.status(401).json({
success: false,
error: {
code: "TOKEN_INVALIDATED",
details: "보안 정책에 의해 재로그인이 필요합니다.",
},
});
return;
}
}
// 요청 객체에 사용자 정보 설정 (기존 PersonBean과 동일)
req.user = userInfo;
@@ -173,11 +193,11 @@ export const requireUserOrAdmin = (targetUserId: string) => {
* 토큰 갱신 미들웨어
* 토큰이 곧 만료될 경우 자동으로 갱신
*/
export const refreshTokenIfNeeded = (
export const refreshTokenIfNeeded = async (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
): void => {
): Promise<void> => {
try {
const authHeader = req.get("Authorization");
const token = authHeader && authHeader.split(" ")[1];
@@ -191,6 +211,16 @@ export const refreshTokenIfNeeded = (
// 1시간(3600초) 이내에 만료되는 경우 갱신
if (timeUntilExpiry > 0 && timeUntilExpiry < 3600) {
// 갱신 전 token_version 검증
if (decoded.tokenVersion !== undefined) {
const dbVersion = await TokenInvalidationService.getUserTokenVersion(decoded.userId);
if (decoded.tokenVersion !== dbVersion) {
// 무효화된 토큰은 갱신하지 않음
next();
return;
}
}
const newToken = JwtUtils.refreshToken(token);
// 새로운 토큰을 응답 헤더에 포함