All files / services auth.service.js

0% Statements 0/38
0% Branches 0/32
0% Functions 0/2
0% Lines 0/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98                                                                                                                                                                                                   
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.execute(
      `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, 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,
          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,
};