diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 2ba7bd5..6a37c6b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -58,6 +58,7 @@ jobs: export JWT_SECRET_KEY='${{ secrets.JWT_SECRET_KEY }}' export ADMIN_SECRET='${{ secrets.ADMIN_SECRET }}' export KAKAO_APP_KEY='${{ secrets.KAKAO_APP_KEY }}' + export KAKAO_ADMIN_KEY='${{ secrets.KAKAO_ADMIN_KEY }}' export COOLSMS_API_KEY=${{ secrets.COOLSMS_API_KEY }} export COOLSMS_API_SECRET=${{ secrets.COOLSMS_API_SECRET }} diff --git a/docker-compose.yml b/docker-compose.yml index ff54f69..d7f90bd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: JWT_SECRET_KEY: ${JWT_SECRET_KEY} ADMIN_SECRET: ${ADMIN_SECRET} KAKAO_APP_KEY: ${KAKAO_APP_KEY} + KAKAO_ADMIN_KEY: ${KAKAO_ADMIN_KEY} COOLSMS_API_KEY: ${COOLSMS_API_KEY} COOLSMS_API_SECRET: ${COOLSMS_API_SECRET} COOLSMS_SENDER: ${COOLSMS_SENDER} diff --git a/src/main/java/com/meetkey/server/domain/auth/controller/AuthController.java b/src/main/java/com/meetkey/server/domain/auth/controller/AuthController.java index 8835b46..4f23c27 100644 --- a/src/main/java/com/meetkey/server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/meetkey/server/domain/auth/controller/AuthController.java @@ -86,6 +86,18 @@ public ResponseEntity> signup( .body(BasicResponse.success(CommonSuccessStatus._OK, jwts)); } + @Operation(summary = "회원 소프트 탈퇴 API") + @PostMapping("/withdraw") + public ResponseEntity> withdraw( + @RequestHeader(value = "refresh") String refreshToken, + @AuthenticationPrincipal CustomUserDetails details + ){ + Long memberId = details.getMemberId(); + authService.withdraw(memberId, refreshToken); + return ResponseEntity.ok() + .body(BasicResponse.success(CommonSuccessStatus._OK, null)); + } + @Operation(summary = "토큰 재발급 API", description = "access 토큰 기간 만료 시 refresh 토큰으로 재발급합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "성공", content = @Content(schema = @Schema(implementation = JwtResDTO.JwtResponse.class))), diff --git a/src/main/java/com/meetkey/server/domain/auth/service/AuthService.java b/src/main/java/com/meetkey/server/domain/auth/service/AuthService.java index 2dd9712..dc3b517 100644 --- a/src/main/java/com/meetkey/server/domain/auth/service/AuthService.java +++ b/src/main/java/com/meetkey/server/domain/auth/service/AuthService.java @@ -10,6 +10,7 @@ import com.meetkey.server.domain.member.entity.Member; import com.meetkey.server.domain.member.entity.SocialLogin; import com.meetkey.server.domain.member.enums.Provider; +import com.meetkey.server.domain.member.enums.Status; import com.meetkey.server.domain.member.repository.SocialLoginRepository; import com.meetkey.server.domain.member.service.MemberService; import com.meetkey.server.global.security.jwt.dto.JwtResDTO; @@ -21,8 +22,10 @@ import com.meetkey.server.domain.auth.exception.AuthErrorStatus; import com.meetkey.server.domain.auth.exception.AuthException; import com.meetkey.server.global.security.oauth.dto.OidcDTO; +import com.meetkey.server.global.security.oauth.kakao.KakaoApiClient; import com.meetkey.server.global.security.oauth.kakao.KakaoOauthClient; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,12 +35,16 @@ @Service @RequiredArgsConstructor +@Slf4j public class AuthService { + private final KakaoApiClient kakaoApiClient; @Value("${kakao.app-key}") private String kakaoAppKey; @Value(("{apple.app-key}")) private String appleAppKey; + @Value("${kakao.admin-key}") + private String kakaoAdminKey; private final KakaoOauthClient kakaoOauthClient; private final AppleOauthClient appleClient; @@ -47,7 +54,6 @@ public class AuthService { private final OauthOidcHelper oAuthOIDCHelper; private final JwtUtil jwtUtil; private final MemberService memberService; - private final BadgeService badgeService; private final SocialLoginRepository socialLoginRepository; private final RefreshTokenRepository refreshTokenRepository; @@ -85,19 +91,20 @@ else if (provider == Provider.APPLE) { } else throw new AuthException(AuthErrorStatus.INVALID_SOCIAL); - + Member member; MemberReqDTO.Signup memberReqDTO = OauthConverter.toMemberSignUpDTO(req); - // Member 생성 - Member member = memberService.signup(provider, providerId, memberReqDTO); - // 회원 가입시 자동적으로 뱃지 생성 - Badge initalBadge = Badge.builder() - .member(member) - .total_score(0) - .level(BadgeLevel.NONE) - .build(); + // Member 생성 + member = memberService.signup(provider, providerId, memberReqDTO); - badgeRepository.save(initalBadge); + // 회원 가입시 자동적으로 뱃지 생성 + Badge initalBadge = Badge.builder() + .member(member) + .total_score(0) + .level(BadgeLevel.NONE) + .build(); + + badgeRepository.save(initalBadge); // 밋키 서비스 토큰 발급 return getJwtResponseDTO(member); @@ -128,9 +135,32 @@ public JwtResDTO.JwtResponse login(Provider provider, String idToken){ // 존재하면 jwtToken 발급 Member member = socialMember.get().getMember(); + if (member.getStatus() == Status.INACTIVE){ + return JwtResDTO.JwtResponse.builder() + .accessToken(null) + .refreshToken(null) + .isNewMember(true) + .memberId(member.getId()) + .build(); + } return getJwtResponseDTO(member); } + @Transactional + public void withdraw(Long memberId, String refreshToken){ + Member member = memberService.findMemberById(memberId); + SocialLogin socialLogin = socialLoginRepository.findByMember(member) + .orElseThrow(() -> new AuthException(AuthErrorStatus.INVALID_SOCIAL)); + + log.info("해당 유저의 social ProviderId: " + socialLogin.getProviderId()); + + member.updateMemberStatus(Status.INACTIVE); // 소프트 탈퇴 처리 + member.updatePhoneNumber(""); + refreshTokenRepository.delete(refreshToken); + + kakaoApiClient.unlink("KakaoAK "+ kakaoAdminKey, "user_id", Long.parseLong(socialLogin.getProviderId())); + } + @Transactional public JwtResDTO.JwtResponse reissue(String refreshToken){ jwtUtil.isValid(refreshToken, false); diff --git a/src/main/java/com/meetkey/server/domain/badge/entity/Badge.java b/src/main/java/com/meetkey/server/domain/badge/entity/Badge.java index 0e08395..c335c38 100644 --- a/src/main/java/com/meetkey/server/domain/badge/entity/Badge.java +++ b/src/main/java/com/meetkey/server/domain/badge/entity/Badge.java @@ -31,6 +31,4 @@ public class Badge extends BaseEntity { public void addScore(int score) { this.total_score += score; } - - } diff --git a/src/main/java/com/meetkey/server/domain/member/entity/Member.java b/src/main/java/com/meetkey/server/domain/member/entity/Member.java index d37f0be..6025f7b 100644 --- a/src/main/java/com/meetkey/server/domain/member/entity/Member.java +++ b/src/main/java/com/meetkey/server/domain/member/entity/Member.java @@ -113,6 +113,29 @@ public void updateMemberShip() { this.membership = Membership.FREE; } } + public void updateName(String name){ + this.name = name; + } + + public void updateGender(Gender gender){ + this.gender = gender; + } + + public void updateBirthday(LocalDate birthday){ + this.birthday = birthday; + } + + public void updateHomeTown(HomeTown homeTown){ + this.homeTown = homeTown; + } + + public void updatePhoneNumber(String phoneNumber){ + this.phoneNumber = phoneNumber; + } + + public void updateMemberStatus(Status status){ + this.status = status; + } public void increaseRecommend() { this.recommendCount++; diff --git a/src/main/java/com/meetkey/server/domain/member/repository/SocialLoginRepository.java b/src/main/java/com/meetkey/server/domain/member/repository/SocialLoginRepository.java index 6208ec0..2a02d30 100644 --- a/src/main/java/com/meetkey/server/domain/member/repository/SocialLoginRepository.java +++ b/src/main/java/com/meetkey/server/domain/member/repository/SocialLoginRepository.java @@ -1,5 +1,6 @@ package com.meetkey.server.domain.member.repository; +import com.meetkey.server.domain.member.entity.Member; import com.meetkey.server.domain.member.entity.SocialLogin; import com.meetkey.server.domain.member.enums.Provider; import org.springframework.data.jpa.repository.JpaRepository; @@ -13,4 +14,6 @@ public interface SocialLoginRepository extends JpaRepository "WHERE s.provider = :provider AND s.providerId = :providerId") Optional findByProviderAndProviderId( @Param("provider") Provider provider, @Param("providerId") String providerId); + + Optional findByMember(Member member); } diff --git a/src/main/java/com/meetkey/server/domain/member/service/MemberService.java b/src/main/java/com/meetkey/server/domain/member/service/MemberService.java index 134b9b8..84d5165 100644 --- a/src/main/java/com/meetkey/server/domain/member/service/MemberService.java +++ b/src/main/java/com/meetkey/server/domain/member/service/MemberService.java @@ -10,6 +10,7 @@ import com.meetkey.server.domain.member.entity.mapping.MemberBlock; import com.meetkey.server.domain.member.enums.Provider; import com.meetkey.server.domain.member.enums.Role; +import com.meetkey.server.domain.member.enums.Status; import com.meetkey.server.domain.member.exception.MemberErrorStatus; import com.meetkey.server.domain.member.exception.MemberException; import com.meetkey.server.domain.member.repository.MemberBlockRepository; diff --git a/src/main/java/com/meetkey/server/global/security/oauth/kakao/KakaoApiClient.java b/src/main/java/com/meetkey/server/global/security/oauth/kakao/KakaoApiClient.java new file mode 100644 index 0000000..4d4db5a --- /dev/null +++ b/src/main/java/com/meetkey/server/global/security/oauth/kakao/KakaoApiClient.java @@ -0,0 +1,22 @@ +package com.meetkey.server.global.security.oauth.kakao; + +import com.meetkey.server.global.config.FeignConfig; +import com.meetkey.server.global.config.OauthConfig; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient( + name = "KakaoApiClient", + url = "https://kapi.kakao.com", + configuration = {OauthConfig.class, FeignConfig.class} +) +public interface KakaoApiClient { + @PostMapping("/v1/user/unlink") + void unlink( + @RequestHeader("Authorization") String adminKey, + @RequestParam("target_id_type") String targetIdType, + @RequestParam("target_id") Long targetId + ); +} \ No newline at end of file diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index e8d6094..91be29c 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -34,6 +34,7 @@ admin: kakao: app-key: ${KAKAO_APP_KEY:} map-api-key: ${KAKAO_MAP_API_KEY:} + admin-key: ${KAKAO_ADMIN_KEY:} apple: app-key: ${APPLE_APP_KEY:} diff --git a/src/main/resources/application-prod.yaml b/src/main/resources/application-prod.yaml index 07e0654..831debf 100644 --- a/src/main/resources/application-prod.yaml +++ b/src/main/resources/application-prod.yaml @@ -34,6 +34,7 @@ admin: kakao: app-key: ${KAKAO_APP_KEY:} map-api-key: ${KAKAO_MAP_API_KEY:} + admin-key: ${KAKAO_ADMIN_KEY:} apple: app-key: ${APPLE_APP_KEY:} \ No newline at end of file diff --git a/src/main/resources/application-test.yaml b/src/main/resources/application-test.yaml index 6d107dd..b2610e6 100644 --- a/src/main/resources/application-test.yaml +++ b/src/main/resources/application-test.yaml @@ -37,6 +37,7 @@ admin: kakao: app-key: ${KAKAO_APP_KEY:} + admin-key: ${KAKAO_ADMIN_KEY:} map-api-key: ${KAKAO_MAP_API_KEY:} apple: diff --git a/src/main/resources/static/chat-test.html b/src/main/resources/static/chat-test.html index 0900100..51aa123 100644 --- a/src/main/resources/static/chat-test.html +++ b/src/main/resources/static/chat-test.html @@ -90,7 +90,7 @@

📨 Messages

} function connect() { - const socket = new SockJS("http://localhost:8080/ws-chat"); + const socket = new SockJS("https://meetkey.test-route53.shop/ws-chat"); stompClient = Stomp.over(socket); const token = document.getElementById("token").value;