From 349ab6056181447f20fe17582183873caf0e187e Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Thu, 11 Dec 2025 12:12:44 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20Phase=203.3=20-=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=EC=9D=B8=EC=A6=9D/=EC=9D=B8=EA=B0=80=20=EB=AF=B8?= =?UTF-8?q?=EB=93=A4=EC=9B=A8=EC=96=B4=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20?= =?UTF-8?q?=EA=B5=AC=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 주요 변경사항: 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 --- .../middlewares/accessMiddleware.js | 52 ++- api.hyungi.net/middlewares/auth.js | 359 +++++++++++++++++- api.hyungi.net/middlewares/authMiddleware.js | 115 ++---- api.hyungi.net/routes/auth.js | 11 +- api.hyungi.net/routes/systemRoutes.js | 17 +- .../routes/workReportAnalysisRoutes.js | 6 +- api.hyungi.net/utils/access.js | 40 +- 7 files changed, 449 insertions(+), 151 deletions(-) diff --git a/api.hyungi.net/middlewares/accessMiddleware.js b/api.hyungi.net/middlewares/accessMiddleware.js index a7746e2..d774915 100644 --- a/api.hyungi.net/middlewares/accessMiddleware.js +++ b/api.hyungi.net/middlewares/accessMiddleware.js @@ -1,33 +1,29 @@ -// middlewares/accessMiddleware.js +/** + * @deprecated 이 파일은 하위 호환성을 위해 유지됩니다. + * 새로운 코드에서는 '../middlewares/auth'의 requireMinLevel을 사용하세요. + * + * @example + * // 이전 방식 (deprecated) + * const { requireAccess, ACCESS_LEVELS } = require('../middlewares/accessMiddleware'); + * router.get('/admin', requireAccess('admin'), handler); + * + * // 새로운 방식 (권장) + * const { requireMinLevel, ACCESS_LEVELS } = require('../middlewares/auth'); + * router.get('/admin', requireAuth, requireMinLevel('admin'), handler); + */ -// 권한 레벨 정의 -const ACCESS_LEVELS = { - worker: 1, - group_leader: 2, - support_team: 3, - admin: 4, - system: 5 -}; +const { requireMinLevel, ACCESS_LEVELS } = require('./auth'); +/** + * @deprecated requireMinLevel을 사용하세요 + */ const requireAccess = (requiredLevel) => { - return (req, res, next) => { - if (!req.user) { - return res.status(401).json({ error: '인증이 필요합니다.' }); - } - - const userLevel = ACCESS_LEVELS[req.user.access_level] || 0; - const required = ACCESS_LEVELS[requiredLevel] || 999; - - if (userLevel < required) { - return res.status(403).json({ - error: '접근 권한이 없습니다.', - required: requiredLevel, - current: req.user.access_level - }); - } - - next(); - }; + return requireMinLevel(requiredLevel); }; -module.exports = { requireAccess, ACCESS_LEVELS }; \ No newline at end of file +module.exports = { + requireAccess, + ACCESS_LEVELS, + // 새로운 API + requireMinLevel +}; diff --git a/api.hyungi.net/middlewares/auth.js b/api.hyungi.net/middlewares/auth.js index 6ec45cf..12017f5 100644 --- a/api.hyungi.net/middlewares/auth.js +++ b/api.hyungi.net/middlewares/auth.js @@ -1,20 +1,355 @@ -// 📁 middlewares/auth.js +/** + * 통합 인증/인가 미들웨어 + * + * JWT 토큰 검증 및 권한 체크를 위한 미들웨어 모음 + * + * @author TK-FB-Project + * @since 2025-12-11 + */ + const jwt = require('jsonwebtoken'); -require('dotenv').config(); +const { AuthenticationError, ForbiddenError } = require('../utils/errors'); +const logger = require('../utils/logger'); -module.exports = (req, res, next) => { - const authHeader = req.headers['authorization']; - const token = authHeader?.split(' ')[1]; - - if (!token) { - return res.status(401).json({ message: 'Access token required' }); - } +/** + * 권한 레벨 계층 구조 + * 숫자가 높을수록 상위 권한 + */ +const ACCESS_LEVELS = { + worker: 1, + group_leader: 2, + support_team: 3, + admin: 4, + system: 5 +}; +/** + * JWT 토큰 검증 미들웨어 + * + * Authorization 헤더에서 Bearer 토큰을 추출하고 검증합니다. + * 검증 성공 시 req.user에 디코딩된 사용자 정보를 저장합니다. + * + * @throws {AuthenticationError} 토큰이 없거나 유효하지 않을 때 + * + * @example + * router.get('/profile', requireAuth, getProfile); + */ +const requireAuth = (req, res, next) => { try { + const authHeader = req.headers['authorization']; + + if (!authHeader) { + logger.warn('인증 실패: Authorization 헤더 없음', { + path: req.path, + method: req.method, + ip: req.ip + }); + throw new AuthenticationError('Authorization 헤더가 필요합니다'); + } + + const token = authHeader.split(' ')[1]; + + if (!token) { + logger.warn('인증 실패: Bearer 토큰 누락', { + path: req.path, + method: req.method, + ip: req.ip + }); + throw new AuthenticationError('Bearer 토큰이 필요합니다'); + } + + // JWT 검증 const decoded = jwt.verify(token, process.env.JWT_SECRET); - req.user = decoded; // 다른 미들웨어에서 사용할 수 있게 설정 + req.user = decoded; + + logger.debug('인증 성공', { + user_id: decoded.user_id || decoded.id, + username: decoded.username, + role: decoded.role, + access_level: decoded.access_level + }); + next(); } catch (err) { - return res.status(403).json({ message: 'Invalid token' }); + if (err.name === 'JsonWebTokenError') { + logger.warn('인증 실패: 유효하지 않은 토큰', { + error: err.message, + path: req.path, + ip: req.ip + }); + throw new AuthenticationError('유효하지 않은 토큰입니다'); + } else if (err.name === 'TokenExpiredError') { + logger.warn('인증 실패: 만료된 토큰', { + error: err.message, + path: req.path, + ip: req.ip + }); + throw new AuthenticationError('토큰이 만료되었습니다'); + } else if (err instanceof AuthenticationError) { + // 이미 AuthenticationError인 경우 그대로 throw + throw err; + } else { + logger.error('인증 처리 중 예상치 못한 오류', { + error: err.message, + stack: err.stack + }); + throw new AuthenticationError('인증 처리 중 오류가 발생했습니다'); + } } -}; \ No newline at end of file +}; + +/** + * 특정 역할(들) 권한 체크 미들웨어 + * + * 사용자가 지정된 역할 중 하나를 가지고 있는지 확인합니다. + * requireAuth 미들웨어가 먼저 실행되어야 합니다. + * + * @param {...string} roles - 허용할 역할 목록 + * @returns {Function} Express 미들웨어 함수 + * @throws {AuthenticationError} 인증되지 않은 경우 + * @throws {ForbiddenError} 권한이 없는 경우 + * + * @example + * // 단일 역할 + * router.post('/admin/users', requireAuth, requireRole('admin'), createUser); + * + * // 여러 역할 + * router.get('/reports', requireAuth, requireRole('admin', 'support_team'), getReports); + */ +const requireRole = (...roles) => { + return (req, res, next) => { + try { + if (!req.user) { + logger.warn('권한 체크 실패: 인증되지 않은 요청', { + path: req.path, + method: req.method, + ip: req.ip + }); + throw new AuthenticationError('인증이 필요합니다'); + } + + const userRole = req.user.role; + + if (!roles.includes(userRole)) { + logger.warn('권한 체크 실패: 역할 불일치', { + user_id: req.user.user_id || req.user.id, + username: req.user.username, + current_role: userRole, + required_roles: roles, + path: req.path + }); + throw new ForbiddenError( + `이 기능을 사용하려면 ${roles.join(' 또는 ')} 권한이 필요합니다` + ); + } + + logger.debug('역할 권한 확인 성공', { + user_id: req.user.user_id || req.user.id, + username: req.user.username, + role: userRole, + required_roles: roles + }); + + next(); + } catch (err) { + next(err); + } + }; +}; + +/** + * 최소 권한 레벨 체크 미들웨어 (계층적) + * + * 사용자가 요구되는 최소 권한 레벨 이상인지 확인합니다. + * worker(1) < group_leader(2) < support_team(3) < admin(4) < system(5) + * requireAuth 미들웨어가 먼저 실행되어야 합니다. + * + * @param {string} minLevel - 최소 권한 레벨 (worker, group_leader, support_team, admin, system) + * @returns {Function} Express 미들웨어 함수 + * @throws {AuthenticationError} 인증되지 않은 경우 + * @throws {ForbiddenError} 권한이 부족한 경우 + * + * @example + * // admin 이상 필요 (admin, system만 허용) + * router.delete('/users/:id', requireAuth, requireMinLevel('admin'), deleteUser); + * + * // group_leader 이상 필요 (group_leader, support_team, admin, system 허용) + * router.get('/team-reports', requireAuth, requireMinLevel('group_leader'), getTeamReports); + */ +const requireMinLevel = (minLevel) => { + return (req, res, next) => { + try { + if (!req.user) { + logger.warn('권한 레벨 체크 실패: 인증되지 않은 요청', { + path: req.path, + method: req.method, + ip: req.ip + }); + throw new AuthenticationError('인증이 필요합니다'); + } + + const userLevel = ACCESS_LEVELS[req.user.access_level] || 0; + const requiredLevel = ACCESS_LEVELS[minLevel] || 999; + + if (userLevel < requiredLevel) { + logger.warn('권한 레벨 체크 실패: 권한 부족', { + user_id: req.user.user_id || req.user.id, + username: req.user.username, + current_level: req.user.access_level, + current_level_value: userLevel, + required_level: minLevel, + required_level_value: requiredLevel, + path: req.path + }); + throw new ForbiddenError( + `이 기능을 사용하려면 ${minLevel} 이상의 권한이 필요합니다 (현재: ${req.user.access_level})` + ); + } + + logger.debug('권한 레벨 확인 성공', { + user_id: req.user.user_id || req.user.id, + username: req.user.username, + access_level: req.user.access_level, + required_level: minLevel + }); + + next(); + } catch (err) { + next(err); + } + }; +}; + +/** + * 리소스 소유자 또는 관리자 권한 체크 미들웨어 + * + * 요청한 사용자가 리소스의 소유자이거나 관리자 권한이 있는지 확인합니다. + * requireAuth 미들웨어가 먼저 실행되어야 합니다. + * + * @param {Object} options - 옵션 객체 + * @param {string} options.resourceField - 리소스 ID를 가져올 req 필드 (예: 'params.user_id', 'body.worker_id') + * @param {string} options.userField - 사용자 ID 필드명 (기본값: 'user_id', 'id'도 자동 시도) + * @param {string[]} options.adminRoles - 관리자로 인정할 역할들 (기본값: ['admin', 'system']) + * @returns {Function} Express 미들웨어 함수 + * @throws {AuthenticationError} 인증되지 않은 경우 + * @throws {ForbiddenError} 소유자도 아니고 관리자도 아닌 경우 + * + * @example + * // URL 파라미터의 user_id로 체크 + * router.put('/users/:user_id', requireAuth, requireOwnerOrAdmin({ + * resourceField: 'params.user_id' + * }), updateUser); + * + * // 요청 body의 worker_id로 체크, support_team도 관리자로 인정 + * router.delete('/reports/:id', requireAuth, requireOwnerOrAdmin({ + * resourceField: 'body.worker_id', + * adminRoles: ['admin', 'system', 'support_team'] + * }), deleteReport); + */ +const requireOwnerOrAdmin = (options = {}) => { + const { + resourceField = 'params.id', + userField = 'user_id', + adminRoles = ['admin', 'system'] + } = options; + + return (req, res, next) => { + try { + if (!req.user) { + logger.warn('소유자/관리자 체크 실패: 인증되지 않은 요청', { + path: req.path, + method: req.method, + ip: req.ip + }); + throw new AuthenticationError('인증이 필요합니다'); + } + + // 관리자 권한 체크 + const userRole = req.user.role; + const isAdmin = adminRoles.includes(userRole); + + if (isAdmin) { + logger.debug('관리자 권한으로 접근 허용', { + user_id: req.user.user_id || req.user.id, + username: req.user.username, + role: userRole, + path: req.path + }); + return next(); + } + + // 리소스 ID 추출 + const fieldParts = resourceField.split('.'); + let resourceId = req; + for (const part of fieldParts) { + resourceId = resourceId[part]; + if (resourceId === undefined) break; + } + + // 사용자 ID (user_id 또는 id) + const userId = req.user[userField] || req.user.id || req.user.user_id; + + // 소유자 체크 + const isOwner = resourceId && String(resourceId) === String(userId); + + if (!isOwner) { + logger.warn('소유자/관리자 체크 실패: 권한 부족', { + user_id: userId, + username: req.user.username, + role: userRole, + resource_id: resourceId, + resource_field: resourceField, + is_admin: isAdmin, + is_owner: isOwner, + path: req.path + }); + throw new ForbiddenError('본인의 리소스이거나 관리자 권한이 필요합니다'); + } + + logger.debug('리소스 소유자로 접근 허용', { + user_id: userId, + username: req.user.username, + resource_id: resourceId, + path: req.path + }); + + next(); + } catch (err) { + next(err); + } + }; +}; + +/** + * 레거시 호환성을 위한 별칭 + * @deprecated requireAuth를 사용하세요 + */ +const verifyToken = requireAuth; + +/** + * 레거시 호환성을 위한 별칭 + * @deprecated requireRole('admin', 'system')을 사용하세요 + */ +const requireAdmin = requireRole('admin', 'system'); + +/** + * 레거시 호환성을 위한 별칭 + * @deprecated requireRole('system')을 사용하세요 + */ +const requireSystem = requireRole('system'); + +module.exports = { + // 주요 미들웨어 + requireAuth, + requireRole, + requireMinLevel, + requireOwnerOrAdmin, + + // 레거시 호환성 + verifyToken, + requireAdmin, + requireSystem, + + // 상수 + ACCESS_LEVELS +}; diff --git a/api.hyungi.net/middlewares/authMiddleware.js b/api.hyungi.net/middlewares/authMiddleware.js index 7ec9758..6fded28 100644 --- a/api.hyungi.net/middlewares/authMiddleware.js +++ b/api.hyungi.net/middlewares/authMiddleware.js @@ -1,89 +1,36 @@ -const jwt = require('jsonwebtoken'); - -exports.verifyToken = (req, res, next) => { - try { - const authHeader = req.headers['authorization']; - if (!authHeader) { - return res.status(401).json({ error: '토큰 없음' }); - } - - const token = authHeader.split(' ')[1]; - if (!token) { - return res.status(401).json({ error: '토큰 누락' }); - } - - const decoded = jwt.verify(token, process.env.JWT_SECRET); - req.user = decoded; - next(); // ✅ 반드시 next 호출 - } catch (err) { - console.error('[verifyToken 오류]', err.message); - return res.status(403).json({ error: '토큰 검증 실패', detail: err.message }); - } -}; - /** - * Admin 등급 이상 권한 체크 미들웨어 + * @deprecated 이 파일은 하위 호환성을 위해 유지됩니다. + * 새로운 코드에서는 './auth'를 직접 import하세요. + * + * @example + * // 이전 방식 (deprecated) + * const { verifyToken, requireAdmin } = require('../middlewares/authMiddleware'); + * + * // 새로운 방식 (권장) + * const { requireAuth, requireRole } = require('../middlewares/auth'); */ -exports.requireAdmin = (req, res, next) => { - try { - if (!req.user) { - return res.status(401).json({ - error: '인증 필요', - message: '먼저 로그인해주세요.' - }); - } - const userRole = req.user.role; - const adminRoles = ['admin', 'system']; - - if (!adminRoles.includes(userRole)) { - return res.status(403).json({ - error: '권한 부족', - message: '관리자 권한이 필요합니다.', - required: 'admin 또는 system', - current: userRole - }); - } +const { + requireAuth, + requireRole, + requireMinLevel, + requireOwnerOrAdmin, + verifyToken, + requireAdmin, + requireSystem, + ACCESS_LEVELS +} = require('./auth'); - console.log(`✅ Admin 권한 확인: ${req.user.username} (${userRole})`); - next(); - } catch (err) { - console.error('[requireAdmin 오류]', err.message); - return res.status(500).json({ - error: '권한 확인 중 오류 발생', - detail: err.message - }); - } +module.exports = { + // 레거시 별칭 (하위 호환성) + verifyToken, + requireAdmin, + requireSystem, + + // 새로운 API (권장) + requireAuth, + requireRole, + requireMinLevel, + requireOwnerOrAdmin, + ACCESS_LEVELS }; - -/** - * System 등급 권한 체크 미들웨어 - */ -exports.requireSystem = (req, res, next) => { - try { - if (!req.user) { - return res.status(401).json({ - error: '인증 필요', - message: '먼저 로그인해주세요.' - }); - } - - if (req.user.role !== 'system') { - return res.status(403).json({ - error: '권한 부족', - message: '시스템 관리자 권한이 필요합니다.', - required: 'system', - current: req.user.role - }); - } - - console.log(`✅ System 권한 확인: ${req.user.username}`); - next(); - } catch (err) { - console.error('[requireSystem 오류]', err.message); - return res.status(500).json({ - error: '권한 확인 중 오류 발생', - detail: err.message - }); - } -}; \ No newline at end of file diff --git a/api.hyungi.net/routes/auth.js b/api.hyungi.net/routes/auth.js index 1702788..8a1c24b 100644 --- a/api.hyungi.net/routes/auth.js +++ b/api.hyungi.net/routes/auth.js @@ -2,8 +2,7 @@ const express = require('express'); const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); -const { verifyToken } = require('../middlewares/authMiddleware'); -const { requireAccess } = require('../utils/access'); +const { requireAuth, requireRole } = require('../middlewares/auth'); const router = express.Router(); // 임시 사용자 데이터 @@ -83,7 +82,7 @@ router.post('/login', async (req, res) => { /** * 현재 사용자 정보 조회 */ -router.get('/me', verifyToken, (req, res) => { +router.get('/me', requireAuth, (req, res) => { try { const userId = req.user.user_id; const user = users.find(u => u.user_id === userId); @@ -109,7 +108,7 @@ router.get('/me', verifyToken, (req, res) => { /** * 사용자 등록 (관리자만) */ -router.post('/register', verifyToken, requireAccess('admin'), async (req, res) => { +router.post('/register', requireAuth, requireRole('admin', 'system'), async (req, res) => { try { const { username, password, name, access_level, worker_id } = req.body; @@ -159,7 +158,7 @@ router.post('/register', verifyToken, requireAccess('admin'), async (req, res) = /** * 사용자 목록 조회 (관리자만) */ -router.get('/users', verifyToken, requireAccess('admin'), (req, res) => { +router.get('/users', requireAuth, requireRole('admin', 'system'), (req, res) => { try { const userList = users.map(user => ({ user_id: user.user_id, @@ -180,7 +179,7 @@ router.get('/users', verifyToken, requireAccess('admin'), (req, res) => { /** * 사용자 삭제 (관리자만) */ -router.delete('/users/:id', verifyToken, requireAccess('admin'), (req, res) => { +router.delete('/users/:id', requireAuth, requireRole('admin', 'system'), (req, res) => { try { const userId = parseInt(req.params.id); diff --git a/api.hyungi.net/routes/systemRoutes.js b/api.hyungi.net/routes/systemRoutes.js index 8ba265f..32cd528 100644 --- a/api.hyungi.net/routes/systemRoutes.js +++ b/api.hyungi.net/routes/systemRoutes.js @@ -2,22 +2,11 @@ const express = require('express'); const router = express.Router(); const systemController = require('../controllers/systemController'); -const { verifyToken } = require('../middlewares/authMiddleware'); - -// 시스템 권한 확인 미들웨어 -const requireSystemAccess = (req, res, next) => { - if (!req.user || req.user.role !== 'system') { - return res.status(403).json({ - success: false, - error: '시스템 관리자 권한이 필요합니다.' - }); - } - next(); -}; +const { requireAuth, requireRole } = require('../middlewares/auth'); // 모든 라우트에 인증 및 시스템 권한 확인 적용 -router.use(verifyToken); -router.use(requireSystemAccess); +router.use(requireAuth); +router.use(requireRole('system')); // ===== 시스템 상태 관련 ===== diff --git a/api.hyungi.net/routes/workReportAnalysisRoutes.js b/api.hyungi.net/routes/workReportAnalysisRoutes.js index edd7d50..6e53a64 100644 --- a/api.hyungi.net/routes/workReportAnalysisRoutes.js +++ b/api.hyungi.net/routes/workReportAnalysisRoutes.js @@ -2,11 +2,11 @@ const express = require('express'); const router = express.Router(); const workReportAnalysisController = require('../controllers/workReportAnalysisController'); -const { verifyToken, requireAdmin } = require('../middlewares/authMiddleware'); +const { requireAuth, requireRole } = require('../middlewares/auth'); // 🔒 모든 분석 라우트에 인증 + Admin 권한 필요 -router.use(verifyToken); -router.use(requireAdmin); +router.use(requireAuth); +router.use(requireRole('admin', 'system')); // 📋 분석용 필터 데이터 조회 (프로젝트, 작업자, 작업유형 목록) router.get('/filters', workReportAnalysisController.getAnalysisFilters); diff --git a/api.hyungi.net/utils/access.js b/api.hyungi.net/utils/access.js index 999f765..8c6fe5f 100644 --- a/api.hyungi.net/utils/access.js +++ b/api.hyungi.net/utils/access.js @@ -1,7 +1,12 @@ -// utils/access.js - 유틸리티 함수만 남김 (미들웨어 제거) +/** + * @deprecated 이 파일의 미들웨어 함수들은 하위 호환성을 위해 유지됩니다. + * 새로운 코드에서는 '../middlewares/auth'를 사용하세요. + */ + +// utils/access.js - 유틸리티 함수와 레거시 호환성 const ACCESS_LEVELS = { worker: 1, - group_leader: 2, + group_leader: 2, support_team: 3, admin: 4, system: 5 @@ -127,6 +132,30 @@ const canPerformAction = (userLevel, table, action) => { return userLevel && userLevel !== 'anonymous'; }; +// ===== 레거시 호환성: 미들웨어 함수 ===== + +/** + * @deprecated 이 미들웨어는 하위 호환성을 위해 유지됩니다. + * 새로운 코드에서는 '../middlewares/auth'의 requireRole을 사용하세요. + * + * @example + * // 이전 방식 (deprecated) + * const { requireAccess } = require('../utils/access'); + * router.get('/admin', requireAccess('admin'), handler); + * + * // 새로운 방식 (권장) + * const { requireAuth, requireRole } = require('../middlewares/auth'); + * router.get('/admin', requireAuth, requireRole('admin'), handler); + */ +const requireAccess = (...allowed) => { + return (req, res, next) => { + if (!req.user || !allowed.includes(req.user.access_level)) { + return res.status(403).json({ error: '접근 권한이 없습니다' }); + } + next(); + }; +}; + module.exports = { // 기본 유틸리티 함수들 hasPermission, @@ -135,11 +164,14 @@ module.exports = { hasAnyPermission, getAllLevels, getLevelsAbove, - + // 프론트엔드용 함수들 canAccessPage, canPerformAction, - + + // 레거시 호환성 (deprecated) + requireAccess, + // 상수들 ACCESS_LEVELS, ACCESS_LEVEL_NAMES