diff --git a/user-management/api/middleware/auth.js b/user-management/api/middleware/auth.js index 21d5669..4b7b512 100644 --- a/user-management/api/middleware/auth.js +++ b/user-management/api/middleware/auth.js @@ -55,4 +55,25 @@ function requireAdmin(req, res, next) { } } -module.exports = { extractToken, requireAuth, requireAdmin }; +/** + * 관리자 또는 특정 페이지 권한 보유자 미들웨어 팩토리 + */ +function requireAdminOrPermission(pageName) { + return async (req, res, next) => { + const token = extractToken(req); + if (!token) return res.status(401).json({ success: false, error: '인증이 필요합니다' }); + try { + const decoded = jwt.verify(token, JWT_SECRET); + req.user = decoded; + if (['admin', 'system'].includes((decoded.role || '').toLowerCase())) return next(); + const { checkAccess } = require('../models/permissionModel'); + const result = await checkAccess(decoded.user_id || decoded.id, pageName); + if (result.can_access) return next(); + return res.status(403).json({ success: false, error: '권한이 없습니다' }); + } catch { + return res.status(401).json({ success: false, error: '유효하지 않은 토큰입니다' }); + } + }; +} + +module.exports = { extractToken, requireAuth, requireAdmin, requireAdminOrPermission }; diff --git a/user-management/api/routes/partnerRoutes.js b/user-management/api/routes/partnerRoutes.js index 95b7921..d86d04b 100644 --- a/user-management/api/routes/partnerRoutes.js +++ b/user-management/api/routes/partnerRoutes.js @@ -1,7 +1,8 @@ const express = require('express'); const router = express.Router(); -const { requireAuth, requireAdmin } = require('../middleware/auth'); +const { requireAuth, requireAdmin, requireAdminOrPermission } = require('../middleware/auth'); const ctrl = require('../controllers/partnerController'); +const partnerPerm = requireAdminOrPermission('tkuser.partners'); router.use(requireAuth); @@ -9,13 +10,13 @@ router.get('/', ctrl.list); router.get('/:id/delete-info', requireAdmin, ctrl.getDeleteInfo); router.delete('/:id/permanent', requireAdmin, ctrl.permanentDelete); router.get('/:id', ctrl.getById); -router.post('/', requireAdmin, ctrl.create); -router.put('/:id', requireAdmin, ctrl.update); -router.delete('/:id', requireAdmin, ctrl.deactivate); +router.post('/', partnerPerm, ctrl.create); +router.put('/:id', partnerPerm, ctrl.update); +router.delete('/:id', partnerPerm, ctrl.deactivate); router.get('/:id/workers', ctrl.listWorkers); -router.post('/:id/workers', requireAdmin, ctrl.createWorker); -router.put('/workers/:id', requireAdmin, ctrl.updateWorker); -router.delete('/workers/:id', requireAdmin, ctrl.deactivateWorker); +router.post('/:id/workers', partnerPerm, ctrl.createWorker); +router.put('/workers/:id', partnerPerm, ctrl.updateWorker); +router.delete('/workers/:id', partnerPerm, ctrl.deactivateWorker); module.exports = router; diff --git a/user-management/web/static/js/tkuser-partners.js b/user-management/web/static/js/tkuser-partners.js index 7e9dfa9..0c8f5a3 100644 --- a/user-management/web/static/js/tkuser-partners.js +++ b/user-management/web/static/js/tkuser-partners.js @@ -1,4 +1,13 @@ /* ===== tkuser 협력업체 CRUD ===== */ +function hasPartnerPermission() { + if (!currentUser) return false; + if (['admin', 'system'].includes(currentUser.role)) return true; + return typeof currentUserAllowedTabs !== 'undefined' && currentUserAllowedTabs.has('partners'); +} +function isAdminOnly() { + return currentUser && ['admin', 'system'].includes(currentUser.role); +} + let partnersLoaded = false; let partnersList = []; let partnerWorkersList = []; @@ -8,7 +17,7 @@ let editingWorkerIdTkuser = null; async function loadPartnersTab() { if (partnersLoaded) return; partnersLoaded = true; - if (currentUser && ['admin', 'system'].includes(currentUser.role)) { + if (hasPartnerPermission()) { document.getElementById('btnAddPartnerTkuser')?.classList.remove('hidden'); } await loadPartnersList(); @@ -35,7 +44,8 @@ function renderPartnersListTkuser() { c.innerHTML = '
등록된 협력업체가 없습니다.
'; return; } - const isAdmin = currentUser && ['admin', 'system'].includes(currentUser.role); + const canManage = hasPartnerPermission(); + const isAdmin = isAdminOnly(); c.innerHTML = partnersList.map(p => { const types = tryParseJsonTkuser(p.business_type) || []; const typeStr = types.map(t => `${escHtml(t)}`).join(' '); @@ -53,10 +63,10 @@ function renderPartnersListTkuser() { ${typeStr} - ${isAdmin ? `