diff --git a/gateway/html/login.html b/gateway/html/login.html
index 20ede4e..4e639b6 100644
--- a/gateway/html/login.html
+++ b/gateway/html/login.html
@@ -205,6 +205,13 @@
var existingToken = ssoCookie.get('sso_token') || localStorage.getItem('sso_token');
if (existingToken && existingToken !== 'undefined' && existingToken !== 'null') {
if (isTokenValid(existingToken)) {
+ // 쿠키 재설정 (localStorage에만 있고 쿠키가 없는 경우 대비)
+ var existingUser = ssoCookie.get('sso_user') || localStorage.getItem('sso_user');
+ var existingRefresh = ssoCookie.get('sso_refresh_token') || localStorage.getItem('sso_refresh_token');
+ ssoCookie.set('sso_token', existingToken, 7);
+ if (existingUser) ssoCookie.set('sso_user', existingUser, 7);
+ if (existingRefresh) ssoCookie.set('sso_refresh_token', existingRefresh, 30);
+
var redirect = new URLSearchParams(location.search).get('redirect');
window.location.href = (redirect && isSafeRedirect(redirect)) ? redirect : '/';
} else {
diff --git a/gateway/nginx.conf b/gateway/nginx.conf
index dd797d5..531eb19 100644
--- a/gateway/nginx.conf
+++ b/gateway/nginx.conf
@@ -7,8 +7,10 @@ server {
# ===== Gateway 자체 페이지 (포털, 로그인) =====
root /usr/share/nginx/html;
- # 로그인 페이지
+ # 로그인 페이지 (캐시 금지 — SSO 쿠키 재설정 로직 항상 최신 반영)
location = /login {
+ add_header Cache-Control "no-store, no-cache, must-revalidate";
+ add_header Pragma "no-cache";
try_files /login.html =404;
}
diff --git a/system1-factory/api/controllers/dailyWorkReportController.js b/system1-factory/api/controllers/dailyWorkReportController.js
index 69f1a3c..014b861 100644
--- a/system1-factory/api/controllers/dailyWorkReportController.js
+++ b/system1-factory/api/controllers/dailyWorkReportController.js
@@ -135,7 +135,7 @@ const getDailyWorkReports = async (req, res) => {
try {
const userInfo = {
user_id: req.user?.user_id || req.user?.id,
- role: req.user?.role || 'user'
+ role: (req.user?.role || req.user?.access_level || 'user').toLowerCase()
};
if (!userInfo.user_id) {
@@ -303,7 +303,7 @@ const updateWorkReport = async (req, res) => {
const updateData = req.body;
const userInfo = {
user_id: req.user?.user_id || req.user?.id,
- role: req.user?.role || 'user'
+ role: (req.user?.role || req.user?.access_level || 'user').toLowerCase()
};
if (!userInfo.user_id) {
diff --git a/system1-factory/api/routes/authRoutes.js b/system1-factory/api/routes/authRoutes.js
index 5650f9f..b20e8e3 100644
--- a/system1-factory/api/routes/authRoutes.js
+++ b/system1-factory/api/routes/authRoutes.js
@@ -137,9 +137,11 @@ router.post('/refresh-token', async (req, res) => {
const connection = await getDb();
- // 사용자 정보 조회
+ // 사용자 정보 조회 (roles 조인으로 role_name 포함)
const [users] = await connection.execute(
- 'SELECT * FROM users WHERE user_id = ? AND is_active = TRUE',
+ `SELECT u.*, r.name as role_name, u._access_level_old as access_level
+ FROM users u LEFT JOIN roles r ON u.role_id = r.id
+ WHERE u.user_id = ? AND u.is_active = TRUE`,
[decoded.user_id]
);
@@ -149,11 +151,12 @@ router.post('/refresh-token', async (req, res) => {
const user = users[0];
- // 새 토큰 발급
+ // 새 토큰 발급 (role 필드 포함)
const newToken = jwt.sign(
{
user_id: user.user_id,
username: user.username,
+ role: user.role_name || user.access_level || 'user',
access_level: user.access_level,
name: user.name || user.username
},
diff --git a/system1-factory/api/services/dailyWorkReportService.js b/system1-factory/api/services/dailyWorkReportService.js
index b29ff11..37a662b 100644
--- a/system1-factory/api/services/dailyWorkReportService.js
+++ b/system1-factory/api/services/dailyWorkReportService.js
@@ -128,8 +128,9 @@ const getDailyWorkReportsService = async (queryParams, userInfo) => {
});
}
- // 관리자 여부 확인
- const isAdmin = role === 'system' || role === 'admin';
+ // 관리자 여부 확인 (대소문자 무시)
+ const roleLower = (role || '').toLowerCase();
+ const isAdmin = roleLower === 'system' || roleLower === 'admin' || roleLower === 'system admin';
const canViewAll = isAdmin || view_all === 'true';
// 모델에 전달할 조회 옵션 객체 생성
diff --git a/system2-report/web/js/api-base.js b/system2-report/web/js/api-base.js
index b0ca2b5..dbac71e 100644
--- a/system2-report/web/js/api-base.js
+++ b/system2-report/web/js/api-base.js
@@ -1,6 +1,18 @@
// /js/api-base.js
// API 기본 설정 및 보안 유틸리티 - System 2 (신고 시스템)
+// 서비스 워커 해제 (캐시 간섭으로 인한 인증 루프 방지)
+if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.getRegistrations().then(function(registrations) {
+ registrations.forEach(function(registration) { registration.unregister(); });
+ });
+ if (typeof caches !== 'undefined') {
+ caches.keys().then(function(names) {
+ names.forEach(function(name) { caches.delete(name); });
+ });
+ }
+}
+
(function() {
'use strict';
@@ -35,10 +47,11 @@
*/
window.getLoginUrl = function() {
var hostname = window.location.hostname;
+ var t = Date.now();
if (hostname.includes('technicalkorea.net')) {
- return window.location.protocol + '//tkfb.technicalkorea.net/login?redirect=' + encodeURIComponent(window.location.href);
+ return window.location.protocol + '//tkfb.technicalkorea.net/login?redirect=' + encodeURIComponent(window.location.href) + '&_t=' + t;
}
- return window.location.protocol + '//' + hostname + ':30000/login?redirect=' + encodeURIComponent(window.location.href);
+ return window.location.protocol + '//' + hostname + ':30000/login?redirect=' + encodeURIComponent(window.location.href) + '&_t=' + t;
};
window.clearSSOAuth = function() {
@@ -123,10 +136,9 @@
var response = await fetch(url, config);
- // 401 Unauthorized 처리
+ // 401 Unauthorized 처리 — 토큰만 정리하고 에러 throw (리다이렉트는 app-init이 처리)
if (response.status === 401) {
window.clearSSOAuth();
- window.location.href = window.getLoginUrl();
throw new Error('인증이 만료되었습니다.');
}
diff --git a/system2-report/web/js/app-init.js b/system2-report/web/js/app-init.js
index 0c79f2b..9cef057 100644
--- a/system2-report/web/js/app-init.js
+++ b/system2-report/web/js/app-init.js
@@ -4,6 +4,21 @@
(function() {
'use strict';
+ // ===== 리다이렉트 루프 방지 =====
+ var REDIRECT_KEY = '_sso_redirect_ts';
+ var REDIRECT_COOLDOWN = 5000; // 5초 내 재리다이렉트 방지
+
+ function safeRedirectToLogin() {
+ var lastRedirect = parseInt(sessionStorage.getItem(REDIRECT_KEY) || '0', 10);
+ var now = Date.now();
+ if (now - lastRedirect < REDIRECT_COOLDOWN) {
+ console.warn('[System2] 리다이렉트 루프 감지 — 로그인 페이지로 이동하지 않음');
+ return;
+ }
+ sessionStorage.setItem(REDIRECT_KEY, String(now));
+ window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
+ }
+
// ===== 인증 함수 (api-base.js의 전역 헬퍼 활용) =====
function isLoggedIn() {
var token = window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
@@ -28,17 +43,21 @@
// 1. 인증 확인
if (!isLoggedIn()) {
clearAuthData();
- window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
+ safeRedirectToLogin();
return;
}
var currentUser = getUser();
if (!currentUser || !currentUser.username) {
clearAuthData();
- window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
+ safeRedirectToLogin();
return;
}
+ // 인증 성공 — 루프 카운터 리셋 + localStorage 백업
+ sessionStorage.removeItem(REDIRECT_KEY);
+ var token = window.getSSOToken ? window.getSSOToken() : null;
+ if (token && !localStorage.getItem('sso_token')) localStorage.setItem('sso_token', token);
console.log('[System2] 인증 확인:', currentUser.username);
}
diff --git a/system2-report/web/pages/safety/issue-detail.html b/system2-report/web/pages/safety/issue-detail.html
index c920f39..2861fe0 100644
--- a/system2-report/web/pages/safety/issue-detail.html
+++ b/system2-report/web/pages/safety/issue-detail.html
@@ -8,8 +8,8 @@
-
-
+
+