보안 감사 결과 CRITICAL 2건, HIGH 5건 발견 → 수정 완료 + 자동화 구축. [보안 수정] - issue-view.js: 하드코딩 비밀번호 → crypto.getRandomValues() 랜덤 생성 - pushSubscriptionController.js: ntfy 비밀번호 → process.env.NTFY_SUB_PASSWORD - DEPLOY-GUIDE.md/PROGRESS.md/migration SQL: 평문 비밀번호 → placeholder - docker-compose.yml/.env.example: NTFY_SUB_PASSWORD 환경변수 추가 [보안 강제 시스템 - 신규] - scripts/security-scan.sh: 8개 규칙 (CRITICAL 2, HIGH 4, MEDIUM 2) 3모드(staged/all/diff), severity, .securityignore, MEDIUM 임계값 - .githooks/pre-commit: 로컬 빠른 피드백 - .githooks/pre-receive-server.sh: Gitea 서버 최종 차단 bypass 거버넌스([SECURITY-BYPASS: 사유] + 사용자 제한 + 로그) - SECURITY-CHECKLIST.md: 10개 카테고리 자동/수동 구분 - docs/SECURITY-GUIDE.md: 운영자 가이드 (워크플로우, bypass, FAQ) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
108 lines
3.8 KiB
JavaScript
108 lines
3.8 KiB
JavaScript
// /js/app-init.js
|
|
// System 2 (신고 시스템) 앱 초기화 - SSO 인증 체크
|
|
|
|
(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의 cookieGet은 IIFE 내부이므로) =====
|
|
function cookieGet(name) {
|
|
var match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
|
|
return match ? decodeURIComponent(match[1]) : null;
|
|
}
|
|
|
|
// ===== 인증 함수 (api-base.js의 전역 헬퍼 활용) =====
|
|
function isLoggedIn() {
|
|
var token = window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
|
|
return token && token !== 'undefined' && token !== 'null';
|
|
}
|
|
|
|
function getUser() {
|
|
return window.getSSOUser ? window.getSSOUser() : (function() {
|
|
var u = localStorage.getItem('sso_user');
|
|
return u ? JSON.parse(u) : null;
|
|
})();
|
|
}
|
|
|
|
function clearAuthData() {
|
|
if (window.clearSSOAuth) { window.clearSSOAuth(); return; }
|
|
localStorage.removeItem('sso_token');
|
|
localStorage.removeItem('sso_user');
|
|
}
|
|
|
|
// ===== 메인 초기화 =====
|
|
async function init() {
|
|
// 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리
|
|
var cookieToken = cookieGet('sso_token');
|
|
var localToken = localStorage.getItem('sso_token');
|
|
if (!cookieToken && localToken) {
|
|
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
|
|
'currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) { localStorage.removeItem(k); });
|
|
safeRedirectToLogin();
|
|
return;
|
|
}
|
|
|
|
// 1. 인증 확인
|
|
if (!isLoggedIn()) {
|
|
clearAuthData();
|
|
safeRedirectToLogin();
|
|
return;
|
|
}
|
|
|
|
var currentUser = getUser();
|
|
if (!currentUser || !currentUser.username) {
|
|
clearAuthData();
|
|
safeRedirectToLogin();
|
|
return;
|
|
}
|
|
|
|
// 협력업체 계정 차단 (JWT에서 partner_company_id 확인)
|
|
var token = window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
|
|
if (token) {
|
|
try {
|
|
var payload = JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')));
|
|
if (payload.partner_company_id) {
|
|
var h = window.location.hostname;
|
|
window.location.href = h.includes('technicalkorea.net')
|
|
? 'https://tkpurchase.technicalkorea.net/partner-portal.html'
|
|
: window.location.protocol + '//' + h + ':30480/partner-portal.html';
|
|
return;
|
|
}
|
|
} catch(e) { /* ignore decode errors */ }
|
|
}
|
|
|
|
// 인증 성공 — 루프 카운터 리셋 + 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);
|
|
|
|
// 알림 벨 로드
|
|
if (window._loadNotificationBell) window._loadNotificationBell();
|
|
}
|
|
|
|
// DOMContentLoaded 시 실행
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
// 전역 노출
|
|
window.appInit = { getUser: getUser, clearAuthData: clearAuthData, isLoggedIn: isLoggedIn };
|
|
})();
|