fix: TKQC 인증 흐름 무한루프 방지 및 스크립트 로드 순서 정리

- api.js 401 핸들러: window.location.href 리다이렉트 제거, throw Error로 변경 (auth-manager가 처리)
- auth-manager.js refreshAuth(): throw error → return null (무한 리다이렉트 방지)
- auth-manager.js setupTokenExpiryCheck(): catch→logout 대신 then으로 변경 (이중 리다이렉트 방지)
- 모든 HTML: api.js를 auth-manager.js보다 먼저 로드하도록 순서 수정
- 누락 페이지(archive, issue-view)에 api.js + auth-manager.js 추가
- 전체 HTML 캐시 버스팅 버전 v=20260308로 통일

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-08 13:11:40 +09:00
parent 9647ae0d56
commit 81478dc6ac
15 changed files with 112 additions and 124 deletions

View File

@@ -272,14 +272,14 @@
</div>
<!-- 스크립트 -->
<script src="/static/js/core/permissions.js?v=20260306"></script>
<script src="/static/js/components/common-header.js?v=20260306"></script>
<script src="/static/js/core/page-manager.js?v=20260306"></script>
<script src="/static/js/core/auth-manager.js?v=20260306"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260306"></script>
<script src="/static/js/utils/toast.js?v=20260306"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260306"></script>
<script src="/static/js/api.js?v=20260306"></script>
<script src="/static/js/pages/ai-assistant.js?v=20260307"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/core/page-manager.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/utils/toast.js?v=20260308"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260308"></script>
<script src="/static/js/pages/ai-assistant.js?v=20260308"></script>
</body>
</html>

View File

@@ -106,14 +106,16 @@
</main>
<!-- Scripts -->
<script src="/static/js/date-utils.js?v=20260213"></script>
<script src="/static/js/core/permissions.js?v=20260213"></script>
<script src="/static/js/components/common-header.js?v=20260213"></script>
<script src="/static/js/core/page-manager.js?v=20260213"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260213"></script>
<script src="/static/js/utils/photo-modal.js?v=20260213"></script>
<script src="/static/js/utils/toast.js?v=20260213"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260213"></script>
<script src="/static/js/pages/issue-view.js?v=20260213"></script>
<script src="/static/js/date-utils.js?v=20260308"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/core/page-manager.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/utils/photo-modal.js?v=20260308"></script>
<script src="/static/js/utils/toast.js?v=20260308"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260308"></script>
<script src="/static/js/pages/issue-view.js?v=20260308"></script>
</body>
</html>

View File

@@ -196,14 +196,16 @@
</main>
<!-- Scripts -->
<script src="/static/js/date-utils.js?v=20260213"></script>
<script src="/static/js/core/permissions.js?v=20260213"></script>
<script src="/static/js/components/common-header.js?v=20260213"></script>
<script src="/static/js/core/page-manager.js?v=20260213"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260213"></script>
<script src="/static/js/utils/photo-modal.js?v=20260213"></script>
<script src="/static/js/utils/toast.js?v=20260213"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260213"></script>
<script src="/static/js/pages/issues-archive.js?v=20260213"></script>
<script src="/static/js/date-utils.js?v=20260308"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/core/page-manager.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/utils/photo-modal.js?v=20260308"></script>
<script src="/static/js/utils/toast.js?v=20260308"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260308"></script>
<script src="/static/js/pages/issues-archive.js?v=20260308"></script>
</body>
</html>

View File

@@ -550,15 +550,15 @@
</div>
<!-- 스크립트 -->
<script src="/static/js/core/permissions.js?v=20260306"></script>
<script src="/static/js/components/common-header.js?v=20260306"></script>
<script src="/static/js/core/page-manager.js?v=20260306"></script>
<script src="/static/js/core/auth-manager.js?v=20260306"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260306"></script>
<script src="/static/js/utils/photo-modal.js?v=20260306"></script>
<script src="/static/js/utils/toast.js?v=20260306"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260306"></script>
<script src="/static/js/api.js?v=20260306"></script>
<script src="/static/js/pages/issues-dashboard.js?v=20260306"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/core/page-manager.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/utils/photo-modal.js?v=20260308"></script>
<script src="/static/js/utils/toast.js?v=20260308"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260308"></script>
<script src="/static/js/pages/issues-dashboard.js?v=20260308"></script>
</body>
</html>

