fix: 보안 취약점 수정 및 XSS 방지 적용
## 백엔드 보안 수정 - 하드코딩된 비밀번호 및 JWT 시크릿 폴백 제거 - SQL Injection 방지를 위한 화이트리스트 검증 추가 - 인증 미적용 API 라우트에 requireAuth 미들웨어 적용 - CSRF 보호 미들웨어 구현 (csrf.js) - 파일 업로드 보안 유틸리티 추가 (fileUploadSecurity.js) - 비밀번호 정책 검증 유틸리티 추가 (passwordValidator.js) ## 프론트엔드 XSS 방지 - api-base.js에 전역 escapeHtml() 함수 추가 - 17개 주요 JS 파일에 escapeHtml 적용: - tbm.js, daily-patrol.js, daily-work-report.js - task-management.js, workplace-status.js - equipment-detail.js, equipment-management.js - issue-detail.js, issue-report.js - vacation-common.js, worker-management.js - safety-report-list.js, nonconformity-list.js - project-management.js, workplace-management.js ## 정리 - 백업 폴더 및 빈 파일 삭제 - SECURITY_GUIDE.md 문서 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,152 +10,133 @@ const formatDate = (dateStr) => {
|
||||
};
|
||||
|
||||
// 1. 작업자 생성
|
||||
const create = async (worker, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
worker_name,
|
||||
job_type = null,
|
||||
join_date = null,
|
||||
salary = null,
|
||||
annual_leave = null,
|
||||
status = 'active',
|
||||
employment_status = 'employed',
|
||||
department_id = null
|
||||
} = worker;
|
||||
const create = async (worker) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
worker_name,
|
||||
job_type = null,
|
||||
join_date = null,
|
||||
salary = null,
|
||||
annual_leave = null,
|
||||
status = 'active',
|
||||
employment_status = 'employed',
|
||||
department_id = null
|
||||
} = worker;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workers
|
||||
(worker_name, job_type, join_date, salary, annual_leave, status, employment_status, department_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[worker_name, job_type, formatDate(join_date), salary, annual_leave, status, employment_status, department_id]
|
||||
);
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO workers
|
||||
(worker_name, job_type, join_date, salary, annual_leave, status, employment_status, department_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[worker_name, job_type, formatDate(join_date), salary, annual_leave, status, employment_status, department_id]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
console.error('❌ create 함수 에러:', err);
|
||||
callback(err);
|
||||
}
|
||||
return result.insertId;
|
||||
};
|
||||
|
||||
// 2. 전체 조회
|
||||
const getAll = async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
w.*,
|
||||
CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active,
|
||||
u.user_id,
|
||||
d.department_name
|
||||
FROM workers w
|
||||
LEFT JOIN users u ON w.worker_id = u.worker_id
|
||||
LEFT JOIN departments d ON w.department_id = d.department_id
|
||||
ORDER BY w.worker_id DESC
|
||||
`);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getAll = async () => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
w.*,
|
||||
CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active,
|
||||
u.user_id,
|
||||
d.department_name
|
||||
FROM workers w
|
||||
LEFT JOIN users u ON w.worker_id = u.worker_id
|
||||
LEFT JOIN departments d ON w.department_id = d.department_id
|
||||
ORDER BY w.worker_id DESC
|
||||
`);
|
||||
return rows;
|
||||
};
|
||||
|
||||
// 3. 단일 조회
|
||||
const getById = async (worker_id, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
w.*,
|
||||
CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active,
|
||||
u.user_id,
|
||||
d.department_name
|
||||
FROM workers w
|
||||
LEFT JOIN users u ON w.worker_id = u.worker_id
|
||||
LEFT JOIN departments d ON w.department_id = d.department_id
|
||||
WHERE w.worker_id = ?
|
||||
`, [worker_id]);
|
||||
callback(null, rows[0]);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
const getById = async (worker_id) => {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(`
|
||||
SELECT
|
||||
w.*,
|
||||
CASE WHEN w.status = 'active' THEN 1 ELSE 0 END AS is_active,
|
||||
u.user_id,
|
||||
d.department_name
|
||||
FROM workers w
|
||||
LEFT JOIN users u ON w.worker_id = u.worker_id
|
||||
LEFT JOIN departments d ON w.department_id = d.department_id
|
||||
WHERE w.worker_id = ?
|
||||
`, [worker_id]);
|
||||
return rows[0];
|
||||
};
|
||||
|
||||
// 4. 작업자 수정
|
||||
const update = async (worker, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
worker_id,
|
||||
worker_name,
|
||||
job_type,
|
||||
status,
|
||||
join_date,
|
||||
salary,
|
||||
annual_leave,
|
||||
employment_status,
|
||||
department_id
|
||||
} = worker;
|
||||
const update = async (worker) => {
|
||||
const db = await getDb();
|
||||
const {
|
||||
worker_id,
|
||||
worker_name,
|
||||
job_type,
|
||||
status,
|
||||
join_date,
|
||||
salary,
|
||||
annual_leave,
|
||||
employment_status,
|
||||
department_id
|
||||
} = worker;
|
||||
|
||||
// 업데이트할 필드만 동적으로 구성
|
||||
const updates = [];
|
||||
const values = [];
|
||||
// 업데이트할 필드만 동적으로 구성
|
||||
const updates = [];
|
||||
const values = [];
|
||||
|
||||
if (worker_name !== undefined) {
|
||||
updates.push('worker_name = ?');
|
||||
values.push(worker_name);
|
||||
}
|
||||
if (job_type !== undefined) {
|
||||
updates.push('job_type = ?');
|
||||
values.push(job_type);
|
||||
}
|
||||
if (status !== undefined) {
|
||||
updates.push('status = ?');
|
||||
values.push(status);
|
||||
}
|
||||
if (join_date !== undefined) {
|
||||
updates.push('join_date = ?');
|
||||
values.push(formatDate(join_date));
|
||||
}
|
||||
if (salary !== undefined) {
|
||||
updates.push('salary = ?');
|
||||
values.push(salary);
|
||||
}
|
||||
if (annual_leave !== undefined) {
|
||||
updates.push('annual_leave = ?');
|
||||
values.push(annual_leave);
|
||||
}
|
||||
if (employment_status !== undefined) {
|
||||
updates.push('employment_status = ?');
|
||||
values.push(employment_status);
|
||||
}
|
||||
if (department_id !== undefined) {
|
||||
updates.push('department_id = ?');
|
||||
values.push(department_id);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
callback(new Error('업데이트할 필드가 없습니다'));
|
||||
return;
|
||||
}
|
||||
|
||||
values.push(worker_id); // WHERE 조건용
|
||||
|
||||
const query = `UPDATE workers SET ${updates.join(', ')} WHERE worker_id = ?`;
|
||||
|
||||
console.log('🔍 실행할 SQL:', query);
|
||||
console.log('🔍 SQL 파라미터:', values);
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
|
||||
callback(null, result.affectedRows);
|
||||
} catch (err) {
|
||||
console.error('❌ update 함수 에러:', err);
|
||||
callback(new Error(err.message || String(err)));
|
||||
if (worker_name !== undefined) {
|
||||
updates.push('worker_name = ?');
|
||||
values.push(worker_name);
|
||||
}
|
||||
if (job_type !== undefined) {
|
||||
updates.push('job_type = ?');
|
||||
values.push(job_type);
|
||||
}
|
||||
if (status !== undefined) {
|
||||
updates.push('status = ?');
|
||||
values.push(status);
|
||||
}
|
||||
if (join_date !== undefined) {
|
||||
updates.push('join_date = ?');
|
||||
values.push(formatDate(join_date));
|
||||
}
|
||||
if (salary !== undefined) {
|
||||
updates.push('salary = ?');
|
||||
values.push(salary);
|
||||
}
|
||||
if (annual_leave !== undefined) {
|
||||
updates.push('annual_leave = ?');
|
||||
values.push(annual_leave);
|
||||
}
|
||||
if (employment_status !== undefined) {
|
||||
updates.push('employment_status = ?');
|
||||
values.push(employment_status);
|
||||
}
|
||||
if (department_id !== undefined) {
|
||||
updates.push('department_id = ?');
|
||||
values.push(department_id);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
throw new Error('업데이트할 필드가 없습니다');
|
||||
}
|
||||
|
||||
values.push(worker_id); // WHERE 조건용
|
||||
|
||||
const query = `UPDATE workers SET ${updates.join(', ')} WHERE worker_id = ?`;
|
||||
|
||||
console.log('🔍 실행할 SQL:', query);
|
||||
console.log('🔍 SQL 파라미터:', values);
|
||||
|
||||
const [result] = await db.query(query, values);
|
||||
|
||||
return result.affectedRows;
|
||||
};
|
||||
|
||||
// 5. 삭제 (외래키 제약조건 처리)
|
||||
const remove = async (worker_id, callback) => {
|
||||
const remove = async (worker_id) => {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
|
||||
@@ -196,22 +177,21 @@ const remove = async (worker_id, callback) => {
|
||||
console.log(`✅ 작업자 삭제 완료: ${result.affectedRows}건`);
|
||||
|
||||
await conn.commit();
|
||||
callback(null, result.affectedRows);
|
||||
return result.affectedRows;
|
||||
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error(`❌ 작업자 삭제 오류 (worker_id: ${worker_id}):`, err);
|
||||
callback(new Error(`작업자 삭제 중 오류가 발생했습니다: ${err.message}`));
|
||||
throw new Error(`작업자 삭제 중 오류가 발생했습니다: ${err.message}`);
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ 모듈 내보내기 (정상 구조)
|
||||
module.exports = {
|
||||
create,
|
||||
getAll,
|
||||
getById,
|
||||
update,
|
||||
remove
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user