주요 변경사항:
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>
287 lines
6.8 KiB
JavaScript
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;
|