refactor: 로그인 API의 DB 스키마 및 구조 개선
- 새로운 DB 스키마(v2) 추가 (테이블명 snake_case, FK 적용) - 룰.md에 API 성능 관리 규칙 추가 - 로그인 관련 로직을 새로운 스키마에 맞게 수정 - Service와 Model의 역할 분리를 명확하게 리팩토링
This commit is contained in:
@@ -5,6 +5,7 @@ const jwt = require('jsonwebtoken');
|
||||
const mysql = require('mysql2/promise');
|
||||
const { verifyToken } = require('../middlewares/authMiddleware');
|
||||
const router = express.Router();
|
||||
const authController = require('../controllers/authController');
|
||||
|
||||
// DB 연결 설정
|
||||
const dbConfig = {
|
||||
@@ -72,133 +73,7 @@ const recordLoginHistory = async (connection, userId, success, ipAddress, userAg
|
||||
/**
|
||||
* 로그인 - DB 연동 (보안 강화)
|
||||
*/
|
||||
router.post('/login', checkLoginAttempts, async (req, res) => {
|
||||
let connection;
|
||||
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
const ipAddress = req.ip || req.connection.remoteAddress;
|
||||
const userAgent = req.headers['user-agent'];
|
||||
|
||||
console.log(`[로그인 시도] 사용자: ${username}, IP: ${ipAddress}`);
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: '사용자명과 비밀번호를 입력해주세요.' });
|
||||
}
|
||||
|
||||
// DB 연결
|
||||
connection = await mysql.createConnection(dbConfig);
|
||||
|
||||
// 사용자 조회
|
||||
const [rows] = await connection.execute(
|
||||
'SELECT * FROM Users WHERE username = ?',
|
||||
[username]
|
||||
);
|
||||
|
||||
if (rows.length === 0) {
|
||||
console.log(`[로그인 실패] 사용자를 찾을 수 없음: ${username}`);
|
||||
recordLoginAttempt(username, false);
|
||||
// 보안상 구체적인 오류 메시지는 피함
|
||||
return res.status(401).json({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' });
|
||||
}
|
||||
|
||||
const user = rows[0];
|
||||
|
||||
// 계정 활성화 상태 확인
|
||||
if (user.is_active === false) {
|
||||
await recordLoginHistory(connection, user.user_id, false, ipAddress, userAgent, 'account_disabled');
|
||||
return res.status(403).json({ 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 res.status(429).json({ error: `계정이 잠겨있습니다. ${remainingTime}분 후에 다시 시도하세요.` });
|
||||
}
|
||||
|
||||
// 비밀번호 확인
|
||||
const isValid = await bcrypt.compare(password, user.password);
|
||||
if (!isValid) {
|
||||
console.log(`[로그인 실패] 비밀번호 불일치: ${username}`);
|
||||
recordLoginAttempt(username, false);
|
||||
|
||||
// 실패 횟수 업데이트
|
||||
await connection.execute(
|
||||
'UPDATE Users SET failed_login_attempts = failed_login_attempts + 1 WHERE user_id = ?',
|
||||
[user.user_id]
|
||||
);
|
||||
|
||||
// 5회 실패 시 계정 잠금
|
||||
if (user.failed_login_attempts >= 4) {
|
||||
await connection.execute(
|
||||
'UPDATE Users SET locked_until = DATE_ADD(NOW(), INTERVAL 15 MINUTE) WHERE user_id = ?',
|
||||
[user.user_id]
|
||||
);
|
||||
}
|
||||
|
||||
await recordLoginHistory(connection, user.user_id, false, ipAddress, userAgent, 'invalid_password');
|
||||
return res.status(401).json({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' });
|
||||
}
|
||||
|
||||
// 로그인 성공
|
||||
recordLoginAttempt(username, true);
|
||||
|
||||
// 마지막 로그인 시간 업데이트 및 실패 횟수 초기화
|
||||
await connection.execute(
|
||||
'UPDATE Users SET last_login_at = NOW(), failed_login_attempts = 0, locked_until = NULL WHERE user_id = ?',
|
||||
[user.user_id]
|
||||
);
|
||||
|
||||
// JWT 토큰 생성
|
||||
const token = jwt.sign(
|
||||
{
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
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(connection, user.user_id, true, ipAddress, userAgent);
|
||||
|
||||
console.log(`[로그인 성공] 사용자: ${user.username} (${user.access_level})`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
token,
|
||||
refreshToken,
|
||||
user: {
|
||||
user_id: user.user_id,
|
||||
username: user.username,
|
||||
name: user.name || user.username,
|
||||
access_level: user.access_level,
|
||||
worker_id: user.worker_id
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
router.post('/login', authController.login);
|
||||
|
||||
/**
|
||||
* 토큰 갱신
|
||||
|
||||
Reference in New Issue
Block a user