Files
TK-FB-Project/api.hyungi.net/routes/auth.js
Hyungi Ahn 36f110c90a 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>
2026-02-05 06:33:10 +09:00

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;