fix(tksupport): 부서 페이지 권한 동작 수정 — requireAdmin/requireSupportTeam 제거, 네비게이션 권한 기반 렌더링

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-25 14:30:53 +09:00
parent a6724b2a20
commit 280efc46ed
8 changed files with 54 additions and 38 deletions

View File

@@ -1,12 +1,12 @@
const express = require('express');
const router = express.Router();
const { requireAuth, requireSupportTeam, requirePage } = require('../middleware/auth');
const { requireAuth, requirePage } = require('../middleware/auth');
const ctrl = require('../controllers/vacationDashboardController');
router.use(requireAuth);
router.get('/', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getDashboard);
router.get('/yearly-overview', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getYearlyOverview);
router.get('/monthly-detail', requireSupportTeam, requirePage('support_vacation_dashboard'), ctrl.getMonthlyDetail);
router.get('/', requirePage('support_vacation_dashboard'), ctrl.getDashboard);
router.get('/yearly-overview', requirePage('support_vacation_dashboard'), ctrl.getYearlyOverview);
router.get('/monthly-detail', requirePage('support_vacation_dashboard'), ctrl.getMonthlyDetail);
module.exports = router;

View File

@@ -1,6 +1,6 @@
const express = require('express');
const router = express.Router();
const { requireAuth, requireAdmin, requirePage } = require('../middleware/auth');
const { requireAuth, requirePage } = require('../middleware/auth');
const ctrl = require('../controllers/vacationController');
router.use(requireAuth);
@@ -15,25 +15,25 @@ router.get('/requests/:id', ctrl.getRequestById);
router.put('/requests/:id', requirePage('support_vacation_request'), ctrl.updateRequest);
router.patch('/requests/:id/cancel', requirePage('support_vacation_request'), ctrl.cancelRequest);
// 승인 (관리자)
router.get('/pending', requireAdmin, ctrl.getPending);
router.patch('/requests/:id/approve', requireAdmin, requirePage('support_vacation_approval'), ctrl.approveRequest);
router.patch('/requests/:id/reject', requireAdmin, requirePage('support_vacation_approval'), ctrl.rejectRequest);
// 승인
router.get('/pending', requirePage('support_vacation_approval'), ctrl.getPending);
router.patch('/requests/:id/approve', requirePage('support_vacation_approval'), ctrl.approveRequest);
router.patch('/requests/:id/reject', requirePage('support_vacation_approval'), ctrl.rejectRequest);
// 내 휴가 현황
router.get('/my-status', ctrl.getMyStatus);
// 잔여일
router.get('/balance', ctrl.getMyBalance);
router.get('/balance/all', requireAdmin, ctrl.getAllBalances);
router.get('/balance/:userId', requireAdmin, ctrl.getUserBalance);
router.post('/balance/allocate', requireAdmin, ctrl.allocateBalance);
router.get('/balance/all', requirePage('support_vacation_approval'), ctrl.getAllBalances);
router.get('/balance/:userId', requirePage('support_vacation_approval'), ctrl.getUserBalance);
router.post('/balance/allocate', requirePage('support_vacation_approval'), ctrl.allocateBalance);
// 관리자 보정
router.post('/admin/correct', requireAdmin, ctrl.adminCreateRequest);
router.delete('/admin/requests/:id', requireAdmin, ctrl.adminDeleteRequest);
router.post('/admin/correct', requirePage('support_vacation_admin'), ctrl.adminCreateRequest);
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;

View File

@@ -112,10 +112,7 @@
<script>
async function initPage() {
if (!initAuth()) return;
if (!currentUser || !['support_team','admin','system'].includes(currentUser.role)) {
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;
}
// 권한은 API requirePage에서 체크
// 연도 셀렉트
const sel = document.getElementById('yearSelect');

View File

@@ -98,6 +98,8 @@ function toggleMobileMenu() {
}
/* ===== Navbar ===== */
let currentUserPermissions = {};
function renderNavbar() {
const currentPage = location.pathname.replace(/\//g, '') || 'index.html';
const isAdmin = currentUser && ['admin','system'].includes(currentUser.role);
@@ -105,16 +107,18 @@ function renderNavbar() {
{ href: '/', icon: 'fa-home', label: '대시보드', match: ['index.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-approval.html', icon: 'fa-clipboard-check', label: '휴가 승인', match: ['vacation-approval.html'], admin: true },
{ href: '/company-holidays.html', icon: 'fa-calendar-day', label: '전사 휴가 관리', match: ['company-holidays.html'], roles: ['support_team','admin','system'] },
{ href: '/vacation-dashboard.html', icon: 'fa-chart-bar', label: '전체 휴가관리', match: ['vacation-dashboard.html'], roles: ['support_team','admin','system'] },
{ href: '/vacation-admin.html', icon: 'fa-user-edit', label: '휴가 보정', match: ['vacation-admin.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'], page: 'support_company_holidays' },
{ 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'], page: 'support_vacation_admin' },
];
const nav = document.getElementById('sideNav');
if (!nav) return;
nav.innerHTML = links.filter(l => {
if (l.roles) return currentUser && l.roles.includes(currentUser.role);
if (l.admin) return isAdmin;
if (l.page) {
if (isAdmin) return true;
return currentUserPermissions[l.page]?.can_access === true;
}
return true;
}).map(l => {
const active = l.match.some(m => currentPage === m || currentPage.endsWith(m));
@@ -159,6 +163,12 @@ function initAuth() {
const avatarEl = document.getElementById('headerUserAvatar');
if (nameEl) nameEl.textContent = dn;
if (avatarEl) avatarEl.textContent = dn.charAt(0).toUpperCase();
// 권한 로드 후 네비게이션 렌더링
const isAdmin = ['admin','system'].includes(currentUser.role);
if (!isAdmin) {
_loadPermissions(currentUser.id).then(() => renderNavbar());
}
renderNavbar();
// 알림 벨 로드
@@ -168,6 +178,23 @@ function initAuth() {
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() {
const s = document.createElement('script');

View File

@@ -133,10 +133,7 @@
async function initPage() {
if (!initAuth()) return;
if (!currentUser || !['admin','system'].includes(currentUser.role)) {
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;
}
// 권한은 API requirePage에서 체크
const thisYear = new Date().getFullYear();
const ySel = document.getElementById('yearSelect');
for (let y = thisYear + 1; y >= thisYear - 2; y--) ySel.innerHTML += `<option value="${y}" ${y === thisYear ? 'selected' : ''}>${y}년</option>`;

View File

@@ -210,10 +210,7 @@
async function initApprovalPage() {
if (!initAuth()) return;
if (!currentUser || !['admin','system'].includes(currentUser.role)) {
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;
}
// 권한은 API requirePage에서 체크
document.getElementById('allocYear').value = new Date().getFullYear();
await loadDropdowns();

View File

@@ -163,10 +163,7 @@
async function initPage() {
if (!initAuth()) return;
if (!currentUser || !['support_team','admin','system'].includes(currentUser.role)) {
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;
}
// 권한은 API requirePage에서 체크 — 403 시 loadAll에서 에러 표시
const thisYear = new Date().getFullYear();
[document.getElementById('yearSelect'), document.getElementById('v2YearSelect')].forEach(sel => {
for (let y = thisYear + 1; y >= thisYear - 2; y--)

View File

@@ -76,6 +76,7 @@ const DEFAULT_PAGES = {
'support_vacation_approval': { 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_admin': { title: '휴가 보정', system: 'tksupport', group: '관리', default_access: false },
// ===== tkuser - 통합 관리 =====
'tkuser.users': { title: '사용자 관리', system: 'tkuser', group: '통합 관리', default_access: false },