fix(tksupport): 부서 페이지 권한 동작 수정 — requireAdmin/requireSupportTeam 제거, 네비게이션 권한 기반 렌더링
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { requireAuth, requireSupportTeam, requirePage } = require('../middleware/auth');
|
const { requireAuth, requirePage } = require('../middleware/auth');
|
||||||
const ctrl = require('../controllers/vacationDashboardController');
|
const ctrl = require('../controllers/vacationDashboardController');
|
||||||
|
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
|
|
||||||
router.get('/', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getDashboard);
|
router.get('/', requirePage('support_vacation_dashboard'), ctrl.getDashboard);
|
||||||
router.get('/yearly-overview', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getYearlyOverview);
|
router.get('/yearly-overview', requirePage('support_vacation_dashboard'), ctrl.getYearlyOverview);
|
||||||
router.get('/monthly-detail', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getMonthlyDetail);
|
router.get('/monthly-detail', requirePage('support_vacation_dashboard'), ctrl.getMonthlyDetail);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { requireAuth, requireAdmin, requirePage } = require('../middleware/auth');
|
const { requireAuth, requirePage } = require('../middleware/auth');
|
||||||
const ctrl = require('../controllers/vacationController');
|
const ctrl = require('../controllers/vacationController');
|
||||||
|
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
@@ -15,25 +15,25 @@ router.get('/requests/:id', ctrl.getRequestById);
|
|||||||
router.put('/requests/:id', requirePage('support_vacation_request'), ctrl.updateRequest);
|
router.put('/requests/:id', requirePage('support_vacation_request'), ctrl.updateRequest);
|
||||||
router.patch('/requests/:id/cancel', requirePage('support_vacation_request'), ctrl.cancelRequest);
|
router.patch('/requests/:id/cancel', requirePage('support_vacation_request'), ctrl.cancelRequest);
|
||||||
|
|
||||||
// 승인 (관리자)
|
// 승인
|
||||||
router.get('/pending', requireAdmin, ctrl.getPending);
|
router.get('/pending', requirePage('support_vacation_approval'), ctrl.getPending);
|
||||||
router.patch('/requests/:id/approve', requireAdmin, requirePage('support_vacation_approval'), ctrl.approveRequest);
|
router.patch('/requests/:id/approve', requirePage('support_vacation_approval'), ctrl.approveRequest);
|
||||||
router.patch('/requests/:id/reject', requireAdmin, requirePage('support_vacation_approval'), ctrl.rejectRequest);
|
router.patch('/requests/:id/reject', requirePage('support_vacation_approval'), ctrl.rejectRequest);
|
||||||
|
|
||||||
// 내 휴가 현황
|
// 내 휴가 현황
|
||||||
router.get('/my-status', ctrl.getMyStatus);
|
router.get('/my-status', ctrl.getMyStatus);
|
||||||
|
|
||||||
// 잔여일
|
// 잔여일
|
||||||
router.get('/balance', ctrl.getMyBalance);
|
router.get('/balance', ctrl.getMyBalance);
|
||||||
router.get('/balance/all', requireAdmin, ctrl.getAllBalances);
|
router.get('/balance/all', requirePage('support_vacation_approval'), ctrl.getAllBalances);
|
||||||
router.get('/balance/:userId', requireAdmin, ctrl.getUserBalance);
|
router.get('/balance/:userId', requirePage('support_vacation_approval'), ctrl.getUserBalance);
|
||||||
router.post('/balance/allocate', requireAdmin, ctrl.allocateBalance);
|
router.post('/balance/allocate', requirePage('support_vacation_approval'), ctrl.allocateBalance);
|
||||||
|
|
||||||
// 관리자 보정
|
// 관리자 보정
|
||||||
router.post('/admin/correct', requireAdmin, ctrl.adminCreateRequest);
|
router.post('/admin/correct', requirePage('support_vacation_admin'), ctrl.adminCreateRequest);
|
||||||
router.delete('/admin/requests/:id', requireAdmin, ctrl.adminDeleteRequest);
|
router.delete('/admin/requests/:id', requirePage('support_vacation_admin'), ctrl.adminDeleteRequest);
|
||||||
|
|
||||||
// 사용자 목록 (관리자)
|
// 사용자 목록
|
||||||
router.get('/users', requireAdmin, ctrl.getUsers);
|
router.get('/users', requirePage('support_vacation_approval'), ctrl.getUsers);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -112,10 +112,7 @@
|
|||||||
<script>
|
<script>
|
||||||
async function initPage() {
|
async function initPage() {
|
||||||
if (!initAuth()) return;
|
if (!initAuth()) return;
|
||||||
if (!currentUser || !['support_team','admin','system'].includes(currentUser.role)) {
|
// 권한은 API requirePage에서 체크
|
||||||
document.querySelector('.flex-1').innerHTML = '<div class="bg-white rounded-xl shadow-sm p-8 text-center text-gray-500"><i class="fas fa-lock text-4xl mb-3 block"></i>지원팀 이상 권한이 필요합니다</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 연도 셀렉트
|
// 연도 셀렉트
|
||||||
const sel = document.getElementById('yearSelect');
|
const sel = document.getElementById('yearSelect');
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ function toggleMobileMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Navbar ===== */
|
/* ===== Navbar ===== */
|
||||||
|
let currentUserPermissions = {};
|
||||||
|
|
||||||
function renderNavbar() {
|
function renderNavbar() {
|
||||||
const currentPage = location.pathname.replace(/\//g, '') || 'index.html';
|
const currentPage = location.pathname.replace(/\//g, '') || 'index.html';
|
||||||
const isAdmin = currentUser && ['admin','system'].includes(currentUser.role);
|
const isAdmin = currentUser && ['admin','system'].includes(currentUser.role);
|
||||||
@@ -105,16 +107,18 @@ function renderNavbar() {
|
|||||||
{ href: '/', icon: 'fa-home', label: '대시보드', match: ['index.html'] },
|
{ href: '/', icon: 'fa-home', label: '대시보드', match: ['index.html'] },
|
||||||
{ href: '/vacation-request.html', icon: 'fa-paper-plane', label: '휴가 신청', match: ['vacation-request.html'] },
|
{ href: '/vacation-request.html', icon: 'fa-paper-plane', label: '휴가 신청', match: ['vacation-request.html'] },
|
||||||
{ href: '/vacation-status.html', icon: 'fa-calendar-check', label: '내 휴가 현황', match: ['vacation-status.html'] },
|
{ href: '/vacation-status.html', icon: 'fa-calendar-check', label: '내 휴가 현황', match: ['vacation-status.html'] },
|
||||||
{ href: '/vacation-approval.html', icon: 'fa-clipboard-check', label: '휴가 승인', match: ['vacation-approval.html'], admin: true },
|
{ href: '/vacation-approval.html', icon: 'fa-clipboard-check', label: '휴가 승인', match: ['vacation-approval.html'], page: 'support_vacation_approval' },
|
||||||
{ href: '/company-holidays.html', icon: 'fa-calendar-day', label: '전사 휴가 관리', match: ['company-holidays.html'], roles: ['support_team','admin','system'] },
|
{ href: '/company-holidays.html', icon: 'fa-calendar-day', label: '전사 휴가 관리', match: ['company-holidays.html'], page: 'support_company_holidays' },
|
||||||
{ href: '/vacation-dashboard.html', icon: 'fa-chart-bar', label: '전체 휴가관리', match: ['vacation-dashboard.html'], roles: ['support_team','admin','system'] },
|
{ href: '/vacation-dashboard.html', icon: 'fa-chart-bar', label: '전체 휴가관리', match: ['vacation-dashboard.html'], page: 'support_vacation_dashboard' },
|
||||||
{ href: '/vacation-admin.html', icon: 'fa-user-edit', label: '휴가 보정', match: ['vacation-admin.html'], admin: true },
|
{ href: '/vacation-admin.html', icon: 'fa-user-edit', label: '휴가 보정', match: ['vacation-admin.html'], page: 'support_vacation_admin' },
|
||||||
];
|
];
|
||||||
const nav = document.getElementById('sideNav');
|
const nav = document.getElementById('sideNav');
|
||||||
if (!nav) return;
|
if (!nav) return;
|
||||||
nav.innerHTML = links.filter(l => {
|
nav.innerHTML = links.filter(l => {
|
||||||
if (l.roles) return currentUser && l.roles.includes(currentUser.role);
|
if (l.page) {
|
||||||
if (l.admin) return isAdmin;
|
if (isAdmin) return true;
|
||||||
|
return currentUserPermissions[l.page]?.can_access === true;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}).map(l => {
|
}).map(l => {
|
||||||
const active = l.match.some(m => currentPage === m || currentPage.endsWith(m));
|
const active = l.match.some(m => currentPage === m || currentPage.endsWith(m));
|
||||||
@@ -159,6 +163,12 @@ function initAuth() {
|
|||||||
const avatarEl = document.getElementById('headerUserAvatar');
|
const avatarEl = document.getElementById('headerUserAvatar');
|
||||||
if (nameEl) nameEl.textContent = dn;
|
if (nameEl) nameEl.textContent = dn;
|
||||||
if (avatarEl) avatarEl.textContent = dn.charAt(0).toUpperCase();
|
if (avatarEl) avatarEl.textContent = dn.charAt(0).toUpperCase();
|
||||||
|
|
||||||
|
// 권한 로드 후 네비게이션 렌더링
|
||||||
|
const isAdmin = ['admin','system'].includes(currentUser.role);
|
||||||
|
if (!isAdmin) {
|
||||||
|
_loadPermissions(currentUser.id).then(() => renderNavbar());
|
||||||
|
}
|
||||||
renderNavbar();
|
renderNavbar();
|
||||||
|
|
||||||
// 알림 벨 로드
|
// 알림 벨 로드
|
||||||
@@ -168,6 +178,23 @@ function initAuth() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 권한 로드 (tkuser API) ===== */
|
||||||
|
async function _loadPermissions(userId) {
|
||||||
|
try {
|
||||||
|
const tkuserBase = location.hostname.includes('technicalkorea.net')
|
||||||
|
? 'https://tkuser.technicalkorea.net/api'
|
||||||
|
: location.protocol + '//' + location.hostname + ':30300/api';
|
||||||
|
const token = getToken();
|
||||||
|
const res = await fetch(`${tkuserBase}/permissions/users/${userId}/effective-permissions`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
currentUserPermissions = data.permissions || {};
|
||||||
|
}
|
||||||
|
} catch (e) { /* 실패 시 빈 객체 유지 — 권한 메뉴 안 보임 */ }
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== 알림 벨 ===== */
|
/* ===== 알림 벨 ===== */
|
||||||
function _loadNotificationBell() {
|
function _loadNotificationBell() {
|
||||||
const s = document.createElement('script');
|
const s = document.createElement('script');
|
||||||
|
|||||||
@@ -133,10 +133,7 @@
|
|||||||
|
|
||||||
async function initPage() {
|
async function initPage() {
|
||||||
if (!initAuth()) return;
|
if (!initAuth()) return;
|
||||||
if (!currentUser || !['admin','system'].includes(currentUser.role)) {
|
// 권한은 API requirePage에서 체크
|
||||||
document.querySelector('.flex-1').innerHTML = '<div class="bg-white rounded-xl shadow-sm p-8 text-center text-gray-500"><i class="fas fa-lock text-4xl mb-3 block"></i>관리자 권한이 필요합니다</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const thisYear = new Date().getFullYear();
|
const thisYear = new Date().getFullYear();
|
||||||
const ySel = document.getElementById('yearSelect');
|
const ySel = document.getElementById('yearSelect');
|
||||||
for (let y = thisYear + 1; y >= thisYear - 2; y--) ySel.innerHTML += `<option value="${y}" ${y === thisYear ? 'selected' : ''}>${y}년</option>`;
|
for (let y = thisYear + 1; y >= thisYear - 2; y--) ySel.innerHTML += `<option value="${y}" ${y === thisYear ? 'selected' : ''}>${y}년</option>`;
|
||||||
|
|||||||
@@ -210,10 +210,7 @@
|
|||||||
|
|
||||||
async function initApprovalPage() {
|
async function initApprovalPage() {
|
||||||
if (!initAuth()) return;
|
if (!initAuth()) return;
|
||||||
if (!currentUser || !['admin','system'].includes(currentUser.role)) {
|
// 권한은 API requirePage에서 체크
|
||||||
document.querySelector('.flex-1').innerHTML = '<div class="bg-white rounded-xl shadow-sm p-8 text-center text-gray-500"><i class="fas fa-lock text-4xl mb-3 block"></i>관리자 권한이 필요합니다</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('allocYear').value = new Date().getFullYear();
|
document.getElementById('allocYear').value = new Date().getFullYear();
|
||||||
await loadDropdowns();
|
await loadDropdowns();
|
||||||
|
|||||||
@@ -163,10 +163,7 @@
|
|||||||
|
|
||||||
async function initPage() {
|
async function initPage() {
|
||||||
if (!initAuth()) return;
|
if (!initAuth()) return;
|
||||||
if (!currentUser || !['support_team','admin','system'].includes(currentUser.role)) {
|
// 권한은 API requirePage에서 체크 — 403 시 loadAll에서 에러 표시
|
||||||
document.querySelector('.flex-1').innerHTML = '<div class="bg-white rounded-xl shadow-sm p-8 text-center text-gray-500"><i class="fas fa-lock text-4xl mb-3 block"></i>지원팀 이상 권한이 필요합니다</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const thisYear = new Date().getFullYear();
|
const thisYear = new Date().getFullYear();
|
||||||
[document.getElementById('yearSelect'), document.getElementById('v2YearSelect')].forEach(sel => {
|
[document.getElementById('yearSelect'), document.getElementById('v2YearSelect')].forEach(sel => {
|
||||||
for (let y = thisYear + 1; y >= thisYear - 2; y--)
|
for (let y = thisYear + 1; y >= thisYear - 2; y--)
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ const DEFAULT_PAGES = {
|
|||||||
'support_vacation_approval': { title: '휴가 승인', system: 'tksupport', group: '관리', default_access: false },
|
'support_vacation_approval': { title: '휴가 승인', system: 'tksupport', group: '관리', default_access: false },
|
||||||
'support_company_holidays': { title: '전사 휴가 관리', system: 'tksupport', group: '관리', default_access: false },
|
'support_company_holidays': { title: '전사 휴가 관리', system: 'tksupport', group: '관리', default_access: false },
|
||||||
'support_vacation_dashboard': { title: '전체 휴가관리', system: 'tksupport', group: '관리', default_access: false },
|
'support_vacation_dashboard': { title: '전체 휴가관리', system: 'tksupport', group: '관리', default_access: false },
|
||||||
|
'support_vacation_admin': { title: '휴가 보정', system: 'tksupport', group: '관리', default_access: false },
|
||||||
|
|
||||||
// ===== tkuser - 통합 관리 =====
|
// ===== tkuser - 통합 관리 =====
|
||||||
'tkuser.users': { title: '사용자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
'tkuser.users': { title: '사용자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||||
|
|||||||
Reference in New Issue
Block a user