security: 보안 강제 시스템 구축 + 하드코딩 비밀번호 제거
보안 감사 결과 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>
This commit is contained in:
130
system1-factory/web/public/js/change-password.js
Normal file
130
system1-factory/web/public/js/change-password.js
Normal file
@@ -0,0 +1,130 @@
|
||||
// js/change-password.js — 비밀번호 변경 (일반 스크립트, tkfb-core.js 전역 함수 사용)
|
||||
(function() {
|
||||
var form = document.getElementById('changePasswordForm');
|
||||
var messageArea = document.getElementById('message-area');
|
||||
var submitBtn = document.getElementById('submitBtn');
|
||||
var resetBtn = document.getElementById('resetBtn');
|
||||
|
||||
if (!form) return;
|
||||
|
||||
// 비밀번호 토글
|
||||
document.querySelectorAll('.password-toggle').forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
var input = document.getElementById(this.getAttribute('data-target'));
|
||||
if (input) {
|
||||
var isPassword = input.type === 'password';
|
||||
input.type = isPassword ? 'text' : 'password';
|
||||
this.textContent = isPassword ? '숨기기' : '보기';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 초기화
|
||||
if (resetBtn) resetBtn.addEventListener('click', function() {
|
||||
form.reset();
|
||||
messageArea.innerHTML = '';
|
||||
var s = document.getElementById('passwordStrength');
|
||||
if (s) s.innerHTML = '';
|
||||
});
|
||||
|
||||
function showMessage(type, msg) {
|
||||
messageArea.innerHTML = '<div class="message-box ' + type + '">' +
|
||||
(type === 'error' ? '❌ ' : '✅ ') + msg + '</div>';
|
||||
if (type === 'error') setTimeout(function() { messageArea.innerHTML = ''; }, 5000);
|
||||
}
|
||||
|
||||
// 비밀번호 강도 체크
|
||||
var strengthTimer;
|
||||
var newPwInput = document.getElementById('newPassword');
|
||||
if (newPwInput) newPwInput.addEventListener('input', function() {
|
||||
clearTimeout(strengthTimer);
|
||||
var pw = this.value;
|
||||
strengthTimer = setTimeout(function() {
|
||||
if (!pw) { document.getElementById('passwordStrength').innerHTML = ''; return; }
|
||||
var token = (window.getSSOToken && window.getSSOToken()) || localStorage.getItem('sso_token') || '';
|
||||
fetch('/api/auth/check-password-strength', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||
body: JSON.stringify({ password: pw })
|
||||
}).then(function(r) { return r.json(); }).then(function(result) {
|
||||
if (!result.success) return;
|
||||
var d = result.data;
|
||||
var colors = { weak: '#f44336', medium: '#ffc107', strong: '#4caf50' };
|
||||
var labels = { weak: '약함', medium: '보통', strong: '강함' };
|
||||
var pct = (d.score / 5) * 100;
|
||||
document.getElementById('passwordStrength').innerHTML =
|
||||
'<div style="margin-top:10px"><div style="display:flex;justify-content:space-between;margin-bottom:4px">' +
|
||||
'<span style="font-size:0.85rem;color:' + (colors[d.level]||'#ccc') + ';font-weight:500">' + (labels[d.level]||'') + '</span>' +
|
||||
'<span style="font-size:0.8rem;color:#666">' + d.score + '/5</span></div>' +
|
||||
'<div style="height:6px;background:#e0e0e0;border-radius:3px;overflow:hidden">' +
|
||||
'<div style="width:' + pct + '%;height:100%;background:' + (colors[d.level]||'#ccc') + ';transition:all 0.3s"></div></div></div>';
|
||||
}).catch(function() {});
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// 폼 제출
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
messageArea.innerHTML = '';
|
||||
|
||||
var currentPassword = document.getElementById('currentPassword').value;
|
||||
var newPassword = document.getElementById('newPassword').value;
|
||||
var confirmPassword = document.getElementById('confirmPassword').value;
|
||||
|
||||
if (!currentPassword || !newPassword || !confirmPassword) {
|
||||
showMessage('error', '모든 필드를 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
if (newPassword !== confirmPassword) {
|
||||
showMessage('error', '새 비밀번호가 일치하지 않습니다.');
|
||||
return;
|
||||
}
|
||||
if (newPassword.length < 6) {
|
||||
showMessage('error', '비밀번호는 최소 6자 이상이어야 합니다.');
|
||||
return;
|
||||
}
|
||||
if (currentPassword === newPassword) {
|
||||
showMessage('error', '새 비밀번호는 현재 비밀번호와 달라야 합니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
var originalText = submitBtn.innerHTML;
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '처리 중...';
|
||||
|
||||
var token = (window.getSSOToken && window.getSSOToken()) || localStorage.getItem('sso_token') || '';
|
||||
fetch('/api/auth/change-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||
body: JSON.stringify({ currentPassword: currentPassword, newPassword: newPassword })
|
||||
}).then(function(res) {
|
||||
return res.json().then(function(data) { return { ok: res.ok, data: data }; });
|
||||
}).then(function(result) {
|
||||
if (result.ok && result.data.success) {
|
||||
showMessage('success', '비밀번호가 변경되었습니다.');
|
||||
form.reset();
|
||||
var s = document.getElementById('passwordStrength');
|
||||
if (s) s.innerHTML = '';
|
||||
var countdown = 3;
|
||||
var interval = setInterval(function() {
|
||||
showMessage('success', '비밀번호가 변경되었습니다. ' + countdown + '초 후 로그인 페이지로 이동합니다.');
|
||||
countdown--;
|
||||
if (countdown < 0) {
|
||||
clearInterval(interval);
|
||||
// 쿠키 + localStorage 전부 삭제 (doLogout 로직 재사용)
|
||||
_cookieRemove('sso_token'); _cookieRemove('sso_user'); _cookieRemove('sso_refresh_token');
|
||||
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) { localStorage.removeItem(k); });
|
||||
window.location.href = (window.getLoginUrl ? window.getLoginUrl() : '/login') + '&logout=1';
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
showMessage('error', result.data.message || result.data.error || '비밀번호 변경에 실패했습니다.');
|
||||
}
|
||||
}).catch(function() {
|
||||
showMessage('error', '서버와의 연결에 실패했습니다. 잠시 후 다시 시도해주세요.');
|
||||
}).finally(function() {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = originalText;
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user