Skip to content

Commit

Permalink
[refactor] : login된 유저 조회 api가 email을 함께 반환하도록 수정 (#401)
Browse files Browse the repository at this point in the history
  • Loading branch information
devxb authored Mar 8, 2024
1 parent b7802fe commit 1ffe225
Show file tree
Hide file tree
Showing 22 changed files with 154 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import me.nalab.core.data.user.UserOAuthInfoEntity;
import me.nalab.core.idgenerator.idcore.IdGenerator;
import me.nalab.user.domain.user.Provider;
import me.nalab.user.domain.user.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -22,7 +23,7 @@ public class UserInitializer {
private IdGenerator idGenerator;

@Transactional
public void saveUserWithOAuth(Provider provider, String name, String email, Instant date) {
public Long saveUserWithOAuth(Provider provider, String name, String email, Instant date) {
var userEntity = UserEntity.builder()
.id(idGenerator.generate())
.nickname(name)
Expand All @@ -42,6 +43,7 @@ public void saveUserWithOAuth(Provider provider, String name, String email, Inst

entityManager.persist(userEntity);
entityManager.persist(userOauthInfoEntity);
return userEntity.getId();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class UserAcceptanceTestSupporter {

protected ResultActions getLoginedUser(String token) throws Exception {
return mockMvc.perform(MockMvcRequestBuilders
.get(API_VERSION + "/users/logined")
.get(API_VERSION + "/users/logins")
.accept(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, token)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

public class UserAcceptanceValidator {

public static void assertIsLogined(ResultActions resultActions, Long targetId, String nickname) throws Exception {
public static void assertIsLogined(ResultActions resultActions, Long targetId, String nickname, String email) throws Exception {
resultActions.andExpectAll(
status().isOk(),
jsonPath("$.target_id").value(targetId),
jsonPath("$.nickname").value(nickname)
jsonPath("$.nickname").value(nickname),
jsonPath("$.email").value(email)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.time.Instant;
import java.util.Set;

import me.nalab.luffy.api.acceptance.test.UserInitializer;
import me.nalab.user.domain.user.Provider;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -40,22 +42,27 @@ class LoginedTargetFindAcceptanceTest extends UserAcceptanceTestSupporter {
@Autowired
private JwtUtils jwtUtils;

@Autowired
private UserInitializer userInitializer;

@Test
@DisplayName("로그인된 유저 정보 조회 성공 테스트")
void GET_LOGINED_USER_SUCCESS() throws Exception {
// given
String nickname = "devxb";
String email = "email";
Long userId = userInitializer.saveUserWithOAuth(Provider.DEFAULT, nickname, "email", Instant.now());
Long targetId = targetInitializer.saveTargetAndGetId(nickname, Instant.now());
String token = jwtUtils.createAccessToken(Set.of(new Payload(Payload.Key.NICKNAME, nickname),
new Payload(Payload.Key.USER_ID, 12345 + ""), new Payload(Payload.Key.TARGET_ID, targetId + "")));
String token = jwtUtils.createAccessToken(Set.of(new Payload(Payload.Key.USER_ID, String.valueOf(userId)),
new Payload(Payload.Key.TARGET_ID, String.valueOf(targetId))));
applicationEventPublisher.publishEvent(
MockUserRegisterEvent.builder().expectedToken("bearer " + token).expectedId(targetId).build());

// when
ResultActions resultActions = getLoginedUser("bearer " + token);

// then
assertIsLogined(resultActions, targetId, nickname);
assertIsLogined(resultActions, targetId, nickname, email);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
@Data
public class CreateAuthTokenRequest {
private final String userId;
private final String nickname;
private final String targetId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ public class Payload {
public enum Key {
USER_ID,
TARGET_ID,
NICKNAME,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@ public class AuthTokenCreateService implements AuthTokenCreateUseCase {
@Override
public AuthToken create(CreateAuthTokenRequest request) {
var userId = request.getUserId();
var nickname = request.getNickname();
var targetId = request.getTargetId();
Assert.isTrue(userId != null && !userId.isBlank(), "Authentication token 생성 시 유저 식별자는 필수입니다.");
Assert.isTrue(nickname != null && !nickname.isBlank(), "Authentication token 생성 시 유저의 별명은 필수입니다.");
Assert.isTrue(targetId != null && !targetId.isBlank(), "Authentication token 생성 시 유저의 별명은 필수입니다.");

Set<Payload> payload = new HashSet<>();
payload.add(new Payload(Payload.Key.USER_ID, userId));
payload.add(new Payload(Payload.Key.NICKNAME, nickname));
payload.add(new Payload(Payload.Key.TARGET_ID, targetId));

String token = createToken(payload);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import me.nalab.auth.application.common.dto.Payload;
import me.nalab.auth.application.common.utils.JwtUtils;
import me.nalab.auth.application.port.in.web.TargetIdGetPort;
import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.common.dto.TokenInfo;
import me.nalab.user.application.port.out.persistence.LoginedUserGetByTokenPort;

@Service
Expand All @@ -19,14 +19,13 @@ public class JwtLoginedDecryptService implements LoginedUserGetByTokenPort, Targ
private final JwtUtils jwtUtils;

@Override
public LoginedInfo decryptToken(String encryptedToken) {
public TokenInfo decryptToken(String encryptedToken) {
Assert.isTrue(encryptedToken != null && !encryptedToken.isBlank(),
"encryptedToken 으로 blank나 null 값이 들어올 수 없습니다.");
DecodedJWT decodedJWT = jwtUtils.verify(encryptedToken);
String nickName = decodedJWT.getClaim(Payload.Key.NICKNAME.name()).asString();
Long userId = Long.valueOf(decodedJWT.getClaim(Payload.Key.USER_ID.name()).asString());
Long targetId = Long.valueOf(decodedJWT.getClaim(Payload.Key.TARGET_ID.name()).asString());
return new LoginedInfo(nickName, targetId, userId);
return new TokenInfo(targetId, userId);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public AuthToken signInWithOAuth(SignInWithOAuthRequest request) {
var userId = foundUser.orElseThrow(IllegalAccessError::new);
var targetId = targetFindByUsernameUseCase.findTargetByUsername(request.getUsername()).orElseThrow().getId();

var authTokenCreateRequest = new CreateAuthTokenRequest(userId.toString(), request.getUsername(), String.valueOf(targetId));
var authTokenCreateRequest = new CreateAuthTokenRequest(userId.toString(), String.valueOf(targetId));

return authTokenCreateUseCase.create(authTokenCreateRequest);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,8 @@ class AuthTokenCreateServiceTest {
@DisplayName("유저식별자가 없거나 비어있다면 예외를 발생시킨다")
void THROW_EXCEPTION_WHEN_USER_ID_IS_BLANK(String userId) {
// given
var nickname = "nickname";
var targetId = "targetId";
var request = new CreateAuthTokenRequest(userId, nickname, targetId);

// when
var throwable = Assertions.catchThrowable(() -> authTokenCreateService.create(request));

// then
Assertions.assertThat(throwable).isInstanceOf(IllegalArgumentException.class);
}

@ParameterizedTest
@NullAndEmptySource()
@DisplayName("닉네임가 없거나 비어있다면 예외를 발생시킨다")
void THROW_EXCEPTION_WHEN_NICKNAME_IS_BLANK(String nickname) {
// given
var userId = "userId";
var targetId = "targetId";
var request = new CreateAuthTokenRequest(userId, nickname, targetId);
var request = new CreateAuthTokenRequest(userId, targetId);

// when
var throwable = Assertions.catchThrowable(() -> authTokenCreateService.create(request));
Expand All @@ -64,8 +47,7 @@ void THROW_EXCEPTION_WHEN_NICKNAME_IS_BLANK(String nickname) {
void THROW_EXCEPTION_WHEN_TARGET_ID_IS_BLANK(String targetId) {
// given
var userId = "userId";
var nickname = "nickname";
var request = new CreateAuthTokenRequest(userId, nickname, targetId);
var request = new CreateAuthTokenRequest(userId, targetId);

// when
var throwable = Assertions.catchThrowable(() -> authTokenCreateService.create(request));
Expand All @@ -79,9 +61,8 @@ void THROW_EXCEPTION_WHEN_TARGET_ID_IS_BLANK(String targetId) {
void RETURN_TOKEN_WHEN_VALID_REQUEST() {
// given
var userId = "userId";
var nickname = "nickname";
var targetId = "targetId";
var request = new CreateAuthTokenRequest(userId, nickname, targetId);
var request = new CreateAuthTokenRequest(userId, targetId);
var expectedToken = "token";

when(jwtUtils.createAccessToken(any())).thenReturn(expectedToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import me.nalab.auth.application.common.dto.Payload;
import me.nalab.auth.application.common.property.JwtProperties;
import me.nalab.auth.application.common.utils.JwtUtils;
import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.common.dto.TokenInfo;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {JwtLoginedDecryptService.class, JwtUtils.class, JwtProperties.class})
Expand All @@ -38,11 +38,11 @@ void JWT_DECRYPT_SUCCESS() {
long userId = 12345;
long targetId = 54321;
String requestToken = jwtUtils.createAccessToken(
Set.of(new Payload(Payload.Key.NICKNAME, nickName), new Payload(Payload.Key.USER_ID, userId + ""),
Set.of(new Payload(Payload.Key.USER_ID, userId + ""),
new Payload(Payload.Key.TARGET_ID, targetId + "")));

// when
LoginedInfo response = jwtLoginedDecryptService.decryptToken(requestToken);
TokenInfo response = jwtLoginedDecryptService.decryptToken(requestToken);

// then
assertDecryptedInfo(response, nickName, userId, targetId);
Expand All @@ -59,9 +59,8 @@ void THROW_EXCEPTION_WHEN_TOKEN_IS_NULL_OR_EMPTY(String token) {
assertThat(throwable).isInstanceOf(IllegalArgumentException.class);
}

private void assertDecryptedInfo(LoginedInfo response, String expectedName, Long expectedUserId,
private void assertDecryptedInfo(TokenInfo response, String expectedName, Long expectedUserId,
Long expectedTargetId) {
Assertions.assertEquals(response.getNickName(), expectedName);
Assertions.assertEquals(response.getUserId(), expectedUserId);
Assertions.assertEquals(response.getTargetId(), expectedTargetId);
}
Expand All @@ -74,7 +73,6 @@ void DECRYPTE_TARGET_ID_SUCCESS() {
long userId = 12345;
long targetId = 54321;
String requestToken = jwtUtils.createAccessToken(Set.of(
new Payload(Payload.Key.NICKNAME, nickName),
new Payload(Payload.Key.USER_ID, String.valueOf(userId)),
new Payload(Payload.Key.TARGET_ID, String.valueOf(targetId))
));
Expand Down
29 changes: 29 additions & 0 deletions support/e2e/v1_13_get_logined_target.hurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
POST http://nalab-server:8080/v1/oauth/default # Default provider를 통해서 로그인 진행
{
"nickname": "devxb",
"email": "get_logined_target@naver.com"
}

HTTP 200
[Asserts]
header "Content-type" == "application/json"

jsonpath "$.access_token" exists
jsonpath "$.token_type" exists

[Captures]
token_type: jsonpath "$.token_type"
auth_token: jsonpath "$.access_token"

#######

GET http://nalab-server:8080/v1/users/logins # Token에 해당하는 유저 조회
Authorization: {{ token_type }} {{ auth_token }}

HTTP 200
[Asserts]
header "Content-type" == "application/json"

jsonpath "$.target_id" exists
jsonpath "$.nickname" == "devxb"
jsonpath "$.email" == "get_logined_target@naver.com"
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package me.nalab.user.application.common.dto;

import lombok.Data;

@Data
public class LoginedInfo {

private final String nickName;
private final Long targetId;
private final Long userId;

import me.nalab.user.domain.user.User;

public record LoginedInfo(
Long id,
Long targetId,
String nickname,
String email
) {

public static LoginedInfo from(Long targetId, User user) {
return new LoginedInfo(user.getId(), targetId, user.getNickname(), user.getEmail());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package me.nalab.user.application.common.dto;

import lombok.Data;

@Data
public class TokenInfo {

private final Long targetId;
private final Long userId;

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public interface LoginedUserGetByTokenUseCase {
* @param encryptedToken 암호화된 토큰
* @return 복호화된 정보
*/
LoginedInfo decryptToken(String encryptedToken);
LoginedInfo getLoginedInfoByToken(String encryptedToken);

}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package me.nalab.user.application.port.out.persistence;

import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.common.dto.TokenInfo;

/**
* token을 받아 decrypt된 유저의 정보를 반환하는 유즈케이스
*/
public interface LoginedUserGetByTokenPort {

/**
* 암호화된 유저의 토큰을 받아, 복호화된 유저의 정보를 반환합니다.
* 암호화된 유저의 토큰을 받아, 복호화된 토큰의 정보를 반환합니다.
*
* @param encryptedToken 암호화된 토큰
* @return 복호화된 정보
*/
LoginedInfo decryptToken(String encryptedToken);
TokenInfo decryptToken(String encryptedToken);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package me.nalab.user.application.port.out.persistence;

import me.nalab.user.domain.user.User;

public interface UserGetPort {

User getById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,32 @@

import java.util.Objects;

import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.port.out.persistence.UserGetPort;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import me.nalab.user.application.common.dto.LoginedInfo;
import me.nalab.user.application.exception.InvalidTokenException;
import me.nalab.user.application.port.in.LoginedUserGetByTokenUseCase;
import me.nalab.user.application.port.out.persistence.LoginedUserGetByTokenPort;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class LoginedUserGetByTokenService implements LoginedUserGetByTokenUseCase {

private final LoginedUserGetByTokenPort loginedUserGetByTokenPort;
private final UserGetPort userGetPort;

@Override
public LoginedInfo decryptToken(String encryptedToken) {
@Transactional(readOnly = true)
public LoginedInfo getLoginedInfoByToken(String encryptedToken) {
Objects.requireNonNull(encryptedToken, "encryptedToken은 null이 되면 안됩니다.");
String[] split = encryptedToken.split(" ");
throwIfInvalidToken(split);
return loginedUserGetByTokenPort.decryptToken(split[1]);
var tokenInfo = loginedUserGetByTokenPort.decryptToken(split[1]);
var user = userGetPort.getById(tokenInfo.getUserId());
return LoginedInfo.from(tokenInfo.getTargetId(), user);
}

private void throwIfInvalidToken(String[] split) {
Expand Down
Loading

0 comments on commit 1ffe225

Please sign in to comment.