diff --git a/src/main/java/com/example/paycheck/common/exception/ErrorCode.java b/src/main/java/com/example/paycheck/common/exception/ErrorCode.java index c2d2f2d..4d30441 100644 --- a/src/main/java/com/example/paycheck/common/exception/ErrorCode.java +++ b/src/main/java/com/example/paycheck/common/exception/ErrorCode.java @@ -9,6 +9,7 @@ public class ErrorCode { public static final String USER_NOT_FOUND = "USER_NOT_FOUND"; public static final String USER_ALREADY_DELETED = "USER_ALREADY_DELETED"; public static final String USER_NOT_WITHDRAWN = "USER_NOT_WITHDRAWN"; + public static final String RECOVERY_PERIOD_EXPIRED = "RECOVERY_PERIOD_EXPIRED"; // Worker domain public static final String WORKER_NOT_FOUND = "WORKER_NOT_FOUND"; diff --git a/src/main/java/com/example/paycheck/domain/auth/service/AuthService.java b/src/main/java/com/example/paycheck/domain/auth/service/AuthService.java index b57e8e7..b90baa9 100644 --- a/src/main/java/com/example/paycheck/domain/auth/service/AuthService.java +++ b/src/main/java/com/example/paycheck/domain/auth/service/AuthService.java @@ -10,6 +10,7 @@ import com.example.paycheck.domain.user.entity.User; import com.example.paycheck.domain.user.enums.UserType; import com.example.paycheck.domain.user.repository.UserRepository; +import com.example.paycheck.domain.user.scheduler.UserHardDeleteScheduler; import com.example.paycheck.domain.user.service.UserHardDeleteService; import com.example.paycheck.domain.user.service.UserService; import com.example.paycheck.domain.user.service.UserWithdrawService; @@ -26,6 +27,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.time.LocalDateTime; /** * 인증 플로우를 조율하는 서비스 @@ -119,6 +121,21 @@ private AuthDto.LoginResponse buildLoginResponse(User user) { .build(); } + /** + * 탈퇴 후 복구 가능 기간(30일) 이내인지 검증한다. + * Hard-delete 스케줄러 지연/장애로 30일을 초과한 계정이 남아있어도 복구/재가입을 차단한다. + */ + private void assertWithinRecoveryPeriod(User user) { + LocalDateTime cutoff = LocalDateTime.now() + .minusDays(UserHardDeleteScheduler.RETENTION_DAYS); + if (user.getDeletedAt().isBefore(cutoff)) { + throw new BadRequestException( + ErrorCode.RECOVERY_PERIOD_EXPIRED, + "복구 가능 기간(30일)이 지난 계정입니다." + ); + } + } + /** * 탈퇴한 카카오 계정 복구 (탈퇴 취소) * - User.deletedAt = null로 되돌림 @@ -144,6 +161,7 @@ public AuthDto.LoginResponse restoreWithKakao(String kakaoAccessToken) { "탈퇴 상태가 아닌 계정입니다." ); } + assertWithinRecoveryPeriod(user); user.restore(); @@ -174,6 +192,7 @@ public AuthDto.LoginResponse purgeAndRegisterWithKakao(AuthDto.KakaoRegisterRequ "이미 가입된 카카오 계정입니다." ); } + assertWithinRecoveryPeriod(existing); userHardDeleteService.hardDeleteUser(existing.getId()); // JPA 기본 flush 순서(INSERT → DELETE)로 인해 같은 트랜잭션에서 // 신규 가입(INSERT)이 기존 사용자 DELETE보다 먼저 실행되면 diff --git a/src/main/java/com/example/paycheck/domain/user/scheduler/UserHardDeleteScheduler.java b/src/main/java/com/example/paycheck/domain/user/scheduler/UserHardDeleteScheduler.java index c58bace..5a80eed 100644 --- a/src/main/java/com/example/paycheck/domain/user/scheduler/UserHardDeleteScheduler.java +++ b/src/main/java/com/example/paycheck/domain/user/scheduler/UserHardDeleteScheduler.java @@ -19,7 +19,7 @@ @RequiredArgsConstructor public class UserHardDeleteScheduler { - private static final int RETENTION_DAYS = 30; + public static final int RETENTION_DAYS = 30; private final UserRepository userRepository; private final UserHardDeleteService userHardDeleteService; diff --git a/src/main/java/com/example/paycheck/global/security/SecurityConfig.java b/src/main/java/com/example/paycheck/global/security/SecurityConfig.java index 6b99630..cfcf590 100644 --- a/src/main/java/com/example/paycheck/global/security/SecurityConfig.java +++ b/src/main/java/com/example/paycheck/global/security/SecurityConfig.java @@ -38,7 +38,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/health", "/h2-console/**").permitAll() - .requestMatchers("/api/auth/kakao/login", "/api/auth/kakao/register", "/api/auth/refresh", "/api/auth/dev/login").permitAll() + .requestMatchers( + "/api/auth/kakao/login", + "/api/auth/kakao/register", + "/api/auth/kakao/restore", + "/api/auth/kakao/purge-and-register", + "/api/auth/refresh", + "/api/auth/dev/login" + ).permitAll() .requestMatchers("/swagger-ui/**", "/api-docs/**", "/swagger-ui.html").permitAll() .anyRequest().authenticated() )