diff --git a/sso-auth-service/controllers/authController.js b/sso-auth-service/controllers/authController.js index 00b7d6e..879d197 100644 --- a/sso-auth-service/controllers/authController.js +++ b/sso-auth-service/controllers/authController.js @@ -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 }; diff --git a/sso-auth-service/routes/authRoutes.js b/sso-auth-service/routes/authRoutes.js index a3297fb..fa117c5 100644 --- a/sso-auth-service/routes/authRoutes.js +++ b/sso-auth-service/routes/authRoutes.js @@ -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); diff --git a/system1-factory/web/js/production-dashboard.js b/system1-factory/web/js/production-dashboard.js index 0c3986d..badb81f 100644 --- a/system1-factory/web/js/production-dashboard.js +++ b/system1-factory/web/js/production-dashboard.js @@ -92,7 +92,9 @@ function renderDashboard(data) {
${escHtml(initial)}
${escHtml(user.worker_name || user.name)}
-
${escHtml(user.job_type || '')}${user.job_type ? ' · ' : ''}${escHtml(user.department_name)}
+
${escHtml(user.job_type || '')}${user.job_type ? ' · ' : ''}${escHtml(user.department_name)} + 비밀번호 변경 +