fix(auth): 로그인 에러 표시 + 비밀번호 변경 모바일 수정
- 로그인: errEl.style.display='' → 'block' (CSS display:none과 충돌 해소) - 비밀번호 변경: ES모듈→일반 스크립트 전환 (모바일 import 체인 실패 대응) - api-config.js/config.js/navigation.js 의존 제거 - tkfb-core.js 전역 함수(getSSOToken) + fetch 직접 사용 - TODO: ES모듈 import 체인 실패 원인 별도 조사 필요 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -787,7 +787,7 @@
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errEl.textContent = err.message;
|
errEl.textContent = err.message;
|
||||||
errEl.style.display = '';
|
errEl.style.display = 'block';
|
||||||
} finally {
|
} finally {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = '\uB85C\uADF8\uC778';
|
btn.textContent = '\uB85C\uADF8\uC778';
|
||||||
|
|||||||
@@ -1,208 +1,128 @@
|
|||||||
// js/change-password.js
|
// js/change-password.js — 비밀번호 변경 (일반 스크립트, tkfb-core.js 전역 함수 사용)
|
||||||
// 개인 비밀번호 변경 페이지 JavaScript
|
(function() {
|
||||||
|
var form = document.getElementById('changePasswordForm');
|
||||||
|
var messageArea = document.getElementById('message-area');
|
||||||
|
var submitBtn = document.getElementById('submitBtn');
|
||||||
|
var resetBtn = document.getElementById('resetBtn');
|
||||||
|
|
||||||
import { API, getAuthHeaders, ensureAuthenticated } from '/js/api-config.js';
|
if (!form) return;
|
||||||
|
|
||||||
// 인증 확인
|
// 비밀번호 토글
|
||||||
const token = ensureAuthenticated();
|
document.querySelectorAll('.password-toggle').forEach(function(button) {
|
||||||
|
|
||||||
// DOM 요소
|
|
||||||
const form = document.getElementById('changePasswordForm');
|
|
||||||
const messageArea = document.getElementById('message-area');
|
|
||||||
const submitBtn = document.getElementById('submitBtn');
|
|
||||||
const resetBtn = document.getElementById('resetBtn');
|
|
||||||
|
|
||||||
// 비밀번호 토글 기능
|
|
||||||
document.querySelectorAll('.password-toggle').forEach(button => {
|
|
||||||
button.addEventListener('click', function() {
|
button.addEventListener('click', function() {
|
||||||
const targetId = this.getAttribute('data-target');
|
var input = document.getElementById(this.getAttribute('data-target'));
|
||||||
const input = document.getElementById(targetId);
|
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
const isPassword = input.type === 'password';
|
var isPassword = input.type === 'password';
|
||||||
input.type = isPassword ? 'text' : 'password';
|
input.type = isPassword ? 'text' : 'password';
|
||||||
this.textContent = isPassword ? '👁️🗨️' : '👁️';
|
this.textContent = isPassword ? '숨기기' : '보기';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 초기화 버튼
|
// 초기화
|
||||||
resetBtn?.addEventListener('click', () => {
|
if (resetBtn) resetBtn.addEventListener('click', function() {
|
||||||
form.reset();
|
form.reset();
|
||||||
clearMessages();
|
|
||||||
document.getElementById('passwordStrength').innerHTML = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 메시지 표시 함수
|
|
||||||
function showMessage(type, message) {
|
|
||||||
messageArea.innerHTML = `
|
|
||||||
<div class="message-box ${type}">
|
|
||||||
${type === 'error' ? '❌' : '✅'} ${message}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 에러 메시지는 5초 후 자동 제거
|
|
||||||
if (type === 'error') {
|
|
||||||
setTimeout(clearMessages, 5000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearMessages() {
|
|
||||||
messageArea.innerHTML = '';
|
messageArea.innerHTML = '';
|
||||||
}
|
var s = document.getElementById('passwordStrength');
|
||||||
|
if (s) s.innerHTML = '';
|
||||||
// 비밀번호 강도 체크
|
|
||||||
async function checkPasswordStrength(password) {
|
|
||||||
if (!password) {
|
|
||||||
document.getElementById('passwordStrength').innerHTML = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API}/auth/check-password-strength`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ password })
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await res.json();
|
function showMessage(type, msg) {
|
||||||
updatePasswordStrengthUI(result);
|
messageArea.innerHTML = '<div class="message-box ' + type + '">' +
|
||||||
} catch (error) {
|
(type === 'error' ? '❌ ' : '✅ ') + msg + '</div>';
|
||||||
console.error('Password strength check error:', error);
|
if (type === 'error') setTimeout(function() { messageArea.innerHTML = ''; }, 5000);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 비밀번호 강도 UI 업데이트
|
// 비밀번호 강도 체크
|
||||||
function updatePasswordStrengthUI(strength) {
|
var strengthTimer;
|
||||||
const container = document.getElementById('passwordStrength');
|
var newPwInput = document.getElementById('newPassword');
|
||||||
if (!container) return;
|
if (newPwInput) newPwInput.addEventListener('input', function() {
|
||||||
|
clearTimeout(strengthTimer);
|
||||||
const colors = {
|
var pw = this.value;
|
||||||
0: '#f44336',
|
strengthTimer = setTimeout(function() {
|
||||||
1: '#ff9800',
|
if (!pw) { document.getElementById('passwordStrength').innerHTML = ''; return; }
|
||||||
2: '#ffc107',
|
var token = (window.getSSOToken && window.getSSOToken()) || localStorage.getItem('sso_token') || '';
|
||||||
3: '#4caf50',
|
fetch('/api/auth/check-password-strength', {
|
||||||
4: '#2196f3'
|
method: 'POST',
|
||||||
};
|
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||||
|
body: JSON.stringify({ password: pw })
|
||||||
const strengthText = strength.strengthText || '비밀번호를 입력하세요';
|
}).then(function(r) { return r.json(); }).then(function(result) {
|
||||||
const color = colors[strength.strength] || '#ccc';
|
if (!result.success) return;
|
||||||
const percentage = (strength.score / strength.maxScore) * 100;
|
var d = result.data;
|
||||||
|
var colors = { weak: '#f44336', medium: '#ffc107', strong: '#4caf50' };
|
||||||
container.innerHTML = `
|
var labels = { weak: '약함', medium: '보통', strong: '강함' };
|
||||||
<div style="margin-top: 12px;">
|
var pct = (d.score / 5) * 100;
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
document.getElementById('passwordStrength').innerHTML =
|
||||||
<span style="font-size: 0.85rem; color: ${color}; font-weight: 500;">
|
'<div style="margin-top:10px"><div style="display:flex;justify-content:space-between;margin-bottom:4px">' +
|
||||||
${strengthText}
|
'<span style="font-size:0.85rem;color:' + (colors[d.level]||'#ccc') + ';font-weight:500">' + (labels[d.level]||'') + '</span>' +
|
||||||
</span>
|
'<span style="font-size:0.8rem;color:#666">' + d.score + '/5</span></div>' +
|
||||||
<span style="font-size: 0.8rem; color: #666;">
|
'<div style="height:6px;background:#e0e0e0;border-radius:3px;overflow:hidden">' +
|
||||||
${strength.score}/${strength.maxScore}
|
'<div style="width:' + pct + '%;height:100%;background:' + (colors[d.level]||'#ccc') + ';transition:all 0.3s"></div></div></div>';
|
||||||
</span>
|
}).catch(function() {});
|
||||||
</div>
|
|
||||||
<div style="height: 6px; background: #e0e0e0; border-radius: 3px; overflow: hidden;">
|
|
||||||
<div style="width: ${percentage}%; height: 100%; background: ${color}; transition: all 0.3s;"></div>
|
|
||||||
</div>
|
|
||||||
${strength.feedback && strength.feedback.length > 0 ? `
|
|
||||||
<ul style="margin-top: 10px; font-size: 0.8rem; color: #666; padding-left: 20px;">
|
|
||||||
${strength.feedback.map(f => `<li>${f}</li>`).join('')}
|
|
||||||
</ul>
|
|
||||||
` : ''}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 비밀번호 입력 이벤트
|
|
||||||
let strengthCheckTimer;
|
|
||||||
document.getElementById('newPassword')?.addEventListener('input', (e) => {
|
|
||||||
clearTimeout(strengthCheckTimer);
|
|
||||||
strengthCheckTimer = setTimeout(() => {
|
|
||||||
checkPasswordStrength(e.target.value);
|
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 폼 제출
|
// 폼 제출
|
||||||
form?.addEventListener('submit', async (e) => {
|
form.addEventListener('submit', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
clearMessages();
|
messageArea.innerHTML = '';
|
||||||
|
|
||||||
const currentPassword = document.getElementById('currentPassword').value;
|
var currentPassword = document.getElementById('currentPassword').value;
|
||||||
const newPassword = document.getElementById('newPassword').value;
|
var newPassword = document.getElementById('newPassword').value;
|
||||||
const confirmPassword = document.getElementById('confirmPassword').value;
|
var confirmPassword = document.getElementById('confirmPassword').value;
|
||||||
|
|
||||||
// 유효성 검사
|
|
||||||
if (!currentPassword || !newPassword || !confirmPassword) {
|
if (!currentPassword || !newPassword || !confirmPassword) {
|
||||||
showMessage('error', '모든 필드를 입력해주세요.');
|
showMessage('error', '모든 필드를 입력해주세요.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword !== confirmPassword) {
|
if (newPassword !== confirmPassword) {
|
||||||
showMessage('error', '새 비밀번호가 일치하지 않습니다.');
|
showMessage('error', '새 비밀번호가 일치하지 않습니다.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword.length < 6) {
|
if (newPassword.length < 6) {
|
||||||
showMessage('error', '비밀번호는 최소 6자 이상이어야 합니다.');
|
showMessage('error', '비밀번호는 최소 6자 이상이어야 합니다.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPassword === newPassword) {
|
if (currentPassword === newPassword) {
|
||||||
showMessage('error', '새 비밀번호는 현재 비밀번호와 달라야 합니다.');
|
showMessage('error', '새 비밀번호는 현재 비밀번호와 달라야 합니다.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 버튼 상태 변경
|
var originalText = submitBtn.innerHTML;
|
||||||
const originalText = submitBtn.innerHTML;
|
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
submitBtn.innerHTML = '<span>⏳</span><span>처리 중...</span>';
|
submitBtn.innerHTML = '처리 중...';
|
||||||
|
|
||||||
try {
|
var token = (window.getSSOToken && window.getSSOToken()) || localStorage.getItem('sso_token') || '';
|
||||||
const res = await fetch(`${API}/auth/change-password`, {
|
fetch('/api/auth/change-password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthHeaders(),
|
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({ currentPassword: currentPassword, newPassword: newPassword })
|
||||||
currentPassword,
|
}).then(function(res) {
|
||||||
newPassword
|
return res.json().then(function(data) { return { ok: res.ok, data: data }; });
|
||||||
})
|
}).then(function(result) {
|
||||||
});
|
if (result.ok && result.data.success) {
|
||||||
|
showMessage('success', '비밀번호가 변경되었습니다.');
|
||||||
const result = await res.json();
|
|
||||||
|
|
||||||
if (res.ok && result.success) {
|
|
||||||
showMessage('success', '비밀번호가 성공적으로 변경되었습니다.');
|
|
||||||
form.reset();
|
form.reset();
|
||||||
document.getElementById('passwordStrength').innerHTML = '';
|
var s = document.getElementById('passwordStrength');
|
||||||
|
if (s) s.innerHTML = '';
|
||||||
// 카운트다운 시작
|
var countdown = 3;
|
||||||
let countdown = 3;
|
var interval = setInterval(function() {
|
||||||
const countdownInterval = setInterval(() => {
|
showMessage('success', '비밀번호가 변경되었습니다. ' + countdown + '초 후 로그인 페이지로 이동합니다.');
|
||||||
showMessage('success',
|
|
||||||
`비밀번호가 변경되었습니다. ${countdown}초 후 로그인 페이지로 이동합니다.`
|
|
||||||
);
|
|
||||||
countdown--;
|
countdown--;
|
||||||
|
|
||||||
if (countdown < 0) {
|
if (countdown < 0) {
|
||||||
clearInterval(countdownInterval);
|
clearInterval(interval);
|
||||||
if (window.clearSSOAuth) window.clearSSOAuth();
|
if (window.clearSSOAuth) window.clearSSOAuth();
|
||||||
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
|
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const errorMessage = result.message || result.error || '비밀번호 변경에 실패했습니다.';
|
showMessage('error', result.data.message || result.data.error || '비밀번호 변경에 실패했습니다.');
|
||||||
showMessage('error', errorMessage);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}).catch(function() {
|
||||||
console.error('Password change error:', error);
|
|
||||||
showMessage('error', '서버와의 연결에 실패했습니다. 잠시 후 다시 시도해주세요.');
|
showMessage('error', '서버와의 연결에 실패했습니다. 잠시 후 다시 시도해주세요.');
|
||||||
} finally {
|
}).finally(function() {
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
submitBtn.innerHTML = originalText;
|
submitBtn.innerHTML = originalText;
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
})();
|
||||||
// 페이지 로드 시 현재 사용자 정보 표시
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const user = JSON.parse(localStorage.getItem('sso_user') || '{}');
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -391,8 +391,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/tkfb-core.js?v=2026040102"></script>
|
<script src="/static/js/tkfb-core.js?v=2026040102"></script>
|
||||||
<script src="/js/api-base.js?v=2026031401"></script>
|
|
||||||
<script type="module" src="/js/change-password.js"></script>
|
|
||||||
<script>initAuth();</script>
|
<script>initAuth();</script>
|
||||||
|
<script src="/js/change-password.js?v=2026040101"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user