View File

@@ -368,16 +368,17 @@
</div>
<!-- Scripts -->
<script src="/static/js/date-utils.js?v=20260213"></script>
<script src="/static/js/core/permissions.js?v=20260213"></script>
<script src="/static/js/components/common-header.js?v=20260306"></script>
<script src="/static/js/core/page-manager.js?v=20260306"></script>
<script src="/static/js/components/mobile-calendar.js?v=20260306"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260306"></script>
<script src="/static/js/utils/photo-modal.js?v=20260306"></script>
<script src="/static/js/utils/toast.js?v=20260306"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260306"></script>
<script src="/static/js/api.js?v=20260306"></script>
<script src="/static/js/pages/issues-inbox.js?v=20260306"></script>
<script src="/static/js/date-utils.js?v=20260308"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/core/page-manager.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/components/mobile-calendar.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/utils/photo-modal.js?v=20260308"></script>
<script src="/static/js/utils/toast.js?v=20260308"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260308"></script>
<script src="/static/js/pages/issues-inbox.js?v=20260308"></script>
</body>
</html>

View File

@@ -336,15 +336,16 @@
</div>
<!-- Scripts -->
<script src="/static/js/date-utils.js?v=20260213"></script>
<script src="/static/js/core/permissions.js?v=20260306"></script>
<script src="/static/js/components/common-header.js?v=20260306"></script>
<script src="/static/js/core/page-manager.js?v=20260306"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260306"></script>
<script src="/static/js/utils/photo-modal.js?v=20260306"></script>
<script src="/static/js/utils/toast.js?v=20260306"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260306"></script>
<script src="/static/js/api.js?v=20260306"></script>
<script src="/static/js/pages/issues-management.js?v=20260306"></script>
<script src="/static/js/date-utils.js?v=20260308"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/core/page-manager.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/utils/photo-modal.js?v=20260308"></script>
<script src="/static/js/utils/toast.js?v=20260308"></script>
<script src="/static/js/components/mobile-bottom-nav.js?v=20260308"></script>
<script src="/static/js/pages/issues-management.js?v=20260308"></script>
</body>
</html>

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>부적합 현황판</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="/static/css/m-common.css?v=20260305">
<link rel="stylesheet" href="/static/css/m-common.css?v=20260308">
</head>
<body>
<!-- 로딩 -->
@@ -185,11 +185,11 @@
</div>
<!-- 스크립트 -->
<script src="/static/js/api.js?v=20260305"></script>
<script src="/static/js/core/auth-manager.js?v=20260305"></script>
<script src="/static/js/core/permissions.js?v=20260305"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260305"></script>
<script src="/static/js/m/m-common.js?v=20260305"></script>
<script src="/static/js/m/m-dashboard.js?v=20260305"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/m/m-common.js?v=20260308"></script>
<script src="/static/js/m/m-dashboard.js?v=20260308"></script>
</body>
</html>

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>수신함</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="/static/css/m-common.css?v=20260305">
<link rel="stylesheet" href="/static/css/m-common.css?v=20260308">
</head>
<body>
<!-- 로딩 -->
@@ -193,11 +193,11 @@
</div>
<!-- 스크립트 -->
<script src="/static/js/api.js?v=20260305"></script>
<script src="/static/js/core/auth-manager.js?v=20260305"></script>
<script src="/static/js/core/permissions.js?v=20260305"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260305"></script>
<script src="/static/js/m/m-common.js?v=20260305"></script>
<script src="/static/js/m/m-inbox.js?v=20260305"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/m/m-common.js?v=20260308"></script>
<script src="/static/js/m/m-inbox.js?v=20260308"></script>
</body>
</html>

View File

@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>관리함</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="/static/css/m-common.css?v=20260305">
<link rel="stylesheet" href="/static/css/m-common.css?v=20260308">
</head>
<body>
<!-- 로딩 -->
@@ -169,11 +169,11 @@
</div>
<!-- 스크립트 -->
<script src="/static/js/api.js?v=20260305"></script>
<script src="/static/js/core/auth-manager.js?v=20260305"></script>
<script src="/static/js/core/permissions.js?v=20260305"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260305"></script>
<script src="/static/js/m/m-common.js?v=20260305"></script>
<script src="/static/js/m/m-management.js?v=20260305"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/utils/issue-helpers.js?v=20260308"></script>
<script src="/static/js/m/m-common.js?v=20260308"></script>
<script src="/static/js/m/m-management.js?v=20260308"></script>
</body>
</html>

