diff --git a/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java b/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java index 7e8ecf70..805bc129 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java +++ b/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java @@ -23,6 +23,8 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.util.Map; + @Tag(name = "프로필 API") @RestController @Slf4j @@ -38,9 +40,19 @@ public ResponseEntity getProfile(@AuthenticationPrincipal CustomUserDetails u return ResponseEntity.ok(SuccessResponse.from(member)); } + @Operation(summary = "ID 존재 확인") + @GetMapping("/exists") + @Parameters({ + @Parameter(name = "uid", description = "확인할 ID", required = true) + }) + public ResponseEntity getExistsUid(@RequestParam("uid") @NotBlank String uid) { + boolean exists = memberAccountService.existsUid(uid); + return ResponseEntity.ok(SuccessResponse.from(Map.of("valid", exists))); + } + @Operation(summary = "프로필(비밀번호/이름) 수정") @Parameters({ - @Parameter(name = "type", description = "수정할 프로필 타입", example = "name/password/", required = true), + @Parameter(name = "type", description = "수정할 프로필 타입", required = true), @Parameter(name = "req", description = "수정할 프로필 정보") }) @PutMapping("") diff --git a/src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java index 900f3fb8..36e4cfd7 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java @@ -10,4 +10,6 @@ public interface MemberRepository extends ExtendedRepository { Optional findByPhone(String phone); boolean existsByUidOrEmailOrPhone(String uid, String email, String phone); boolean existsByPhone(String phone); + boolean existsByUid(String uid); + boolean existsByPhoneAndUid(String phone, String uid); } diff --git a/src/main/java/com/kcy/fitapet/domain/member/exception/AccountErrorCode.java b/src/main/java/com/kcy/fitapet/domain/member/exception/AccountErrorCode.java index 62b1f83f..3b8075fb 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/exception/AccountErrorCode.java +++ b/src/main/java/com/kcy/fitapet/domain/member/exception/AccountErrorCode.java @@ -24,6 +24,8 @@ public enum AccountErrorCode implements StatusCode { INVALID_NOTIFICATION_TYPE_ERROR(BAD_REQUEST, "유효하지 않은 알림 타입입니다."), + MISSMATCH_PHONE_AND_UID_ERROR(BAD_REQUEST, "등록된 전화번호와 일치하지 않는 유저입니다."), + /* 404 */ NOT_FOUND_MEMBER_ERROR(NOT_FOUND, "존재하지 않는 회원입니다."), NOT_FOUND_PHONE_ERROR(NOT_FOUND, "존재하지 않는 전화번호입니다."), diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java index c30a7775..5b491170 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java @@ -36,6 +36,11 @@ public AccountProfileRes getProfile(Long userId) { return AccountProfileRes.from(member); } + @Transactional(readOnly = true) + public boolean existsUid(String uid) { + return memberSearchService.isExistByUid(uid); + } + @Transactional public void updateProfile(Long userId, ProfilePatchReq req, MemberAttrType type) { Member member = memberSearchService.findById(userId); diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java index a83d4b79..bba950e3 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java @@ -145,12 +145,21 @@ public void checkCertificationForSearch(SmsReq req, String code, SmsPrefix prefi } private void validateForSms(SmsPrefix prefix, SmsReq req) { - if (prefix.equals(SmsPrefix.REGISTER) && memberSearchService.isExistByPhone(req.to())) { + boolean isExistPhone = memberSearchService.isExistByPhone(req.to()); + if (prefix.equals(SmsPrefix.REGISTER) && isExistPhone) { log.warn("중복된 전화번호로 인한 회원가입 요청 실패: {}", req.to()); throw new GlobalErrorException(AccountErrorCode.DUPLICATE_PHONE_ERROR); - } else if (!prefix.equals(SmsPrefix.REGISTER) && !memberSearchService.isExistByPhone(req.to())) { + } else if (prefix.equals(SmsPrefix.UID) && !isExistPhone) { log.warn("DB에 존재하지 않는 전화번호로 인한 SMS 인증 요청 실패: {}", req.to()); throw new GlobalErrorException(AccountErrorCode.NOT_FOUND_PHONE_ERROR); + } else if (prefix.equals(SmsPrefix.PASSWORD)) { + if (!isExistPhone) { + log.warn("DB에 존재하지 않는 전화번호로 인한 SMS 인증 요청 실패: {}", req.to()); + throw new GlobalErrorException(AccountErrorCode.NOT_FOUND_PHONE_ERROR); + } else if (!memberSearchService.isExistByPhoneAndUid(req.to(), req.uid())) { + log.warn("등록된 유저 전화번호와 다른 전화번호 입력: {}", req.to()); + throw new GlobalErrorException(AccountErrorCode.MISSMATCH_PHONE_AND_UID_ERROR); + } } } diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java index 8f403518..be663692 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java @@ -41,4 +41,14 @@ public boolean isExistByUidOrEmailOrPhone(String uid, String email, String phone public boolean isExistByPhone(String phone) { return memberRepository.existsByPhone(phone); } + + @Transactional(readOnly = true) + public boolean isExistByUid(String uid) { + return memberRepository.existsByUid(uid); + } + + @Transactional(readOnly = true) + public boolean isExistByPhoneAndUid(String phone, String uid) { + return memberRepository.existsByPhoneAndUid(phone, uid); + } } diff --git a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsPrefix.java b/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsPrefix.java index 9cc0b104..08cc0bc7 100644 --- a/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsPrefix.java +++ b/src/main/java/com/kcy/fitapet/global/common/redis/sms/SmsPrefix.java @@ -6,14 +6,14 @@ @Getter @RequiredArgsConstructor public enum SmsPrefix { - REGISTER("register@"), - PASSWORD("password@"), - UID("uid@"); + REGISTER("register"), + PASSWORD("password"), + UID("uid"); private final String prefix; public String getTopic(String phoneNumber) { - return this.prefix + phoneNumber; + return this.prefix + '@' + phoneNumber; } @Override diff --git a/src/main/java/com/kcy/fitapet/global/common/response/SuccessResponse.java b/src/main/java/com/kcy/fitapet/global/common/response/SuccessResponse.java index 7d014b7a..8bcd4af3 100644 --- a/src/main/java/com/kcy/fitapet/global/common/response/SuccessResponse.java +++ b/src/main/java/com/kcy/fitapet/global/common/response/SuccessResponse.java @@ -34,6 +34,11 @@ private SuccessResponse(Map data) { */ public static SuccessResponse from(T data) { String key = getDtoName(data); + + if (data instanceof Map) { + key = ((Map) data).keySet().stream().findFirst().orElse(null).toString(); + data = (T) ((Map) data).get(key); + } if (key == null) key = "data"; return SuccessResponse.builder() diff --git a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java index 4688e690..9d788ae3 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java @@ -51,6 +51,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { "/api/v1/auth/refresh", "/api/v1/auth/register-sms/**", "/api/v1/auth/search-sms/**", "/api/v1/accounts/search", "/api/v1/accounts/search/**", + "/api/v1/accounts/exists", "/api/v1/accounts/exists/**", "/v3/api-docs/**", "/swagger-ui/**", "/swagger", "/favicon.ico" diff --git a/src/main/java/com/kcy/fitapet/global/common/util/sms/dto/SmsReq.java b/src/main/java/com/kcy/fitapet/global/common/util/sms/dto/SmsReq.java index 3e639f8d..e0b3a8a0 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/sms/dto/SmsReq.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/sms/dto/SmsReq.java @@ -13,6 +13,8 @@ public record SmsReq( @Schema(description = "수신번호") @NotNull(message = "수신번호는 필수 입력값입니다.") - String to + String to, + @Schema(description = "아이디") + String uid ) { } diff --git a/src/main/java/com/kcy/fitapet/global/config/security/SecurityConfig.java b/src/main/java/com/kcy/fitapet/global/config/security/SecurityConfig.java index d8c829ec..c267d7bb 100644 --- a/src/main/java/com/kcy/fitapet/global/config/security/SecurityConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/security/SecurityConfig.java @@ -39,9 +39,10 @@ public class SecurityConfig { // API "/api/v1/auth/register", "/api/v1/auth/login", "/api/v1/auth/refresh", "/api/v1/auth/register-sms/**", "/api/v1/auth/search-sms/**", - "/api/v1/accounts/search", "/api/v1/accounts/search/**" + "/api/v1/accounts/search", "/api/v1/accounts/search/**", }; private static final String[] publicReadOnlyPublicEndpoints = { + "/api/v1/accounts/exists", "/api/v1/accounts/exists/**" }; @Bean