- fix: 로그인 API에서 user.role_name 필드 올바르게 사용 (auth.service.js) - refactor: navbar 컴포넌트를 최신 dashboard-header 스타일로 전환 - refactor: 구버전 work-report-header 제거 (6개 페이지) - refactor: load-navbar.js를 최신 헤더 구조에 맞게 업데이트 - style: 파란색 그라데이션 헤더, 실시간 시계, 향상된 프로필 메뉴 - docs: 2026-01-20 개발 로그 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
98 lines
3.8 KiB
JavaScript
98 lines
3.8 KiB
JavaScript
const bcrypt = require('bcryptjs');
|
|
const jwt = require('jsonwebtoken');
|
|
const userModel = require('../models/userModel');
|
|
const { getDb } = require('../dbPool');
|
|
|
|
// 로그인 이력 기록 (서비스 내부 헬퍼 함수)
|
|
const recordLoginHistory = async (userId, success, ipAddress, userAgent, failureReason = null) => {
|
|
try {
|
|
const db = await getDb();
|
|
await db.query(
|
|
`INSERT INTO login_logs (user_id, login_time, ip_address, user_agent, login_status, failure_reason)
|
|
VALUES (?, NOW(), ?, ?, ?, ?)`,
|
|
[userId, ipAddress || 'unknown', userAgent || 'unknown', success ? 'success' : 'failed', failureReason]
|
|
);
|
|
} catch (error) {
|
|
console.error('로그인 이력 기록 실패:', error);
|
|
}
|
|
};
|
|
|
|
const loginService = async (username, password, ipAddress, userAgent) => {
|
|
// 서비스 레이어에서는 더 이상 DB 커넥션을 직접 다루지 않음
|
|
try {
|
|
const user = await userModel.findByUsername(username);
|
|
|
|
if (!user) {
|
|
console.log(`[로그인 실패] 사용자를 찾을 수 없음: ${username}`);
|
|
return { success: false, status: 401, error: '아이디 또는 비밀번호가 올바르지 않습니다.' };
|
|
}
|
|
|
|
if (user.is_active === false) {
|
|
await recordLoginHistory(user.user_id, false, ipAddress, userAgent, 'account_disabled');
|
|
return { success: false, status: 403, error: '비활성화된 계정입니다. 관리자에게 문의하세요.' };
|
|
}
|
|
|
|
if (user.locked_until && new Date(user.locked_until) > new Date()) {
|
|
const remainingTime = Math.ceil((new Date(user.locked_until) - new Date()) / 1000 / 60);
|
|
return { success: false, status: 429, error: `계정이 잠겨있습니다. ${remainingTime}분 후에 다시 시도하세요.` };
|
|
}
|
|
|
|
const isValid = await bcrypt.compare(password, user.password);
|
|
if (!isValid) {
|
|
console.log(`[로그인 실패] 비밀번호 불일치: ${username}`);
|
|
|
|
// 모델 함수를 사용하여 로그인 실패 처리
|
|
await userModel.incrementFailedLoginAttempts(user.user_id);
|
|
|
|
if (user.failed_login_attempts >= 4) {
|
|
await userModel.lockUserAccount(user.user_id);
|
|
}
|
|
|
|
await recordLoginHistory(user.user_id, false, ipAddress, userAgent, 'invalid_password');
|
|
return { success: false, status: 401, error: '아이디 또는 비밀번호가 올바르지 않습니다.' };
|
|
}
|
|
|
|
// 성공 시 모델 함수를 사용하여 상태 초기화
|
|
await userModel.resetLoginAttempts(user.user_id);
|
|
|
|
|
|
const token = jwt.sign(
|
|
{ user_id: user.user_id, username: user.username, role: user.role_name, role_id: user.role_id, access_level: user.access_level, worker_id: user.worker_id, name: user.name || user.username },
|
|
process.env.JWT_SECRET || 'your-secret-key',
|
|
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
|
);
|
|
|
|
const refreshToken = jwt.sign(
|
|
{ user_id: user.user_id, type: 'refresh' },
|
|
process.env.JWT_REFRESH_SECRET || process.env.JWT_SECRET || 'your-refresh-secret',
|
|
{ expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d' }
|
|
);
|
|
|
|
await recordLoginHistory(user.user_id, true, ipAddress, userAgent);
|
|
console.log(`[로그인 성공] 사용자: ${user.username} (${user.access_level})`);
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
token,
|
|
refreshToken,
|
|
user: {
|
|
user_id: user.user_id,
|
|
username: user.username,
|
|
name: user.name || user.username,
|
|
role: user.role_name,
|
|
access_level: user.access_level,
|
|
worker_id: user.worker_id
|
|
}
|
|
}
|
|
};
|
|
} catch (error) {
|
|
console.error('Login service error:', error);
|
|
throw new Error('서버 오류가 발생했습니다.');
|
|
}
|
|
// 서비스 레이어에서는 더 이상 DB 커넥션을 직접 다루지 않음
|
|
};
|
|
|
|
module.exports = {
|
|
loginService,
|
|
};
|