Skip to content

Commit

Permalink
Merge pull request #67 from YAPP-Github/feat/PC-632-withdraw-apple
Browse files Browse the repository at this point in the history
[PC-632] 애플 회원 탈퇴
  • Loading branch information
Lujaec authored Feb 21, 2025
2 parents d516d97 + b81efa2 commit 06970da
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.yapp.domain.auth.application.oauth;

public interface OauthClient {

String getOAuthProviderUserId(String accessToken);

void unlink(String accessToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import org.yapp.domain.auth.domain.enums.OauthType;

public interface OauthProvider {

OauthType getOauthType();

OauthClient getOAuthClient();

default boolean match(String providerName){
default boolean match(String providerName) {
return providerName.equals(getOauthType().getTypeName());
}

Expand All @@ -15,4 +17,8 @@ default String getOAuthProviderUserId(String accessToken) {
return client.getOAuthProviderUserId(accessToken);
}

default void unlink(String accessToken) {
OauthClient client = getOAuthClient();
client.unlink(accessToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,38 @@ public String getOAuthProviderUserId(String authorizationCode) {
throw new ApplicationException(AuthErrorCode.OAUTH_ID_NOT_FOUND);
}
}

@Override
public void unlink(String authorizationCode) {
final String url = "https://appleid.apple.com/auth/revoke";

String clientSecret = appleOauthHelper.generateClientSecret();
AppleAuthResponse appleAuthResponse = this.getAppleAuthResponse(
appleOauthProperties.getClientId(),
clientSecret,
"authorization_code",
authorizationCode);

LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("client_id", appleOauthProperties.getClientId());
params.add("client_secret", clientSecret);
params.add("token", appleAuthResponse.getAccessToken());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);

try {
ResponseEntity<String> response = restTemplate.postForEntity(
url, entity, String.class
);

if (response.getStatusCode().is2xxSuccessful()) {
return;
} else {
throw new ApplicationException(AuthErrorCode.OAUTH_ERROR);
}
} catch (Exception e) {
throw new ApplicationException(AuthErrorCode.OAUTH_ERROR);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,12 @@ public String getOAuthProviderUserId(String token) {
}
return googleIdToken.getPayload().getSubject();
}

@Override
public void unlink(String accessToken) {
/*
TODO: 구글 소셜 unlink 로직 필요
*/
return;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
@Component
@RequiredArgsConstructor
public class KakaoOauthClient implements OauthClient {

@Value("${oauth.kakaoUserInfoUri}")
private String userInfoUri;
private final RestTemplate restTemplate;
Expand All @@ -26,10 +27,18 @@ public String getOAuthProviderUserId(String accessToken) {
HttpEntity<Object> entity = new HttpEntity<>(headers);

ResponseEntity<JsonNode> response = restTemplate.exchange(
userInfoUri, HttpMethod.GET, entity, JsonNode.class);
userInfoUri, HttpMethod.GET, entity, JsonNode.class);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody().get("id").asText();
}
throw new RuntimeException();
}

@Override
public void unlink(String accessToken) {
/*
TODO: 카카오 소셜 unlink 로직 필요
*/
return;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.yapp.core.auth.AuthToken;
import org.yapp.core.auth.AuthTokenGenerator;
import org.yapp.core.auth.token.RefreshTokenService;
Expand All @@ -13,14 +14,17 @@
import org.yapp.domain.auth.presentation.dto.request.OauthLoginRequest;
import org.yapp.domain.auth.presentation.dto.response.OauthLoginResponse;
import org.yapp.domain.setting.application.SettingService;
import org.yapp.domain.user.application.UserService;
import org.yapp.domain.user.dao.UserRepository;
import org.yapp.domain.user.presentation.dto.request.OauthUserDeleteRequest;

@Service
@RequiredArgsConstructor
public class OauthService {

private final OauthProviderResolver oauthProviderResolver;
private final UserRepository userRepository;
private final UserService userService;
private final RefreshTokenService refreshTokenService;
private final AuthTokenGenerator authTokenGenerator;
private final SettingService settingService;
Expand Down Expand Up @@ -76,6 +80,12 @@ public OauthLoginResponse login(OauthLoginRequest request) {
return new OauthLoginResponse(user.getRole(), accessToken, refreshToken);
}

@Transactional
public void withdraw(OauthUserDeleteRequest request, Long userId) {
userService.deleteUser(userId, request.getReason());
OauthProvider oauthProvider = oauthProviderResolver.find(request.getProviderName());
oauthProvider.unlink(request.getOauthCredential());
}

public OauthLoginResponse tmpTokenGet(Long userId) {
//이미 가입된 유저인지 확인하고 가입되어 있지 않으면 회원가입 처리
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.yapp.domain.auth.application.oauth.service.OauthService;
import org.yapp.domain.user.application.UserService;
import org.yapp.domain.user.presentation.dto.request.OauthUserDeleteRequest;
import org.yapp.domain.user.presentation.dto.request.UserDeleteRequest;
import org.yapp.domain.user.presentation.dto.response.UserRejectHistoryResponse;
import org.yapp.format.CommonResponse;
Expand All @@ -22,27 +24,39 @@
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

@PreAuthorize(value = "hasRole('USER')")
@GetMapping("/reject")
@Operation(summary = "사용자 거절 사유 조회", description = "사용자의 최근 거절 사유를 조회합니다.", tags = {"사용자"})
public ResponseEntity<CommonResponse<UserRejectHistoryResponse>> getUserRejectHistory(
@AuthenticationPrincipal Long userId) {
UserRejectHistoryResponse response = userService.getUserRejectHistoryLatest(
userId);

return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.createSuccess(response));
}

@DeleteMapping
@PreAuthorize(value = "hasAnyAuthority('REGISTER','PENDING','USER')")
@Operation(summary = "회원 탈퇴", description = "회원 탈퇴합니다.", tags = {"사용자"})
public ResponseEntity<CommonResponse<Void>> deleteUser(
@RequestBody @Valid UserDeleteRequest request,
@AuthenticationPrincipal Long userId) {
userService.deleteUser(userId, request.getReason());
return ResponseEntity.ok(CommonResponse.createSuccessWithNoContent());
}
private final UserService userService;
private final OauthService oauthService;

@GetMapping("/reject")
@PreAuthorize(value = "hasAnyAuthority('PENDING')")
@Operation(summary = "사용자 거절 사유 조회", description = "사용자의 최근 거절 사유를 조회합니다.", tags = {"사용자"})
public ResponseEntity<CommonResponse<UserRejectHistoryResponse>> getUserRejectHistory(
@AuthenticationPrincipal Long userId) {
UserRejectHistoryResponse response = userService.getUserRejectHistoryLatest(
userId);

return ResponseEntity.status(HttpStatus.OK)
.body(CommonResponse.createSuccess(response));
}

@DeleteMapping
@PreAuthorize(value = "hasAnyAuthority('REGISTER','PENDING','USER')")
@Operation(summary = "회원 탈퇴", description = "회원 탈퇴합니다.", tags = {"사용자"})
public ResponseEntity<CommonResponse<Void>> deleteUser(
@RequestBody @Valid UserDeleteRequest request,
@AuthenticationPrincipal Long userId) {
userService.deleteUser(userId, request.getReason());
return ResponseEntity.ok(CommonResponse.createSuccessWithNoContent());
}

@DeleteMapping("/oauth")
@PreAuthorize(value = "hasAnyAuthority('REGISTER','PENDING','USER')")
@Operation(summary = "회원 탈퇴", description = "회원 탈퇴합니다.", tags = {"사용자"})
public ResponseEntity<CommonResponse<Void>> deleteUser(
@RequestBody @Valid OauthUserDeleteRequest request,
@AuthenticationPrincipal Long userId) {

oauthService.withdraw(request, userId);
return ResponseEntity.ok(CommonResponse.createSuccessWithNoContent());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.yapp.domain.user.presentation.dto.request;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class OauthUserDeleteRequest {

@NotNull(message = "providerName은 null일 수 없습니다.")
@Pattern(
regexp = "^(apple|kakako|google)$",
message = "providerName은 apple, kakako, google 중 하나여야 합니다."
)
private String providerName;
private String oauthCredential;
@Size(max = 100, message = "탈퇴 이유는 100자 이하여야합니다.")
private String reason;
}
48 changes: 24 additions & 24 deletions core/domain/src/main/java/org/yapp/core/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,37 @@
@NoArgsConstructor
public class User extends BaseEntity {

@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "oauth_id")
private String oauthId;
@Column(name = "oauth_id")
private String oauthId;

@Column(name = "name")
private String name;
@Column(name = "name")
private String name;

@Column(name = "phone")
private String phoneNumber;
@Column(name = "phone")
private String phoneNumber;

@Column(name = "role")
private String role;
@Column(name = "role")
private String role;

@OneToOne(cascade = {CascadeType.REMOVE})
@JoinColumn(name = "profile_id", unique = true) // User가 profile_id를 FK로 가짐
private Profile profile;
@OneToOne(cascade = {CascadeType.REMOVE})
@JoinColumn(name = "profile_id", unique = true) // User가 profile_id를 FK로 가짐
private Profile profile;

public void initializePhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public void initializePhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}

public void setProfile(Profile profile) {
this.profile = profile;
}
public void setProfile(Profile profile) {
this.profile = profile;
}

public void updateUserRole(String role) {
this.role = role;
}
public void updateUserRole(String role) {
this.role = role;
}
}

0 comments on commit 06970da

Please sign in to comment.