package com.zioinfo.mes.auth; import com.zioinfo.mes.auth.mapper.UserMapper; import com.zioinfo.mes.uiws.auth.TwoFactorService; import com.zioinfo.mes.uiws.common.UiwsApiException; import com.zioinfo.mes.uiws.common.UiwsErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; /** * MES 인증 서비스. * - 기존 단일 JWT 로그인 보존(2FA off 또는 미설정 시 회귀 0). * - UIWS 2FA 레이어(이메일 코드) 추가: 2FA on 이면 1차 통과 후 verify-token + 이메일코드 발급. * 실패 누적 max-login-fail 회 시 계정 잠금. */ @Service @RequiredArgsConstructor public class AuthService { private final UserMapper userMapper; private final PasswordEncoder passwordEncoder; private final JwtUtil jwtUtil; private final TwoFactorService twoFactorService; /** * 1차 로그인. 2FA 활성 시 verify-token + 이메일코드 흐름으로 분기, * 비활성 시 기존처럼 access 토큰 즉시 발급. * * @return 2FA off: { token, type, twofa:"false" } * 2FA on : { verifyToken, step:"EMAIL", maskedEmail, twofa:"true" } */ public Map login(String username, String password) { MesUser user = userMapper.findByUsername(username); // 잠금 우선 차단(존재하는 계정에 한해 잠금 메시지 — 존재 여부 누설 최소화) if (user != null && twoFactorService.isLocked(user)) { throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED); } if (user == null || !user.isActive()) { throw new RuntimeException("ERR-AUTH-001: 존재하지 않거나 비활성 계정"); } if (!passwordEncoder.matches(password, user.getPasswordHash())) { // 2FA 활성 시 실패 누적/잠금. 비활성 시 기존 동작(메시지만) 유지. if (twoFactorService.isEnabled()) { twoFactorService.recordLoginFailure(username); MesUser after = userMapper.findByUsername(username); if (after != null && Boolean.TRUE.equals(after.getLocked())) { throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED); } } throw new RuntimeException("ERR-AUTH-002: 비밀번호 불일치"); } // 비밀번호 검증 통과 if (twoFactorService.isEnabled()) { Map step1 = twoFactorService.beginTwoFactor(user); // verifyToken/step/maskedEmail + twofa 플래그 return Map.of( "twofa", "true", "verifyToken", step1.get("verifyToken"), "step", step1.get("step"), "maskedEmail", step1.getOrDefault("maskedEmail", "")); } // 2FA 비활성 — 기존 단일 로그인 흐름(회귀 0) userMapper.resetLoginFail(username); String token = jwtUtil.generate(username, user.getRole()); return Map.of("twofa", "false", "token", token, "type", "Bearer"); } public Map me(String token) { String username = jwtUtil.getUsername(token); String role = jwtUtil.getRole(token); MesUser u = userMapper.findByUsername(username); Map m = new HashMap<>(); m.put("username", username); m.put("role", role); m.put("displayName", u != null ? u.getDisplayName() : username); return m; } }