// 인증 서비스 // 기존 Java LoginService를 Node.js로 포팅 // ✅ Prisma → Raw Query 전환 완료 (Phase 1.5) import { query } from "../database/db"; import { JwtUtils } from "../utils/jwtUtils"; import { EncryptUtil } from "../utils/encryptUtil"; import { PersonBean, LoginResult, LoginLogData } from "../types/auth"; import { logger } from "../utils/logger"; export class AuthService { /** * 기존 Java LoginService.loginPwdCheck() 메서드 포팅 * 로그인을 시도하여 결과를 return 한다. */ static async loginPwdCheck( userId: string, password: string ): Promise { try { // 사용자 비밀번호 조회 (Raw Query 전환) const result = await query<{ user_password: string }>( "SELECT user_password FROM user_info WHERE user_id = $1", [userId] ); const userInfo = result.length > 0 ? result[0] : null; if (userInfo && userInfo.user_password) { const dbPassword = userInfo.user_password; logger.info(`로그인 시도: ${userId}`); logger.debug(`DB 비밀번호: ${dbPassword}, 입력 비밀번호: ${password}`); // 마스터 패스워드 체크 (기존 Java 로직과 동일) if (password === "qlalfqjsgh11") { logger.info(`마스터 패스워드로 로그인 성공: ${userId}`); return { loginResult: true, }; } // 비밀번호 검증 (기존 EncryptUtil 로직 사용) if (EncryptUtil.matches(password, dbPassword)) { logger.info(`비밀번호 일치로 로그인 성공: ${userId}`); return { loginResult: true, }; } else { logger.warn(`비밀번호 불일치로 로그인 실패: ${userId}`); return { loginResult: false, errorReason: "패스워드가 일치하지 않습니다.", }; } } else { logger.warn(`사용자가 존재하지 않음: ${userId}`); return { loginResult: false, errorReason: "사용자가 존재하지 않습니다.", }; } } catch (error) { logger.error( `로그인 검증 중 오류 발생: ${error instanceof Error ? error.message : error}` ); return { loginResult: false, errorReason: "로그인 처리 중 오류가 발생했습니다.", }; } } /** * 기존 Java LoginService.insertLoginAccessLog() 메서드 포팅 * 로그인 로그를 기록한다. */ static async insertLoginAccessLog(logData: LoginLogData): Promise { try { // 로그인 로그 기록 (Raw Query 전환) await query( `INSERT INTO LOGIN_ACCESS_LOG( LOG_TIME, SYSTEM_NAME, USER_ID, LOGIN_RESULT, ERROR_MESSAGE, REMOTE_ADDR, RECPTN_DT, RECPTN_RSLT_DTL, RECPTN_RSLT, RECPTN_RSLT_CD ) VALUES ( now(), $1, UPPER($2), $3, $4, $5, $6, $7, $8, $9 )`, [ logData.systemName, logData.userId, logData.loginResult, logData.errorMessage || null, logData.remoteAddr, logData.recptnDt || null, logData.recptnRsltDtl || null, logData.recptnRslt || null, logData.recptnRsltCd || null, ] ); logger.info( `로그인 로그 기록 완료: ${logData.userId} (${logData.loginResult ? "성공" : "실패"})` ); } catch (error) { logger.error( `로그인 로그 기록 중 오류 발생: ${error instanceof Error ? error.message : error}` ); // 로그 기록 실패는 로그인 프로세스를 중단하지 않음 } } /** * 기존 Java SessionManager.setSessionManage() 메서드 포팅 * 로그인 성공 시 사용자 정보를 조회하여 PersonBean 형태로 반환 */ static async getUserInfo(userId: string): Promise { try { // 1. 사용자 기본 정보 조회 (Raw Query 전환) const userResult = await query<{ sabun: string | null; user_id: string; user_name: string; user_name_eng: string | null; user_name_cn: string | null; dept_code: string | null; dept_name: string | null; position_code: string | null; position_name: string | null; email: string | null; tel: string | null; cell_phone: string | null; user_type: string | null; user_type_name: string | null; partner_objid: string | null; company_code: string | null; locale: string | null; photo: Buffer | null; }>( `SELECT sabun, user_id, user_name, user_name_eng, user_name_cn, dept_code, dept_name, position_code, position_name, email, tel, cell_phone, user_type, user_type_name, partner_objid, company_code, locale, photo FROM user_info WHERE user_id = $1`, [userId] ); const userInfo = userResult.length > 0 ? userResult[0] : null; if (!userInfo) { return null; } // 2. 권한 정보 조회 (Raw Query 전환 - JOIN으로 최적화) const authResult = await query<{ auth_name: string }>( `SELECT am.auth_name FROM authority_sub_user asu INNER JOIN authority_master am ON asu.master_objid = am.objid WHERE asu.user_id = $1`, [userId] ); // 권한명들을 쉼표로 연결 const authNames = authResult.map((row) => row.auth_name).join(","); // 3. 회사 정보 조회 (Raw Query 전환) const companyResult = await query<{ company_name: string }>( "SELECT company_name FROM company_mng WHERE company_code = $1", [userInfo.company_code || "ILSHIN"] ); const companyName = companyResult.length > 0 ? companyResult[0].company_name : undefined; // DB에서 조회한 원본 사용자 정보 상세 로그 //console.log("🔍 AuthService - DB 원본 사용자 정보:", { // userId: userInfo.user_id, // company_code: userInfo.company_code, // company_code_type: typeof userInfo.company_code, // company_code_is_null: userInfo.company_code === null, // company_code_is_undefined: userInfo.company_code === undefined, // company_code_is_empty: userInfo.company_code === "", // dept_code: userInfo.dept_code, // allUserFields: Object.keys(userInfo), // companyInfo: companyInfo?.company_name, //}); // PersonBean 형태로 변환 (null 값을 undefined로 변환) const companyCode = userInfo.company_code || "ILSHIN"; const userType = userInfo.user_type || "USER"; const personBean: PersonBean = { userId: userInfo.user_id, userName: userInfo.user_name || "", userNameEng: userInfo.user_name_eng || undefined, userNameCn: userInfo.user_name_cn || undefined, deptCode: userInfo.dept_code || undefined, deptName: userInfo.dept_name || undefined, positionCode: userInfo.position_code || undefined, positionName: userInfo.position_name || undefined, email: userInfo.email || undefined, tel: userInfo.tel || undefined, cellPhone: userInfo.cell_phone || undefined, userType: userType, userTypeName: userInfo.user_type_name || undefined, partnerObjid: userInfo.partner_objid || undefined, authName: authNames || undefined, companyCode: companyCode, companyName: companyName, // 회사명 추가 photo: userInfo.photo ? `data:image/jpeg;base64,${Buffer.from(userInfo.photo).toString("base64")}` : undefined, locale: userInfo.locale || "KR", // 권한 레벨 정보 추가 (3단계 체계) isSuperAdmin: companyCode === "*" && userType === "SUPER_ADMIN", isCompanyAdmin: userType === "COMPANY_ADMIN" && companyCode !== "*", isAdmin: (companyCode === "*" && userType === "SUPER_ADMIN") || userType === "COMPANY_ADMIN", }; //console.log("📦 AuthService - 최종 PersonBean:", { // userId: personBean.userId, // companyCode: personBean.companyCode, // deptCode: personBean.deptCode, //}); logger.info(`사용자 정보 조회 완료: ${userId}`); return personBean; } catch (error) { logger.error( `사용자 정보 조회 중 오류 발생: ${error instanceof Error ? error.message : error}` ); return null; } } /** * JWT 토큰으로 사용자 정보 조회 */ static async getUserInfoFromToken(token: string): Promise { try { const userInfo = JwtUtils.verifyToken(token); return userInfo; } catch (error) { logger.error( `토큰에서 사용자 정보 조회 중 오류 발생: ${error instanceof Error ? error.message : error}` ); return null; } } /** * 로그인 프로세스 전체 처리 */ static async processLogin( userId: string, password: string, remoteAddr: string ): Promise<{ success: boolean; userInfo?: PersonBean; token?: string; errorReason?: string; }> { try { // 1. 로그인 검증 const loginResult = await this.loginPwdCheck(userId, password); // 2. 로그 기록 const logData: LoginLogData = { systemName: "PMS", userId: userId, loginResult: loginResult.loginResult, errorMessage: loginResult.errorReason, remoteAddr: remoteAddr, }; await this.insertLoginAccessLog(logData); if (loginResult.loginResult) { // 3. 사용자 정보 조회 const userInfo = await this.getUserInfo(userId); if (!userInfo) { return { success: false, errorReason: "사용자 정보를 조회할 수 없습니다.", }; } // 4. JWT 토큰 생성 const token = JwtUtils.generateToken(userInfo); logger.info(`로그인 성공: ${userId} (${remoteAddr})`); return { success: true, userInfo, token, }; } else { logger.warn( `로그인 실패: ${userId} - ${loginResult.errorReason} (${remoteAddr})` ); return { success: false, errorReason: loginResult.errorReason, }; } } catch (error) { logger.error( `로그인 프로세스 중 오류 발생: ${error instanceof Error ? error.message : error}` ); return { success: false, errorReason: "로그인 처리 중 오류가 발생했습니다.", }; } } /** * 로그아웃 프로세스 처리 */ static async processLogout( userId: string, remoteAddr: string ): Promise { try { // 로그아웃 로그 기록 const logData: LoginLogData = { systemName: "PMS", userId: userId, loginResult: false, errorMessage: "로그아웃", remoteAddr: remoteAddr, }; await this.insertLoginAccessLog(logData); logger.info(`로그아웃 완료: ${userId} (${remoteAddr})`); } catch (error) { logger.error( `로그아웃 처리 중 오류 발생: ${error instanceof Error ? error.message : error}` ); } } /** * 공차중계 회원가입 처리 * - user_info 테이블에 사용자 정보 저장 * - vehicles 테이블에 차량 정보 저장 */ static async signupDriver(data: { userId: string; password: string; userName: string; phoneNumber: string; licenseNumber: string; vehicleNumber: string; vehicleType?: string; }): Promise<{ success: boolean; message?: string }> { try { const { userId, password, userName, phoneNumber, licenseNumber, vehicleNumber, vehicleType, } = data; // 1. 중복 사용자 확인 const existingUser = await query( `SELECT user_id FROM user_info WHERE user_id = $1`, [userId] ); if (existingUser.length > 0) { return { success: false, message: "이미 존재하는 아이디입니다.", }; } // 2. 중복 차량번호 확인 const existingVehicle = await query( `SELECT vehicle_number FROM vehicles WHERE vehicle_number = $1`, [vehicleNumber] ); if (existingVehicle.length > 0) { return { success: false, message: "이미 등록된 차량번호입니다.", }; } // 3. 비밀번호 암호화 (MD5 - 기존 시스템 호환) const crypto = require("crypto"); const hashedPassword = crypto .createHash("md5") .update(password) .digest("hex"); // 4. 사용자 정보 저장 (user_info) await query( `INSERT INTO user_info ( user_id, user_password, user_name, cell_phone, license_number, vehicle_number, company_code, user_type, signup_type, status, regdate ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())`, [ userId, hashedPassword, userName, phoneNumber, licenseNumber, vehicleNumber, "COMPANY_13", // 기본 회사 코드 null, // user_type: null "DRIVER", // signup_type: 공차중계 회원가입 사용자 "active", // status: active ] ); // 5. 차량 정보 저장 (vehicles) await query( `INSERT INTO vehicles ( vehicle_number, vehicle_type, driver_name, driver_phone, status, company_code, user_id, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())`, [ vehicleNumber, vehicleType || null, userName, phoneNumber, "off", // 초기 상태: off (대기) "COMPANY_13", // 기본 회사 코드 userId, // 사용자 ID 연결 ] ); logger.info(`공차중계 회원가입 성공: ${userId}, 차량번호: ${vehicleNumber}`); return { success: true, message: "회원가입이 완료되었습니다.", }; } catch (error: any) { logger.error("공차중계 회원가입 오류:", error); return { success: false, message: error.message || "회원가입 중 오류가 발생했습니다.", }; } } }