refactor: System1 API 인증 체계 SSO 전환 및 마이그레이션 정비

- SSO JWT 인증으로 전환 (auth.service.js)
- worker_id → user_id 마이그레이션 완료
- departments 연동, CORS 미들웨어 정리
- 불필요 파일 삭제 (tk_database.db, visitRequestController.js)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-06 23:18:00 +09:00
parent 2f7e083db0
commit ec755ed52f
47 changed files with 181 additions and 716 deletions

View File

@@ -9,20 +9,12 @@
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const mysql = require('mysql2/promise');
const { getDb } = require('../dbPool');
const { verifyToken } = require('../middlewares/auth');
const { validatePassword, getPasswordError } = require('../utils/passwordValidator');
const router = express.Router();
const authController = require('../controllers/authController');
// DB 연결 설정
const dbConfig = {
host: process.env.DB_HOST || 'db_hyungi_net',
user: process.env.DB_USER || 'hyungi',
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME || 'hyungi'
};
// 로그인 시도 추적 (메모리 기반 - 실제로는 Redis 권장)
const loginAttempts = new Map();
@@ -143,7 +135,7 @@ router.post('/refresh-token', async (req, res) => {
return res.status(401).json({ error: '유효하지 않은 토큰입니다.' });
}
const connection = await mysql.createConnection(dbConfig);
const connection = await getDb();
// 사용자 정보 조회
const [users] = await connection.execute(
@@ -151,8 +143,6 @@ router.post('/refresh-token', async (req, res) => {
[decoded.user_id]
);
await connection.end();
if (users.length === 0) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
}
@@ -167,7 +157,7 @@ router.post('/refresh-token', async (req, res) => {
access_level: user.access_level,
name: user.name || user.username
},
process.env.JWT_SECRET || 'your-secret-key',
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
);
@@ -224,7 +214,7 @@ router.post('/change-password', verifyToken, async (req, res) => {
});
}
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
// 현재 사용자의 비밀번호 조회
const [users] = await connection.execute(
@@ -290,10 +280,6 @@ router.post('/change-password', verifyToken, async (req, res) => {
success: false,
error: '서버 오류가 발생했습니다.'
});
} finally {
if (connection) {
await connection.end();
}
}
});
@@ -334,7 +320,7 @@ router.post('/admin/change-password', verifyToken, async (req, res) => {
});
}
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
// 대상 사용자 확인
const [users] = await connection.execute(
@@ -391,10 +377,6 @@ router.post('/admin/change-password', verifyToken, async (req, res) => {
success: false,
error: '서버 오류가 발생했습니다.'
});
} finally {
if (connection) {
await connection.end();
}
}
});
@@ -453,7 +435,7 @@ router.get('/me', verifyToken, async (req, res) => {
try {
const userId = req.user.user_id;
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
const [rows] = await connection.execute(
'SELECT user_id, username, name, email, access_level, last_login_at, created_at FROM users WHERE user_id = ?',
[userId]
@@ -477,10 +459,6 @@ router.get('/me', verifyToken, async (req, res) => {
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
} finally {
if (connection) {
await connection.end();
}
}
});
@@ -516,7 +494,7 @@ router.post('/register', verifyToken, async (req, res) => {
});
}
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
// 사용자명 중복 체크
const [existing] = await connection.execute(
@@ -586,10 +564,6 @@ router.post('/register', verifyToken, async (req, res) => {
success: false,
error: '서버 오류가 발생했습니다.'
});
} finally {
if (connection) {
await connection.end();
}
}
});
@@ -600,7 +574,7 @@ router.get('/users', verifyToken, async (req, res) => {
let connection;
try {
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
// 기본 쿼리 (role 테이블과 JOIN)
let query = `
@@ -656,10 +630,6 @@ router.get('/users', verifyToken, async (req, res) => {
} catch (error) {
console.error('Get users error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
} finally {
if (connection) {
await connection.end();
}
}
});
@@ -691,7 +661,7 @@ router.put('/users/:id', verifyToken, async (req, res) => {
}
}
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
// 사용자 존재 확인
const [existing] = await connection.execute(
@@ -802,10 +772,6 @@ router.put('/users/:id', verifyToken, async (req, res) => {
success: false,
error: '서버 오류가 발생했습니다.'
});
} finally {
if (connection) {
await connection.end();
}
}
});
@@ -834,7 +800,7 @@ router.delete('/users/:id', verifyToken, async (req, res) => {
});
}
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
// 사용자 존재 확인
const [existing] = await connection.execute(
@@ -871,10 +837,6 @@ router.delete('/users/:id', verifyToken, async (req, res) => {
success: false,
error: '서버 오류가 발생했습니다.'
});
} finally {
if (connection) {
await connection.end();
}
}
});
@@ -887,17 +849,13 @@ router.post('/logout', verifyToken, async (req, res) => {
// 로그아웃 시간 기록 (선택사항)
let connection;
try {
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
await connection.execute(
'UPDATE login_logs SET logout_time = NOW() WHERE user_id = ? AND logout_time IS NULL ORDER BY login_time DESC LIMIT 1',
[req.user.user_id]
);
} catch (error) {
console.error('로그아웃 기록 실패:', error);
} finally {
if (connection) {
await connection.end();
}
}
res.json({
@@ -916,7 +874,7 @@ router.get('/login-history', verifyToken, async (req, res) => {
const { limit = 50, offset = 0 } = req.query;
const userId = req.user.user_id;
connection = await mysql.createConnection(dbConfig);
connection = await getDb();
// 본인의 로그인 이력만 조회 (관리자는 전체 조회 가능)
let query = `
@@ -958,10 +916,6 @@ router.get('/login-history', verifyToken, async (req, res) => {
} catch (error) {
console.error('Get login history error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
} finally {
if (connection) {
await connection.end();
}
}
});

View File

@@ -41,7 +41,6 @@ router.get('/check-overwrite', (req, res) => {
});
}
console.log(`🔍 덮어쓰기 권한 확인: 날짜=${date}, 작업자=${user_id} (누적입력모드)`);
// 누적입력 시스템에서는 항상 덮어쓰기 가능 (실제로는 누적만 함)
res.json({

View File

@@ -8,7 +8,6 @@ router.post('/setup-monthly-status', async (req, res) => {
try {
const db = await getDb();
console.log('📊 월별 집계 테이블 생성 중...');
// 1. 월별 작업자 상태 집계 테이블
await db.execute(`
@@ -86,7 +85,6 @@ router.post('/setup-monthly-status', async (req, res) => {
) COMMENT='월별 일자별 요약 테이블 (캘린더 최적화용)'
`);
console.log('📊 집계 프로시저 생성 중...');
// 3. 집계 업데이트 프로시저
await db.execute(`DROP PROCEDURE IF EXISTS UpdateMonthlyWorkerStatus`);
@@ -239,7 +237,6 @@ router.post('/setup-monthly-status', async (req, res) => {
END
`);
console.log('📊 트리거 생성 중...');
// 4. 트리거 생성
await db.execute(`DROP TRIGGER IF EXISTS tr_daily_work_reports_insert`);
@@ -276,7 +273,6 @@ router.post('/setup-monthly-status', async (req, res) => {
END
`);
console.log('📊 기존 데이터로 집계 테이블 초기화 중...');
// 5. 기존 작업 데이터로 집계 테이블 초기화
const [existingDates] = await db.execute(`
@@ -302,7 +298,6 @@ router.post('/setup-monthly-status', async (req, res) => {
}
if (i % 100 === 0) {
console.log(`📊 집계 초기화 진행률: ${processedCount}/${existingDates.length}`);
}
}
@@ -329,7 +324,7 @@ router.post('/setup-monthly-status', async (req, res) => {
});
} catch (error) {
console.error(' 월별 집계 시스템 설정 오류:', error);
console.error(' 월별 집계 시스템 설정 오류:', error);
res.status(500).json({
success: false,
message: '월별 집계 시스템 설정 중 오류가 발생했습니다.',
@@ -340,12 +335,10 @@ router.post('/setup-monthly-status', async (req, res) => {
router.post('/setup-attendance-db', async (req, res) => {
try {
console.log('🚀 근태 관리 DB 설정 API 호출됨');
const db = await getDb();
// 1. 근로 유형 테이블 생성
console.log('📋 근로 유형 테이블 생성 중...');
await db.execute(`
CREATE TABLE IF NOT EXISTS work_attendance_types (
id INT PRIMARY KEY AUTO_INCREMENT,
@@ -359,7 +352,6 @@ router.post('/setup-attendance-db', async (req, res) => {
`);
// 2. 휴가 유형 테이블 생성
console.log('🏖️ 휴가 유형 테이블 생성 중...');
await db.execute(`
CREATE TABLE IF NOT EXISTS vacation_types (
id INT PRIMARY KEY AUTO_INCREMENT,
@@ -374,7 +366,6 @@ router.post('/setup-attendance-db', async (req, res) => {
`);
// 3. 일일 근태 기록 테이블 생성
console.log('📊 일일 근태 기록 테이블 생성 중...');
await db.execute(`
CREATE TABLE IF NOT EXISTS daily_attendance_records (
id INT PRIMARY KEY AUTO_INCREMENT,
@@ -394,7 +385,6 @@ router.post('/setup-attendance-db', async (req, res) => {
`);
// 4. 작업자별 휴가 잔여 관리 테이블 생성
console.log('👥 작업자별 휴가 잔여 관리 테이블 생성 중...');
await db.execute(`
CREATE TABLE IF NOT EXISTS worker_vacation_balance (
id INT PRIMARY KEY AUTO_INCREMENT,
@@ -410,7 +400,6 @@ router.post('/setup-attendance-db', async (req, res) => {
`);
// 5. 기본 데이터 삽입
console.log('📝 기본 데이터 삽입 중...');
// 근로 유형 기본 데이터
await db.execute(`
@@ -446,7 +435,7 @@ router.post('/setup-attendance-db', async (req, res) => {
});
} catch (error) {
console.error(' DB 설정 API 오류:', error);
console.error(' DB 설정 API 오류:', error);
res.status(500).json({
success: false,
message: 'DB 설정 중 오류가 발생했습니다.',
@@ -460,7 +449,6 @@ router.post('/add-overtime-warning', async (req, res) => {
try {
const db = await getDb();
console.log('⚠️ 12시간 초과 상태 컬럼 추가 중...');
// 1. monthly_summary 테이블에 컬럼 추가
try {
@@ -468,10 +456,8 @@ router.post('/add-overtime-warning', async (req, res) => {
ALTER TABLE monthly_summary
ADD COLUMN overtime_warning_workers INT DEFAULT 0 COMMENT '확인필요(12시간초과) 작업자 수' AFTER error_workers
`);
console.log('✅ overtime_warning_workers 컬럼 추가 완료');
} catch (error) {
if (error.code === 'ER_DUP_FIELDNAME') {
console.log(' overtime_warning_workers 컬럼이 이미 존재합니다.');
} else {
throw error;
}
@@ -482,10 +468,8 @@ router.post('/add-overtime-warning', async (req, res) => {
ALTER TABLE monthly_summary
ADD COLUMN has_overtime_warning BOOLEAN DEFAULT FALSE COMMENT '확인필요 상태 있음' AFTER has_errors
`);
console.log('✅ has_overtime_warning 컬럼 추가 완료');
} catch (error) {
if (error.code === 'ER_DUP_FIELDNAME') {
console.log(' has_overtime_warning 컬럼이 이미 존재합니다.');
} else {
throw error;
}
@@ -551,7 +535,6 @@ router.post('/add-overtime-warning', async (req, res) => {
last_updated = CURRENT_TIMESTAMP;
END
`);
console.log('✅ UpdateDailySummary 프로시저 업데이트 완료');
res.json({
success: true,
@@ -561,7 +544,7 @@ router.post('/add-overtime-warning', async (req, res) => {
});
} catch (error) {
console.error(' 12시간 초과 상태 설정 오류:', error);
console.error(' 12시간 초과 상태 설정 오류:', error);
res.status(500).json({
success: false,
message: '12시간 초과 상태 설정 실패',
@@ -575,7 +558,6 @@ router.post('/migrate-existing-data', async (req, res) => {
try {
const db = await getDb();
console.log('🔄 기존 데이터 마이그레이션 시작...');
// 1. 기존 데이터 범위 확인
const [dateRange] = await db.execute(`
@@ -595,12 +577,10 @@ router.post('/migrate-existing-data', async (req, res) => {
}
const { min_date, max_date, total_reports } = dateRange[0];
console.log(`📊 데이터 범위: ${min_date} ~ ${max_date} (총 ${total_reports}건)`);
// 2. 기존 monthly_worker_status, monthly_summary 데이터 삭제
await db.execute('DELETE FROM monthly_summary');
await db.execute('DELETE FROM monthly_worker_status');
console.log('🗑️ 기존 집계 데이터 삭제 완료');
// 3. 날짜별로 작업자별 상태 재계산
const [allDates] = await db.execute(`
@@ -610,7 +590,6 @@ router.post('/migrate-existing-data', async (req, res) => {
ORDER BY report_date, worker_id
`, [min_date, max_date]);
console.log(`🔄 ${allDates.length}개 날짜-작업자 조합 처리 중...`);
let processedCount = 0;
for (const { report_date, worker_id } of allDates) {
@@ -620,10 +599,9 @@ router.post('/migrate-existing-data', async (req, res) => {
processedCount++;
if (processedCount % 50 === 0) {
console.log(`📈 진행률: ${processedCount}/${allDates.length} (${Math.round(processedCount/allDates.length*100)}%)`);
}
} catch (error) {
console.error(` ${report_date} ${worker_id} 처리 오류:`, error.message);
console.error(` ${report_date} ${worker_id} 처리 오류:`, error.message);
}
}
@@ -631,7 +609,6 @@ router.post('/migrate-existing-data', async (req, res) => {
const [workerStatusCount] = await db.execute('SELECT COUNT(*) as count FROM monthly_worker_status');
const [summaryCount] = await db.execute('SELECT COUNT(*) as count FROM monthly_summary');
console.log(`✅ 마이그레이션 완료:`);
console.log(` - monthly_worker_status: ${workerStatusCount[0].count}`);
console.log(` - monthly_summary: ${summaryCount[0].count}`);
@@ -649,7 +626,7 @@ router.post('/migrate-existing-data', async (req, res) => {
});
} catch (error) {
console.error(' 데이터 마이그레이션 오류:', error);
console.error(' 데이터 마이그레이션 오류:', error);
res.status(500).json({
success: false,
message: '데이터 마이그레이션 실패',
@@ -705,7 +682,7 @@ router.get('/check-data-status', async (req, res) => {
});
} catch (error) {
console.error(' DB 상태 확인 오류:', error);
console.error(' DB 상태 확인 오류:', error);
res.status(500).json({
success: false,
message: 'DB 상태 확인 실패',

View File

@@ -2,6 +2,7 @@
const express = require('express');
const router = express.Router();
const systemController = require('../controllers/systemController');
const userController = require('../controllers/userController');
const { requireAuth, requireRole } = require('../middlewares/auth');
// 모든 라우트에 인증 및 시스템 권한 확인 적용
@@ -46,31 +47,31 @@ router.get('/users/stats', systemController.getUserStats);
* GET /api/system/users
* 모든 사용자 목록 조회
*/
router.get('/users', systemController.getAllUsers);
router.get('/users', userController.getAllUsers);
/**
* POST /api/system/users
* 새 사용자 생성
*/
router.post('/users', systemController.createUser);
router.post('/users', userController.createUser);
/**
* PUT /api/system/users/:id
* 사용자 정보 수정
*/
router.put('/users/:id', systemController.updateUser);
router.put('/users/:id', userController.updateUser);
/**
* DELETE /api/system/users/:id
* 사용자 삭제
*/
router.delete('/users/:id', systemController.deleteUser);
router.delete('/users/:id', userController.deleteUser);
/**
* POST /api/system/users/:id/reset-password
* 사용자 비밀번호 재설정
*/
router.post('/users/:id/reset-password', systemController.resetUserPassword);
router.post('/users/:id/reset-password', userController.resetUserPassword);
// ===== 시스템 로그 관련 =====
@@ -219,7 +220,6 @@ router.post('/migrations/fix-work-type-id', async (req, res) => {
const { getDb } = require('../dbPool');
const db = await getDb();
console.log('🔄 TBM 기반 작업보고서 work_type_id 수정 시작...');
// 1. 수정 대상 확인
const [checkResult] = await db.query(`
@@ -256,7 +256,6 @@ router.post('/migrations/fix-work-type-id', async (req, res) => {
AND dwr.work_type_id != ta.task_id
`);
console.log(`✅ 업데이트 완료: ${updateResult.affectedRows}개 레코드 수정됨`);
// 3. 수정된 샘플 조회
const [samples] = await db.query(`