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:
Hyungi Ahn
2026-04-01 13:34:49 +09:00
parent 30222df0ef
commit 02e9f8c0ab
3 changed files with 69 additions and 2 deletions

View File

@@ -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
};