Files
tk-factory-services/user-management/api/models/userModel.js
Hyungi Ahn 0c149673fb refactor: shared 모듈 추출 Phase 1~4 (notifyHelper, errors, logger, auth, dbPool)
Phase 1: notifyHelper.js → shared/utils/ (4개 서비스 중복 제거)
Phase 2: auth.js → shared/middleware/ (system1/system2 통합)
Phase 3: errors.js + logger.js → shared/utils/ (system1/system2 통합)
Phase 4: DB pool → shared/config/database.js (Group B 4개 서비스 통합)

- Docker 빌드 컨텍스트를 루트로 변경 (6개 API 서비스)
- 기존 파일은 re-export 패턴으로 consumer 변경 0개 유지
- .dockerignore 추가로 빌드 최적화

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 19:07:22 +09:00

142 lines
4.2 KiB
JavaScript

/**
* 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
};