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 토큰 또는 쿠키에서 토큰 추출
|
* Bearer 토큰 또는 쿠키에서 토큰 추출
|
||||||
*/
|
*/
|
||||||
@@ -388,5 +447,7 @@ module.exports = {
|
|||||||
getUsers,
|
getUsers,
|
||||||
createUser,
|
createUser,
|
||||||
updateUser,
|
updateUser,
|
||||||
deleteUser
|
deleteUser,
|
||||||
|
changePassword,
|
||||||
|
checkPasswordStrength
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ router.get('/me', authController.me);
|
|||||||
router.post('/refresh', authController.refresh);
|
router.post('/refresh', authController.refresh);
|
||||||
router.post('/logout', authController.logout);
|
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.get('/users', requireAdmin, authController.getUsers);
|
||||||
router.post('/users', requireAdmin, authController.createUser);
|
router.post('/users', requireAdmin, authController.createUser);
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ function renderDashboard(data) {
|
|||||||
<div class="pd-avatar">${escHtml(initial)}</div>
|
<div class="pd-avatar">${escHtml(initial)}</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="pd-profile-name">${escHtml(user.worker_name || user.name)}</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>
|
</div>
|
||||||
<div class="pd-info-list">
|
<div class="pd-info-list">
|
||||||
|
|||||||
Reference in New Issue
Block a user