diff --git a/src/main/java/com/eactive/apim/portal/apprequest/repository/AppRequestRepository.java b/src/main/java/com/eactive/apim/portal/apprequest/repository/AppRequestRepository.java index 5e4bb04..dfb2167 100644 --- a/src/main/java/com/eactive/apim/portal/apprequest/repository/AppRequestRepository.java +++ b/src/main/java/com/eactive/apim/portal/apprequest/repository/AppRequestRepository.java @@ -23,6 +23,8 @@ public interface AppRequestRepository extends BaseRepository List findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List types, List approvalStates); + List findAllByOrgAndTypeIsInAndApprovalIsNull(PortalOrg org, List types); + int countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List types, List approvalStates); Optional findByIdAndOrg(String id, PortalOrg org); diff --git a/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java b/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java index c8df0a9..e01593f 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java @@ -88,7 +88,11 @@ public class PortalUser extends Auditable implements Serializable, com.eactive.e @Comment("로그인실패횟수") private Integer loginFailureCount; - @Column(name = "password_change_date") + // PASSWORD_CHANGE_DATE 컬럼은 VARCHAR2(14) yyyyMMddHHmmss 문자열이다. + // LocalDateTime 을 그대로 바인딩하면 27자로 변환되어 ORA-12899(값 초과)가 발생하므로 + // 14자 문자열로 변환하는 컨버터를 적용한다. + @Column(name = "password_change_date", length = 14) + @Convert(converter = com.eactive.eai.data.converter.LocalDateTimeToStringConverter14.class) @Comment("비밀번호변경일시") private LocalDateTime passwordChangeDate; diff --git a/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java b/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java index a838918..632fac2 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java @@ -20,7 +20,8 @@ public class TwoFactorAuth { @Id @Column(name = "id", length = 36, nullable = false) - @GenericGenerator(name = "uuid-gen", strategy = "uuid2") + // UUID v7(타임스탬프 기반, 시간순 정렬) — PK 인덱스 삽입 지역성 개선. 기존 v4 행과 컬럼 호환. + @GenericGenerator(name = "uuid-gen", strategy = "com.eactive.apim.portal.jpa.UuidV7Generator") @GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY) private String id; diff --git a/src/main/java/com/eactive/apim/portal/portaluser/repository/TwoFactorAuthRepository.java b/src/main/java/com/eactive/apim/portal/portaluser/repository/TwoFactorAuthRepository.java index ef636a0..a774cb9 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/repository/TwoFactorAuthRepository.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/repository/TwoFactorAuthRepository.java @@ -2,7 +2,9 @@ package com.eactive.apim.portal.portaluser.repository; import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import java.time.LocalDateTime; import java.util.Optional; public interface TwoFactorAuthRepository extends JpaRepository { @@ -12,6 +14,14 @@ public interface TwoFactorAuthRepository extends JpaRepository findByRecipient(String recipientKey); diff --git a/src/main/java/com/eactive/apim/portal/portaluser/service/AuthNumberException.java b/src/main/java/com/eactive/apim/portal/portaluser/service/AuthNumberException.java index a6dac78..17b7dc2 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/service/AuthNumberException.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/service/AuthNumberException.java @@ -1,7 +1,37 @@ package com.eactive.apim.portal.portaluser.service; -public class AuthNumberException extends RuntimeException{ +/** + * 인증번호(2FA/이메일/SMS) 검증·발송 실패 예외. + * + *

기존에는 메시지 문자열만 있었으나, 감사 로그에서 실패 사유(만료/불일치/미존재)를 + * 코드로 구분해야 하므로 {@link Reason} 을 추가했다. 기존 메시지 전용 생성자는 유지되며 + * 이 경우 {@code reason == null} 이다(하위호환).

+ */ +public class AuthNumberException extends RuntimeException { + + /** 실패 사유 분류 */ + public enum Reason { + /** 저장된 인증번호가 없음(미발송/이미 소비/스케줄러 정리) */ + NOT_FOUND, + /** 유효시간 초과 */ + EXPIRED, + /** 인증번호 불일치 */ + MISMATCH + } + + private final Reason reason; + public AuthNumberException(String message) { super(message); + this.reason = null; + } + + public AuthNumberException(String message, Reason reason) { + super(message); + this.reason = reason; + } + + public Reason getReason() { + return reason; } } diff --git a/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java b/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java index c19a33d..62d0bac 100644 --- a/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java +++ b/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java @@ -45,4 +45,12 @@ public class UserLog { @Column(name = "success", nullable = false) private boolean success; + /** 실패 사유 코드 (LoginFailureReason). 성공 행은 null */ + @Column(name = "failure_reason", length = 64) + private String failureReason; + + /** 로그인 유형 코드 (LoginType: NORMAL/TWO_FACTOR/SIGNUP_AUTO). 실패 행·과거 행은 null */ + @Column(name = "login_type", length = 32) + private String loginType; + }