View File

@@ -182,10 +182,10 @@
</main>
<!-- JavaScript -->
<script src="/static/js/core/auth-manager.js"></script>
<script src="/static/js/core/permissions.js"></script>
<script src="/static/js/components/common-header.js"></script>
<script src="/static/js/api.js"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script>
let projects = [];

View File

@@ -69,10 +69,10 @@
</main>
<!-- JavaScript -->
<script src="/static/js/core/auth-manager.js"></script>
<script src="/static/js/core/permissions.js"></script>
<script src="/static/js/components/common-header.js"></script>
<script src="/static/js/api.js"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script>
// 페이지 초기화

View File

@@ -68,10 +68,10 @@
</main>
<!-- JavaScript -->
<script src="/static/js/core/auth-manager.js"></script>
<script src="/static/js/core/permissions.js"></script>
<script src="/static/js/components/common-header.js"></script>
<script src="/static/js/api.js"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script>
// 페이지 초기화

View File

@@ -170,10 +170,10 @@
<!-- JavaScript -->
<script src="/static/js/core/auth-manager.js"></script>
<script src="/static/js/core/permissions.js"></script>
<script src="/static/js/components/common-header.js"></script>
<script src="/static/js/api.js"></script>
<script src="/static/js/core/permissions.js?v=20260308"></script>
<script src="/static/js/components/common-header.js?v=20260308"></script>
<script src="/static/js/api.js?v=20260308"></script>
<script src="/static/js/core/auth-manager.js?v=20260308"></script>
<script>
// 페이지 초기화

View File

@@ -106,11 +106,10 @@ async function apiRequest(endpoint, options = {}) {
const response = await fetch(`${API_BASE_URL}${endpoint}`, config);
if (response.status === 401) {
// 인증 실패 시 중앙 로그인 페이지로
// 인증 실패 — 토큰만 정리하고 에러 throw (리다이렉트는 auth-manager가 처리)
TokenManager.removeToken();
TokenManager.removeUser();
window.location.href = _getLoginUrl();
return;
throw new Error('인증이 만료되었습니다.');
}
if (!response.ok) {
@@ -266,41 +265,21 @@ const ReportsAPI = {
}
};
// 권한 체크
// 권한 체크 — authManager.checkAuth()로 통일 권장
// 레거시 호환용으로 유지 (localStorage만 체크, API 호출 없음)
function checkAuth() {
const user = TokenManager.getUser();
if (!user) {
window.location.href = _getLoginUrl();
return null;
}
if (!user) return null;
return user;
}
function checkAdminAuth() {
const user = checkAuth();
if (user && user.role !== 'admin') {
alert('관리자 권한이 필요합니다.');
window.location.href = _getLoginUrl();
return null;
}
return user;
}
// 페이지 접근 권한 체크 함수
function checkPageAccess(pageName) {
const user = checkAuth();
if (!user) return null;
// admin은 모든 페이지 접근 가능
if (user.role === 'admin') return user;
// 페이지별 권한 체크는 pagePermissionManager에서 처리
if (window.pagePermissionManager && !window.pagePermissionManager.canAccessPage(pageName)) {
alert('이 페이지에 접근할 권한이 없습니다.');
window.location.href = _getLoginUrl();
return null;
}
return user;
}

View File

@@ -152,7 +152,7 @@ class AuthManager {
console.error('인증 실패:', error);
this.clearAuth();
this.notifyListeners('auth-failed', error);
throw error;
return null;
}
}
@@ -235,8 +235,11 @@ class AuthManager {
setupTokenExpiryCheck() {
setInterval(() => {
if (this.isAuthenticated) {
this.refreshAuth().catch(() => {
this.logout();
this.refreshAuth().then(user => {
if (!user) {
// 인증 실패 — clearAuth()는 refreshAuth 내부에서 이미 처리됨
this.notifyListeners('token-expired');
}
});
}
}, 30 * 60 * 1000);