- 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.
178 lines
5.3 KiB
TypeScript
178 lines
5.3 KiB
TypeScript
// JWT 토큰 관리 유틸리티
|
|
// 기존 PersonBean 정보를 JWT 페이로드로 변환
|
|
|
|
import jwt from "jsonwebtoken";
|
|
import { PersonBean, JwtPayload } from "../types/auth";
|
|
import config from "../config/environment";
|
|
|
|
export class JwtUtils {
|
|
/**
|
|
* 사용자 정보로 JWT 토큰 생성
|
|
* 기존 PersonBean 정보를 JWT 페이로드로 변환
|
|
*/
|
|
static generateToken(userInfo: PersonBean): string {
|
|
try {
|
|
const payload: JwtPayload = {
|
|
userId: userInfo.userId,
|
|
userName: userInfo.userName,
|
|
deptName: userInfo.deptName,
|
|
companyCode: userInfo.companyCode,
|
|
companyName: userInfo.companyName, // 회사명 추가
|
|
userType: userInfo.userType,
|
|
userTypeName: userInfo.userTypeName,
|
|
tokenVersion: userInfo.tokenVersion ?? 0,
|
|
};
|
|
|
|
return jwt.sign(payload, config.jwt.secret, {
|
|
expiresIn: config.jwt.expiresIn,
|
|
issuer: "PMS-System",
|
|
audience: "PMS-Users",
|
|
} as any);
|
|
} catch (error) {
|
|
console.error("JWT token generation error:", error);
|
|
throw new Error("토큰 생성 중 오류가 발생했습니다.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* JWT 토큰 검증 및 사용자 정보 추출
|
|
*/
|
|
static verifyToken(token: string): PersonBean {
|
|
try {
|
|
const decoded = jwt.verify(token, config.jwt.secret) as JwtPayload;
|
|
|
|
// PersonBean 형태로 변환
|
|
const personBean: PersonBean = {
|
|
userId: decoded.userId,
|
|
userName: decoded.userName,
|
|
deptName: decoded.deptName,
|
|
companyCode: decoded.companyCode,
|
|
companyName: decoded.companyName, // 회사명 추가
|
|
userType: decoded.userType,
|
|
userTypeName: decoded.userTypeName,
|
|
};
|
|
|
|
return personBean;
|
|
} catch (error) {
|
|
if (error instanceof jwt.TokenExpiredError) {
|
|
throw new Error("토큰이 만료되었습니다.");
|
|
} else if (error instanceof jwt.JsonWebTokenError) {
|
|
throw new Error("유효하지 않은 토큰입니다.");
|
|
} else {
|
|
console.error("JWT token verification error:", error);
|
|
throw new Error("토큰 검증 중 오류가 발생했습니다.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* JWT 토큰에서 페이로드만 추출 (검증 없이)
|
|
*/
|
|
static decodeToken(token: string): JwtPayload | null {
|
|
try {
|
|
return jwt.decode(token) as JwtPayload;
|
|
} catch (error) {
|
|
console.error("JWT token decode error:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 토큰 만료 시간 확인
|
|
*/
|
|
static isTokenExpired(token: string): boolean {
|
|
try {
|
|
const decoded = jwt.decode(token) as JwtPayload;
|
|
if (!decoded || !decoded.exp) {
|
|
return true;
|
|
}
|
|
|
|
const currentTime = Math.floor(Date.now() / 1000);
|
|
return decoded.exp < currentTime;
|
|
} catch (error) {
|
|
console.error("Token expiration check error:", error);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 토큰 갱신 (만료 시간만 연장)
|
|
*/
|
|
static refreshToken(token: string): string {
|
|
try {
|
|
const decoded = jwt.decode(token) as JwtPayload;
|
|
if (!decoded) {
|
|
throw new Error("토큰을 디코드할 수 없습니다.");
|
|
}
|
|
|
|
// 페이로드에서 만료 시간 관련 필드 제거
|
|
const { iat, exp, aud, iss, ...payload } = decoded;
|
|
|
|
return jwt.sign(payload, config.jwt.secret, {
|
|
expiresIn: config.jwt.expiresIn,
|
|
issuer: "PMS-System",
|
|
} as any);
|
|
} catch (error) {
|
|
console.error("JWT token refresh error:", error);
|
|
throw new Error("토큰 갱신 중 오류가 발생했습니다.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 토큰에서 사용자 ID 추출
|
|
*/
|
|
static getUserIdFromToken(token: string): string | null {
|
|
try {
|
|
const decoded = jwt.decode(token) as JwtPayload;
|
|
return decoded?.userId || null;
|
|
} catch (error) {
|
|
console.error("Get user ID from token error:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 토큰 유효성 검사 (만료 여부 포함)
|
|
*/
|
|
static validateToken(token: string): { isValid: boolean; error?: string } {
|
|
try {
|
|
jwt.verify(token, config.jwt.secret);
|
|
return { isValid: true };
|
|
} catch (error) {
|
|
if (error instanceof jwt.TokenExpiredError) {
|
|
return { isValid: false, error: "토큰이 만료되었습니다." };
|
|
} else if (error instanceof jwt.JsonWebTokenError) {
|
|
return { isValid: false, error: "유효하지 않은 토큰입니다." };
|
|
} else {
|
|
return { isValid: false, error: "토큰 검증 중 오류가 발생했습니다." };
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 테스트용 메서드 (개발 환경에서만 사용)
|
|
*/
|
|
static testJwtUtils(userInfo: PersonBean): void {
|
|
console.log("=== JWT 토큰 테스트 ===");
|
|
console.log("사용자 정보:", userInfo);
|
|
|
|
const token = this.generateToken(userInfo);
|
|
console.log("생성된 토큰:", token);
|
|
|
|
const decoded = this.decodeToken(token);
|
|
console.log("디코드된 페이로드:", decoded);
|
|
|
|
const verified = this.verifyToken(token);
|
|
console.log("검증된 사용자 정보:", verified);
|
|
|
|
const isExpired = this.isTokenExpired(token);
|
|
console.log("토큰 만료 여부:", isExpired);
|
|
|
|
const userId = this.getUserIdFromToken(token);
|
|
console.log("토큰에서 추출한 사용자 ID:", userId);
|
|
|
|
const validation = this.validateToken(token);
|
|
console.log("토큰 유효성 검사:", validation);
|
|
}
|
|
}
|