refactor: 보안 취약점 제거 + 데드코드 정리 + 프론트엔드 중복 통합
- 인증 없는 임시 엔드포인트 삭제 (index.js, healthRoutes.js, publicPaths) - skipAuth 우회 라우트 삭제 (workAnalysis.js) - 하드코딩 유저 백도어 삭제 (routes/auth.js) - 안전체크 CRUD에 admin 권한 추가 (tbmRoutes.js) - deprecated shim 3개 삭제 + 8개 소비 파일 import 정리 (auth.js 직접 참조) - 미사용 pageAccessController, db.js, common/security.js 삭제 - escapeHtml() 5곳 로컬 중복 제거 → api-base.js 전역 사용 - userPageAccess_v2_v2 캐시 키 버그 수정 (app-init.js) - system3 .bak 파일 삭제, PROGRESS.md 업데이트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { getAnalysisData } = require('../controllers/analysisController');
|
||||
const { verifyToken } = require('../middlewares/authMiddleware'); // 인증 미들웨어 추가
|
||||
const { verifyToken } = require('../middlewares/auth');
|
||||
|
||||
// GET /api/analysis?startDate=...&endDate=...
|
||||
router.get('/', verifyToken, getAnalysisData);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const AttendanceController = require('../controllers/attendanceController');
|
||||
const { verifyToken } = require('../middlewares/authMiddleware');
|
||||
const { verifyToken } = require('../middlewares/auth');
|
||||
|
||||
// 모든 라우트에 인증 미들웨어 적용
|
||||
router.use(verifyToken);
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
// routes/auth.js
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { requireAuth, requireRole } = require('../middlewares/auth');
|
||||
const router = express.Router();
|
||||
|
||||
// 임시 사용자 데이터 (실제 운영 시 DB 사용 필수)
|
||||
// 비밀번호 해시는 bcrypt.hash('password', 10)으로 생성됨
|
||||
let users = [
|
||||
{
|
||||
user_id: 1,
|
||||
username: 'admin',
|
||||
password: '$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZRGdjGj/n3.w7VtL.r8yR.nTfC7Hy', // 'password' 해시
|
||||
name: '관리자',
|
||||
access_level: 'admin',
|
||||
worker_id: null,
|
||||
created_at: new Date()
|
||||
},
|
||||
{
|
||||
user_id: 2,
|
||||
username: 'group_leader1',
|
||||
password: '$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZRGdjGj/n3.w7VtL.r8yR.nTfC7Hy', // 'password' 해시
|
||||
name: '김그룹장',
|
||||
access_level: 'group_leader',
|
||||
worker_id: 1,
|
||||
created_at: new Date()
|
||||
}
|
||||
];
|
||||
|
||||
// 보안 경고: 운영 환경에서는 반드시 .env의 JWT_SECRET을 설정해야 합니다
|
||||
if (!process.env.JWT_SECRET) {
|
||||
console.warn('⚠️ WARNING: JWT_SECRET이 설정되지 않았습니다. 운영 환경에서는 반드시 설정하세요!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인
|
||||
*/
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: '사용자명과 비밀번호를 입력해주세요.' });
|
||||
}
|
||||
|
||||
const user = users.find(u => u.username === username);
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: '사용자를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 비밀번호 확인 (bcrypt.compare 사용)
|
||||
const isValid = await bcrypt.compare(password, user.password);
|
||||
if (!isValid) {
|
||||
return res.status(401).json({ error: '비밀번호가 올바르지 않습니다.' });
|
||||
}
|
||||
|
||||
// JWT 토큰 생성
|
||||
const token = jwt.sign(
|
||||
{
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
access_level: user.access_level,
|
||||
worker_id: user.worker_id
|
||||
},
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
token,
|
||||
user: {
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
access_level: user.access_level,
|
||||
worker_id: user.worker_id
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 현재 사용자 정보 조회
|
||||
*/
|
||||
router.get('/me', requireAuth, (req, res) => {
|
||||
try {
|
||||
const userId = req.user.user_id;
|
||||
const user = users.find(u => u.user_id === userId);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
access_level: user.access_level,
|
||||
worker_id: user.worker_id
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Get current user error:', error);
|
||||
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 사용자 등록 (관리자만)
|
||||
*/
|
||||
router.post('/register', requireAuth, requireRole('admin', 'system'), async (req, res) => {
|
||||
try {
|
||||
const { username, password, name, access_level, worker_id } = req.body;
|
||||
|
||||
if (!username || !password || !name || !access_level) {
|
||||
return res.status(400).json({ error: '필수 항목을 모두 입력해주세요.' });
|
||||
}
|
||||
|
||||
// 사용자명 중복 체크
|
||||
const existingUser = users.find(u => u.username === username);
|
||||
if (existingUser) {
|
||||
return res.status(409).json({ error: '이미 존재하는 사용자명입니다.' });
|
||||
}
|
||||
|
||||
// 비밀번호 해시
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
|
||||
const newUser = {
|
||||
user_id: users.length + 1,
|
||||
username,
|
||||
password: hashedPassword,
|
||||
name,
|
||||
access_level,
|
||||
worker_id: worker_id || null,
|
||||
created_at: new Date()
|
||||
};
|
||||
|
||||
users.push(newUser);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '사용자가 성공적으로 등록되었습니다.',
|
||||
user: {
|
||||
user_id: newUser.user_id,
|
||||
username: newUser.username,
|
||||
name: newUser.name,
|
||||
access_level: newUser.access_level,
|
||||
worker_id: newUser.worker_id
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Register error:', error);
|
||||
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 사용자 목록 조회 (관리자만)
|
||||
*/
|
||||
router.get('/users', requireAuth, requireRole('admin', 'system'), (req, res) => {
|
||||
try {
|
||||
const userList = users.map(user => ({
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
access_level: user.access_level,
|
||||
worker_id: user.worker_id,
|
||||
created_at: user.created_at
|
||||
}));
|
||||
|
||||
res.json(userList);
|
||||
} catch (error) {
|
||||
console.error('Get users error:', error);
|
||||
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 사용자 삭제 (관리자만)
|
||||
*/
|
||||
router.delete('/users/:id', requireAuth, requireRole('admin', 'system'), (req, res) => {
|
||||
try {
|
||||
const userId = parseInt(req.params.id);
|
||||
|
||||
// 자기 자신 삭제 방지
|
||||
if (userId === req.user.user_id) {
|
||||
return res.status(400).json({ error: '자기 자신은 삭제할 수 없습니다.' });
|
||||
}
|
||||
|
||||
const userIndex = users.findIndex(u => u.user_id === userId);
|
||||
if (userIndex === -1) {
|
||||
return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
users.splice(userIndex, 1);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '사용자가 성공적으로 삭제되었습니다.'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Delete user error:', error);
|
||||
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -10,7 +10,7 @@ const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const mysql = require('mysql2/promise');
|
||||
const { verifyToken } = require('../middlewares/authMiddleware');
|
||||
const { verifyToken } = require('../middlewares/auth');
|
||||
const { validatePassword, getPasswordError } = require('../utils/passwordValidator');
|
||||
const router = express.Router();
|
||||
const authController = require('../controllers/authController');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const departmentController = require('../controllers/departmentController');
|
||||
const { requireAuth, requireRole } = require('../middlewares/authMiddleware');
|
||||
const { requireAuth, requireRole } = require('../middlewares/auth');
|
||||
|
||||
// 부서 목록 조회 (인증 필요)
|
||||
router.get('/', requireAuth, departmentController.getAll);
|
||||
|
||||
@@ -25,99 +25,4 @@ router.get('/detail', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 임시 마이그레이션 엔드포인트 - TBM work_type_id 수정
|
||||
// 실행 후 이 코드를 삭제하세요!
|
||||
router.post('/migrate-work-type-id', async (req, res) => {
|
||||
try {
|
||||
const { getDb } = require('../dbPool');
|
||||
const db = await getDb();
|
||||
|
||||
console.log('🔄 TBM 기반 작업보고서 work_type_id 수정 시작...');
|
||||
|
||||
// 1. 수정 대상 확인
|
||||
const [checkResult] = await db.query(`
|
||||
SELECT
|
||||
dwr.id,
|
||||
dwr.work_type_id as current_work_type_id,
|
||||
ta.task_id as correct_task_id,
|
||||
w.worker_name,
|
||||
dwr.report_date
|
||||
FROM daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
INNER JOIN workers w ON dwr.worker_id = w.worker_id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
AND ta.task_id IS NOT NULL
|
||||
AND dwr.work_type_id != ta.task_id
|
||||
ORDER BY dwr.report_date DESC
|
||||
`);
|
||||
|
||||
console.log(`📊 수정 대상: ${checkResult.length}개 레코드`);
|
||||
|
||||
if (checkResult.length === 0) {
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '수정할 데이터가 없습니다.',
|
||||
data: { affected_rows: 0 }
|
||||
});
|
||||
}
|
||||
|
||||
// 수정 전 샘플 로깅
|
||||
console.log('수정 전 샘플:', checkResult.slice(0, 5));
|
||||
|
||||
// 2. 업데이트 실행
|
||||
const [updateResult] = await db.query(`
|
||||
UPDATE daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
SET dwr.work_type_id = ta.task_id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
AND ta.task_id IS NOT NULL
|
||||
AND dwr.work_type_id != ta.task_id
|
||||
`);
|
||||
|
||||
console.log(`✅ 업데이트 완료: ${updateResult.affectedRows}개 레코드 수정됨`);
|
||||
|
||||
// 3. 수정 후 확인
|
||||
const [samples] = await db.query(`
|
||||
SELECT
|
||||
dwr.id,
|
||||
dwr.work_type_id,
|
||||
t.task_name,
|
||||
wt.name as work_type_name,
|
||||
w.worker_name,
|
||||
dwr.report_date
|
||||
FROM daily_work_reports dwr
|
||||
INNER JOIN tbm_team_assignments ta ON dwr.tbm_assignment_id = ta.assignment_id
|
||||
LEFT JOIN tasks t ON dwr.work_type_id = t.task_id
|
||||
LEFT JOIN work_types wt ON t.work_type_id = wt.id
|
||||
LEFT JOIN workers w ON dwr.worker_id = w.worker_id
|
||||
WHERE dwr.tbm_assignment_id IS NOT NULL
|
||||
ORDER BY dwr.report_date DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${updateResult.affectedRows}개 레코드가 수정되었습니다.`,
|
||||
data: {
|
||||
affected_rows: updateResult.affectedRows,
|
||||
before_count: checkResult.length,
|
||||
samples: samples.map(s => ({
|
||||
id: s.id,
|
||||
worker: s.worker_name,
|
||||
date: s.report_date,
|
||||
task: s.task_name,
|
||||
work_type: s.work_type_name
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('마이그레이션 실패:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: '마이그레이션 실패: ' + error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -4,7 +4,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const MonthlyStatusController = require('../controllers/monthlyStatusController');
|
||||
const { verifyToken } = require('../middlewares/authMiddleware');
|
||||
const { verifyToken } = require('../middlewares/auth');
|
||||
|
||||
// 모든 라우트에 인증 미들웨어 적용 (임시로 주석 처리 - 테스트용)
|
||||
// router.use(verifyToken);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const notificationRecipientController = require('../controllers/notificationRecipientController');
|
||||
const { verifyToken, requireMinLevel } = require('../middlewares/authMiddleware');
|
||||
const { verifyToken, requireMinLevel } = require('../middlewares/auth');
|
||||
|
||||
// 모든 라우트에 인증 필요
|
||||
router.use(verifyToken);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const TbmController = require('../controllers/tbmController');
|
||||
const { requireAuth } = require('../middlewares/auth');
|
||||
const { requireAuth, requireRole } = require('../middlewares/auth');
|
||||
|
||||
// ==================== TBM 세션 관련 ====================
|
||||
|
||||
@@ -56,13 +56,13 @@ router.delete('/sessions/:sessionId/team/:workerId', requireAuth, TbmController.
|
||||
router.get('/safety-checks', requireAuth, TbmController.getAllSafetyChecks);
|
||||
|
||||
// 안전 체크 항목 생성 (관리자용)
|
||||
router.post('/safety-checks', requireAuth, TbmController.createSafetyCheck);
|
||||
router.post('/safety-checks', requireAuth, requireRole('admin', 'system'), TbmController.createSafetyCheck);
|
||||
|
||||
// 안전 체크 항목 수정 (관리자용)
|
||||
router.put('/safety-checks/:checkId', requireAuth, TbmController.updateSafetyCheck);
|
||||
router.put('/safety-checks/:checkId', requireAuth, requireRole('admin', 'system'), TbmController.updateSafetyCheck);
|
||||
|
||||
// 안전 체크 항목 삭제 (관리자용)
|
||||
router.delete('/safety-checks/:checkId', requireAuth, TbmController.deleteSafetyCheck);
|
||||
router.delete('/safety-checks/:checkId', requireAuth, requireRole('admin', 'system'), TbmController.deleteSafetyCheck);
|
||||
|
||||
// TBM 세션의 안전 체크 기록 조회
|
||||
router.get('/sessions/:sessionId/safety', requireAuth, TbmController.getSafetyRecords);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const userController = require('../controllers/userController');
|
||||
const { verifyToken } = require('../middlewares/authMiddleware');
|
||||
const { verifyToken } = require('../middlewares/auth');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const visitRequestController = require('../controllers/visitRequestController');
|
||||
const { verifyToken } = require('../middlewares/authMiddleware');
|
||||
const { verifyToken } = require('../middlewares/auth');
|
||||
|
||||
// 모든 라우트에 인증 미들웨어 적용
|
||||
router.use(verifyToken);
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
// routes/workAnalysis.js
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const workAnalysisController = require('../controllers/workAnalysisController');
|
||||
|
||||
// 🔒 분석 기능은 admin 또는 system 권한만 접근 가능
|
||||
const requireAnalysisAccess = (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: '인증이 필요합니다.' });
|
||||
}
|
||||
|
||||
const allowedLevels = ['admin', 'system'];
|
||||
if (!allowedLevels.includes(req.user.access_level)) {
|
||||
return res.status(403).json({
|
||||
error: '분석 기능 접근 권한이 없습니다. 관리자 권한이 필요합니다.',
|
||||
required: 'admin 또는 system',
|
||||
current: req.user.access_level
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`🔓 분석 기능 접근 허용: ${req.user.username} (${req.user.access_level})`);
|
||||
next();
|
||||
};
|
||||
|
||||
// 임시로 권한 체크 건너뛰기 (테스트용)
|
||||
const skipAuth = (req, res, next) => {
|
||||
console.log('⚠️ 임시로 권한 체크 건너뛰기');
|
||||
next();
|
||||
};
|
||||
|
||||
// 기본 통계 조회 - 임시로 권한 체크 비활성화
|
||||
router.get('/stats', skipAuth, workAnalysisController.getStats);
|
||||
|
||||
// 일별 작업시간 추이 - 임시로 권한 체크 비활성화
|
||||
router.get('/daily-trend', skipAuth, workAnalysisController.getDailyTrend);
|
||||
|
||||
// 작업자별 통계 - 임시로 권한 체크 비활성화
|
||||
router.get('/worker-stats', skipAuth, workAnalysisController.getWorkerStats);
|
||||
|
||||
// 프로젝트별 통계 - 임시로 권한 체크 비활성화
|
||||
router.get('/project-stats', skipAuth, workAnalysisController.getProjectStats);
|
||||
|
||||
// 작업유형별 통계 - 임시로 권한 체크 비활성화
|
||||
router.get('/worktype-stats', skipAuth, workAnalysisController.getWorkTypeStats);
|
||||
|
||||
// 최근 작업 현황 - 임시로 권한 체크 비활성화
|
||||
router.get('/recent-work', skipAuth, workAnalysisController.getRecentWork);
|
||||
|
||||
// 요일별 패턴 분석
|
||||
router.get('/weekday-pattern', requireAnalysisAccess, workAnalysisController.getWeekdayPattern);
|
||||
|
||||
// 에러 분석
|
||||
router.get('/error-analysis', requireAnalysisAccess, workAnalysisController.getErrorAnalysis);
|
||||
|
||||
// 월별 비교 분석
|
||||
router.get('/monthly-comparison', requireAnalysisAccess, workAnalysisController.getMonthlyComparison);
|
||||
|
||||
// 작업자별 전문분야 분석
|
||||
router.get('/worker-specialization', requireAnalysisAccess, workAnalysisController.getWorkerSpecialization);
|
||||
|
||||
// 대시보드용 종합 데이터 (한 번에 여러 데이터 조회)
|
||||
router.get('/dashboard', requireAnalysisAccess, workAnalysisController.getDashboardData);
|
||||
|
||||
// 헬스체크 - 인증 없이 접근 가능
|
||||
router.get('/health', (req, res) => {
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Work Analysis API is running',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user