## 백엔드 보안 수정 - 하드코딩된 비밀번호 및 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>
215 lines
5.8 KiB
JavaScript
215 lines
5.8 KiB
JavaScript
// 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; |