/** * User Model * * sso_users 테이블 CRUD 및 비밀번호 관리 * sso-auth-service/models/userModel.js 기반 */ const { getPool } = require('../shared/config/database'); const bcrypt = require('bcrypt'); const crypto = require('crypto'); /** * pbkdf2_sha256 해시 검증 (passlib 호환) */ function verifyPbkdf2(password, storedHash) { try { const parts = storedHash.split('$'); if (parts.length < 5) return false; const rounds = parseInt(parts[2]); const salt = parts[3].replace(/\./g, '+'); const hash = parts[4].replace(/\./g, '+'); const padded = (s) => s + '='.repeat((4 - s.length % 4) % 4); const saltBuffer = Buffer.from(padded(salt), 'base64'); const expectedHash = Buffer.from(padded(hash), 'base64'); const derivedKey = crypto.pbkdf2Sync(password, saltBuffer, rounds, expectedHash.length, 'sha256'); return crypto.timingSafeEqual(derivedKey, expectedHash); } catch (err) { console.error('pbkdf2 verify error:', err.message); return false; } } /** * 비밀번호 검증 (bcrypt 또는 pbkdf2_sha256 자동 감지) */ async function verifyPassword(password, storedHash) { if (!password || !storedHash) return false; if (storedHash.startsWith('$pbkdf2-sha256$')) { return verifyPbkdf2(password, storedHash); } if (storedHash.startsWith('$2b$') || storedHash.startsWith('$2a$')) { return bcrypt.compare(password, storedHash); } return false; } /** * bcrypt로 비밀번호 해시 생성 */ async function hashPassword(password) { return bcrypt.hash(password, 10); } async function findByUsername(username) { const db = getPool(); const [rows] = await db.query( 'SELECT * FROM sso_users WHERE username = ? AND is_active = TRUE', [username] ); return rows[0] || null; } async function findById(userId) { const db = getPool(); const [rows] = await db.query( 'SELECT * FROM sso_users WHERE user_id = ?', [userId] ); return rows[0] || null; } async function findAll() { const db = getPool(); const [rows] = await db.query( 'SELECT user_id, username, name, department, department_id, role, system1_access, system2_access, system3_access, is_active, last_login, created_at FROM sso_users WHERE partner_company_id IS NULL ORDER BY user_id' ); return rows; } async function create({ username, password, name, department, department_id, role }) { const db = getPool(); const password_hash = await hashPassword(password); const [result] = await db.query( `INSERT INTO sso_users (username, password_hash, name, department, department_id, role) VALUES (?, ?, ?, ?, ?, ?)`, [username, password_hash, name || null, department || null, department_id || null, role || 'user'] ); return findById(result.insertId); } async function update(userId, data) { const db = getPool(); const fields = []; const values = []; if (data.name !== undefined) { fields.push('name = ?'); values.push(data.name); } if (data.department !== undefined) { fields.push('department = ?'); values.push(data.department); } if (data.department_id !== undefined) { fields.push('department_id = ?'); values.push(data.department_id); } if (data.role !== undefined) { fields.push('role = ?'); values.push(data.role); } if (data.system1_access !== undefined) { fields.push('system1_access = ?'); values.push(data.system1_access); } if (data.system2_access !== undefined) { fields.push('system2_access = ?'); values.push(data.system2_access); } if (data.system3_access !== undefined) { fields.push('system3_access = ?'); values.push(data.system3_access); } if (data.is_active !== undefined) { fields.push('is_active = ?'); values.push(data.is_active); } if (data.password) { fields.push('password_hash = ?'); values.push(await hashPassword(data.password)); } if (fields.length === 0) return findById(userId); values.push(userId); await db.query( `UPDATE sso_users SET ${fields.join(', ')} WHERE user_id = ?`, values ); return findById(userId); } async function deleteUser(userId) { const db = getPool(); await db.query('UPDATE sso_users SET is_active = FALSE WHERE user_id = ?', [userId]); } module.exports = { verifyPassword, hashPassword, findByUsername, findById, findAll, create, update, deleteUser, getPool };