feat(auth): 비밀번호 변경 API 추가 + 대시보드 바로가기
- POST /api/auth/change-password: 현재 비밀번호 검증 후 변경 - POST /api/auth/check-password-strength: 비밀번호 강도 체크 - 대시보드 프로필 카드에 '비밀번호 변경' 바로가기 링크 추가 - 프론트엔드(password.html + change-password.js)는 이미 구현됨 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -366,6 +366,65 @@ async function deleteUser(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/change-password — 본인 비밀번호 변경
|
||||
*/
|
||||
async function changePassword(req, res, next) {
|
||||
try {
|
||||
const token = extractToken(req);
|
||||
if (!token) return res.status(401).json({ success: false, message: '인증이 필요합니다' });
|
||||
|
||||
const decoded = jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] });
|
||||
const userId = decoded.user_id || decoded.id;
|
||||
const user = await userModel.findById(userId);
|
||||
if (!user || !user.is_active) {
|
||||
return res.status(401).json({ success: false, message: '유효하지 않은 사용자입니다' });
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
if (!currentPassword || !newPassword) {
|
||||
return res.status(400).json({ success: false, message: '현재 비밀번호와 새 비밀번호를 모두 입력해주세요' });
|
||||
}
|
||||
if (newPassword.length < 6) {
|
||||
return res.status(400).json({ success: false, message: '새 비밀번호는 6자 이상이어야 합니다' });
|
||||
}
|
||||
if (currentPassword === newPassword) {
|
||||
return res.status(400).json({ success: false, message: '새 비밀번호는 현재 비밀번호와 달라야 합니다' });
|
||||
}
|
||||
|
||||
const isValid = await userModel.verifyPassword(currentPassword, user.password_hash);
|
||||
if (!isValid) {
|
||||
return res.status(400).json({ success: false, message: '현재 비밀번호가 올바르지 않습니다' });
|
||||
}
|
||||
|
||||
await userModel.update(userId, { password: newPassword });
|
||||
res.json({ success: true, message: '비밀번호가 변경되었습니다. 다시 로그인해주세요.' });
|
||||
} catch (err) {
|
||||
if (err.name === 'JsonWebTokenError' || err.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({ success: false, message: '인증이 만료되었습니다' });
|
||||
}
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/check-password-strength — 비밀번호 강도 체크
|
||||
*/
|
||||
async function checkPasswordStrength(req, res) {
|
||||
const { password } = req.body;
|
||||
if (!password) return res.json({ success: true, data: { score: 0, level: 'weak' } });
|
||||
|
||||
let score = 0;
|
||||
if (password.length >= 6) score++;
|
||||
if (password.length >= 8) score++;
|
||||
if (/[A-Z]/.test(password)) score++;
|
||||
if (/[0-9]/.test(password)) score++;
|
||||
if (/[^A-Za-z0-9]/.test(password)) score++;
|
||||
|
||||
const level = score <= 1 ? 'weak' : score <= 3 ? 'medium' : 'strong';
|
||||
res.json({ success: true, data: { score, level } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Bearer 토큰 또는 쿠키에서 토큰 추출
|
||||
*/
|
||||
@@ -388,5 +447,7 @@ module.exports = {
|
||||
getUsers,
|
||||
createUser,
|
||||
updateUser,
|
||||
deleteUser
|
||||
deleteUser,
|
||||
changePassword,
|
||||
checkPasswordStrength
|
||||
};
|
||||
|
||||
@@ -33,6 +33,10 @@ router.get('/me', authController.me);
|
||||
router.post('/refresh', authController.refresh);
|
||||
router.post('/logout', authController.logout);
|
||||
|
||||
// 인증 사용자 엔드포인트
|
||||
router.post('/change-password', authController.changePassword);
|
||||
router.post('/check-password-strength', authController.checkPasswordStrength);
|
||||
|
||||
// 관리자 엔드포인트
|
||||
router.get('/users', requireAdmin, authController.getUsers);
|
||||
router.post('/users', requireAdmin, authController.createUser);
|
||||
|
||||
@@ -92,7 +92,9 @@ function renderDashboard(data) {
|
||||
<div class="pd-avatar">${escHtml(initial)}</div>
|
||||
<div>
|
||||
<div class="pd-profile-name">${escHtml(user.worker_name || user.name)}</div>
|
||||
<div class="pd-profile-sub">${escHtml(user.job_type || '')}${user.job_type ? ' · ' : ''}${escHtml(user.department_name)}</div>
|
||||
<div class="pd-profile-sub">${escHtml(user.job_type || '')}${user.job_type ? ' · ' : ''}${escHtml(user.department_name)}
|
||||
<a href="/pages/profile/password.html" style="margin-left:8px;color:rgba(255,255,255,0.7);font-size:11px;text-decoration:underline" title="비밀번호 변경"><i class="fas fa-key" style="margin-right:2px"></i>비밀번호 변경</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pd-info-list">
|
||||
|
||||
Reference in New Issue
Block a user