Files
TK-FB-Project/api.hyungi.net/routes/systemRoutes.js
Hyungi Ahn 349ab60561 refactor: Phase 3.3 - 통합 인증/인가 미들웨어 시스템 구축
주요 변경사항:

1. middlewares/auth.js 신규 생성 (355 lines)
   * 4개 핵심 미들웨어 통합:
     - requireAuth: JWT 토큰 검증
     - requireRole(...roles): 특정 역할 체크
     - requireMinLevel(level): 계층적 권한 레벨 체크
     - requireOwnerOrAdmin(options): 소유자/관리자 체크

   * 커스텀 에러 클래스 적용:
     - AuthenticationError (401)
     - ForbiddenError (403)

   * 구조화된 로깅 시스템 통합
   * 레거시 호환성 별칭 제공:
     - verifyToken = requireAuth
     - requireAdmin = requireRole('admin', 'system')
     - requireSystem = requireRole('system')

   * ACCESS_LEVELS 상수 정의 및 export

2. 라우터 업데이트 (새로운 미들웨어 적용)
   * routes/workReportAnalysisRoutes.js
     - authMiddleware → auth로 변경
     - requireAdmin → requireRole('admin', 'system')

   * routes/systemRoutes.js
     - 커스텀 requireSystemAccess 제거
     - requireRole('system') 사용
     - 14줄 코드 감소 (298 → 284 lines)

   * routes/auth.js
     - utils/access의 requireAccess 제거
     - requireAuth + requireRole 조합 사용

3. 레거시 호환성 래퍼 (하위 호환성 유지)
   * middlewares/authMiddleware.js (89 → 37 lines, 58% 감소)
     - auth.js의 래퍼로 변경
     - @deprecated 태그 추가
     - 기존 22개 파일 호환성 유지

   * middlewares/accessMiddleware.js (33 → 30 lines)
     - requireMinLevel 래퍼로 변경
     - @deprecated 태그 및 마이그레이션 가이드 추가

   * utils/access.js
     - requireAccess 레거시 함수 추가 (하위 호환성)
     - 유틸리티 함수들은 그대로 유지

기술적 개선사항:
- 중복 코드 제거: 4개 파일에 분산된 인증 로직 통합
- 일관된 에러 처리: 커스텀 에러 클래스 사용
- 상세한 로깅: 인증/인가 실패 원인 추적 가능
- 보안 강화: TokenExpiredError, JsonWebTokenError 세분화 처리
- 확장성: 새로운 권한 체크 패턴 쉽게 추가 가능
- JSDoc 문서화: 모든 함수에 상세한 사용 예제 포함

통합 전후 비교:
- 미들웨어 파일: 4개 → 1개 (통합) + 3개 (래퍼)
- 중복 코드: ~150 lines → 0 lines
- 일관성: 4가지 다른 패턴 → 1가지 통합 패턴

레거시 호환성:
- 기존 22개 라우터 파일 중 19개는 수정 없이 동작
- 3개 라우터만 새로운 패턴으로 업데이트 (예시용)
- verifyToken, requireAdmin, requireSystem 별칭 제공

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-11 12:12:44 +09:00

287 lines
6.8 KiB
JavaScript

