주요 변경사항:
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>
209 lines
5.3 KiB
JavaScript
209 lines
5.3 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();
|
|
|
|
// 임시 사용자 데이터
|
|
let users = [
|
|
{
|
|
user_id: 1,
|
|
username: 'admin',
|
|
password: '$2b$10$example',
|
|
name: '관리자',
|
|
access_level: 'admin',
|
|
worker_id: null,
|
|
created_at: new Date()
|
|
},
|
|
{
|
|
user_id: 2,
|
|
username: 'group_leader1',
|
|
password: '$2b$10$example',
|
|
name: '김그룹장',
|
|
access_level: 'group_leader',
|
|
worker_id: 1,
|
|
created_at: new Date()
|
|
}
|
|
];
|
|
|
|
/**
|
|
* 로그인
|
|
*/
|
|
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 = password === '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 || 'your-secret-key',
|
|
{ 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; |