// 시스템 관리 라우트
const express = require('express');
const router = express.Router();
const systemController = require('../controllers/systemController');
const { requireAuth, requireRole } = require('../middlewares/auth');
// 모든 라우트에 인증 및 시스템 권한 확인 적용
router.use(requireAuth);
router.use(requireRole('system'));
// ===== 시스템 상태 관련 =====
/**
* GET /api/system/status
* 시스템 전체 상태 확인
*/
router.get('/status', systemController.getSystemStatus);
/**
* GET /api/system/db-status
* 데이터베이스 상태 확인
*/
router.get('/db-status', systemController.getDatabaseStatus);
/**
* GET /api/system/alerts
* 시스템 알림 조회
*/
router.get('/alerts', systemController.getSystemAlerts);
/**
* GET /api/system/recent-activities
* 최근 시스템 활동 조회
*/
router.get('/recent-activities', systemController.getRecentActivities);
// ===== 사용자 관리 관련 =====
/**
* GET /api/system/users/stats
* 사용자 통계 조회
*/
router.get('/users/stats', systemController.getUserStats);
/**
* GET /api/system/users
* 모든 사용자 목록 조회
*/
router.get('/users', systemController.getAllUsers);
/**
* POST /api/system/users
* 새 사용자 생성
*/
router.post('/users', systemController.createUser);
/**
* PUT /api/system/users/:id
* 사용자 정보 수정
*/
router.put('/users/:id', systemController.updateUser);
/**
* DELETE /api/system/users/:id
* 사용자 삭제
*/
router.delete('/users/:id', systemController.deleteUser);
/**
* POST /api/system/users/:id/reset-password
* 사용자 비밀번호 재설정
*/
router.post('/users/:id/reset-password', systemController.resetUserPassword);
// ===== 시스템 로그 관련 =====
/**
* GET /api/system/logs/login
* 로그인 로그 조회
*/
router.get('/logs/login', async (req, res) => {
try {
const { getDb } = require('../dbPool');
const db = await getDb();
const { page = 1, limit = 50, status, user_id, start_date, end_date } = req.query;
const offset = (page - 1) * limit;
let whereClause = '1=1';
const params = [];
if (status) {
whereClause += ' AND ll.login_status = ?';
params.push(status);
}
if (user_id) {
whereClause += ' AND ll.user_id = ?';
params.push(user_id);
}
if (start_date) {
whereClause += ' AND ll.login_time >= ?';
params.push(start_date);
}
if (end_date) {
whereClause += ' AND ll.login_time <= ?';
params.push(end_date);
}
const [logs] = await db.query(`
SELECT
ll.log_id,
ll.user_id,
u.username,
u.name,
ll.login_time,
ll.ip_address,
ll.user_agent,
ll.login_status,
ll.failure_reason
FROM login_logs ll
LEFT JOIN Users u ON ll.user_id = u.user_id
WHERE ${whereClause}
ORDER BY ll.login_time DESC
LIMIT ? OFFSET ?
`, [...params, parseInt(limit), parseInt(offset)]);
const [totalCount] = await db.query(`
SELECT COUNT(*) as count
FROM login_logs ll
WHERE ${whereClause}
`, params);
res.json({
success: true,
data: {
logs,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: totalCount[0].count,
pages: Math.ceil(totalCount[0].count / limit)
}
}
});
} catch (error) {
console.error('로그인 로그 조회 오류:', error);
res.status(500).json({
success: false,
error: '로그인 로그를 조회할 수 없습니다.'
});
}
});
/**
* GET /api/system/logs/password-changes
* 비밀번호 변경 로그 조회
*/
router.get('/logs/password-changes', async (req, res) => {
try {
const { getDb } = require('../dbPool');
const db = await getDb();
const { page = 1, limit = 50 } = req.query;
const offset = (page - 1) * limit;
const [logs] = await db.query(`
SELECT
pcl.log_id,
pcl.user_id,
u.username,
u.name,
pcl.changed_by_user_id,
admin.username as changed_by_username,
admin.name as changed_by_name,
pcl.changed_at,
pcl.change_type,
pcl.ip_address
FROM password_change_logs pcl
LEFT JOIN Users u ON pcl.user_id = u.user_id
LEFT JOIN Users admin ON pcl.changed_by_user_id = admin.user_id
ORDER BY pcl.changed_at DESC
LIMIT ? OFFSET ?
`, [parseInt(limit), parseInt(offset)]);
const [totalCount] = await db.query('SELECT COUNT(*) as count FROM password_change_logs');
res.json({
success: true,
data: {
logs,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: totalCount[0].count,
pages: Math.ceil(totalCount[0].count / limit)
}
}
});
} catch (error) {
console.error('비밀번호 변경 로그 조회 오류:', error);
res.status(500).json({
success: false,
error: '비밀번호 변경 로그를 조회할 수 없습니다.'
});
}
});
/**
* GET /api/system/logs/activity
* 활동 로그 조회 (activity_logs 테이블이 있는 경우)
*/
router.get('/logs/activity', async (req, res) => {
try {
const { getDb } = require('../dbPool');
const db = await getDb();
const { page = 1, limit = 50, activity_type, user_id } = req.query;
const offset = (page - 1) * limit;
let whereClause = '1=1';
const params = [];
if (activity_type) {
whereClause += ' AND al.activity_type = ?';
params.push(activity_type);
}
if (user_id) {
whereClause += ' AND al.user_id = ?';
params.push(user_id);
}
const [logs] = await db.query(`
SELECT
al.log_id,
al.user_id,
u.username,
u.name,
al.activity_type,
al.table_name,
al.record_id,
al.action,
al.ip_address,
al.user_agent,
al.created_at
FROM activity_logs al
LEFT JOIN Users u ON al.user_id = u.user_id
WHERE ${whereClause}
ORDER BY al.created_at DESC
LIMIT ? OFFSET ?
`, [...params, parseInt(limit), parseInt(offset)]);
const [totalCount] = await db.query(`
SELECT COUNT(*) as count
FROM activity_logs al
WHERE ${whereClause}
`, params);
res.json({
success: true,
data: {
logs,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: totalCount[0].count,
pages: Math.ceil(totalCount[0].count / limit)
}
}
});
} catch (error) {
console.error('활동 로그 조회 오류:', error);
res.status(500).json({
success: false,
error: '활동 로그를 조회할 수 없습니다.'
});
}
});
module.exports = router;