Enable dual-device login and mobile APK update checks
This commit is contained in:
@@ -120,7 +120,7 @@ class AuthControllerValidationTest {
|
||||
"new-refresh-token",
|
||||
new UserProfileResponse(7L, "alice", "alice@example.com", LocalDateTime.now())
|
||||
);
|
||||
when(authService.refresh("refresh-1")).thenReturn(response);
|
||||
when(authService.refresh("refresh-1", AuthClientType.DESKTOP)).thenReturn(response);
|
||||
|
||||
mockMvc.perform(post("/api/auth/refresh")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
@@ -134,6 +134,6 @@ class AuthControllerValidationTest {
|
||||
.andExpect(jsonPath("$.data.refreshToken").value("new-refresh-token"))
|
||||
.andExpect(jsonPath("$.data.user.username").value("alice"));
|
||||
|
||||
verify(authService).refresh("refresh-1");
|
||||
verify(authService).refresh("refresh-1", AuthClientType.DESKTOP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@ class AuthServiceTest {
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
return user;
|
||||
});
|
||||
when(jwtTokenProvider.generateAccessToken(eq(1L), eq("alice"), anyString())).thenReturn("access-token");
|
||||
when(refreshTokenService.issueRefreshToken(any(User.class))).thenReturn("refresh-token");
|
||||
when(jwtTokenProvider.generateAccessToken(eq(1L), eq("alice"), anyString(), eq(AuthClientType.DESKTOP))).thenReturn("access-token");
|
||||
when(refreshTokenService.issueRefreshToken(any(User.class), eq(AuthClientType.DESKTOP))).thenReturn("refresh-token");
|
||||
|
||||
AuthResponse response = authService.register(request);
|
||||
|
||||
@@ -166,8 +166,8 @@ class AuthServiceTest {
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
when(userRepository.findByUsername("alice")).thenReturn(Optional.of(user));
|
||||
when(userRepository.save(user)).thenReturn(user);
|
||||
when(jwtTokenProvider.generateAccessToken(eq(1L), eq("alice"), anyString())).thenReturn("access-token");
|
||||
when(refreshTokenService.issueRefreshToken(user)).thenReturn("refresh-token");
|
||||
when(jwtTokenProvider.generateAccessToken(eq(1L), eq("alice"), anyString(), eq(AuthClientType.DESKTOP))).thenReturn("access-token");
|
||||
when(refreshTokenService.issueRefreshToken(user, AuthClientType.DESKTOP)).thenReturn("refresh-token");
|
||||
|
||||
AuthResponse response = authService.login(request);
|
||||
|
||||
@@ -188,9 +188,9 @@ class AuthServiceTest {
|
||||
user.setEmail("alice@example.com");
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
when(refreshTokenService.rotateRefreshToken("old-refresh"))
|
||||
.thenReturn(new RefreshTokenService.RotatedRefreshToken(user, "new-refresh"));
|
||||
.thenReturn(new RefreshTokenService.RotatedRefreshToken(user, "new-refresh", AuthClientType.DESKTOP));
|
||||
when(userRepository.save(user)).thenReturn(user);
|
||||
when(jwtTokenProvider.generateAccessToken(eq(1L), eq("alice"), anyString())).thenReturn("new-access");
|
||||
when(jwtTokenProvider.generateAccessToken(eq(1L), eq("alice"), anyString(), eq(AuthClientType.DESKTOP))).thenReturn("new-access");
|
||||
|
||||
AuthResponse response = authService.refresh("old-refresh");
|
||||
|
||||
@@ -232,8 +232,8 @@ class AuthServiceTest {
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
return user;
|
||||
});
|
||||
when(jwtTokenProvider.generateAccessToken(eq(9L), eq("demo"), anyString())).thenReturn("access-token");
|
||||
when(refreshTokenService.issueRefreshToken(any(User.class))).thenReturn("refresh-token");
|
||||
when(jwtTokenProvider.generateAccessToken(eq(9L), eq("demo"), anyString(), eq(AuthClientType.DESKTOP))).thenReturn("access-token");
|
||||
when(refreshTokenService.issueRefreshToken(any(User.class), eq(AuthClientType.DESKTOP))).thenReturn("refresh-token");
|
||||
|
||||
AuthResponse response = authService.devLogin("demo");
|
||||
|
||||
@@ -296,7 +296,7 @@ class AuthServiceTest {
|
||||
when(passwordEncoder.matches("OldPass1!", "encoded-old")).thenReturn(true);
|
||||
when(passwordEncoder.encode("NewPass1!A")).thenReturn("encoded-new");
|
||||
when(userRepository.save(user)).thenReturn(user);
|
||||
when(jwtTokenProvider.generateAccessToken(eq(1L), eq("alice"), anyString())).thenReturn("new-access");
|
||||
when(jwtTokenProvider.generateAccessToken(eq(1L), eq("alice"), anyString(), eq(AuthClientType.DESKTOP))).thenReturn("new-access");
|
||||
when(refreshTokenService.issueRefreshToken(user)).thenReturn("new-refresh");
|
||||
|
||||
AuthResponse response = authService.changePassword("alice", request);
|
||||
|
||||
@@ -38,16 +38,78 @@ class AuthSingleDeviceIntegrationTest {
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private com.yoyuzh.files.StoredFileRepository storedFileRepository;
|
||||
|
||||
@Autowired
|
||||
private RefreshTokenRepository refreshTokenRepository;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
storedFileRepository.deleteAll();
|
||||
refreshTokenRepository.deleteAll();
|
||||
userRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInvalidatePreviousAccessTokenAfterLoggingInAgain() throws Exception {
|
||||
void shouldKeepDesktopAndMobileAccessTokensValidAtTheSameTime() throws Exception {
|
||||
User user = new User();
|
||||
user.setUsername("alice");
|
||||
user.setDisplayName("Alice");
|
||||
user.setEmail("alice@example.com");
|
||||
user.setPhoneNumber("13800138000");
|
||||
user.setPasswordHash(passwordEncoder.encode("StrongPass1!"));
|
||||
user.setPreferredLanguage("zh-CN");
|
||||
user.setRole(UserRole.USER);
|
||||
user.setCreatedAt(LocalDateTime.now());
|
||||
userRepository.save(user);
|
||||
|
||||
String loginRequest = """
|
||||
{
|
||||
"username": "alice",
|
||||
"password": "StrongPass1!"
|
||||
}
|
||||
""";
|
||||
|
||||
String desktopLoginResponse = mockMvc.perform(post("/api/auth/login")
|
||||
.contentType("application/json")
|
||||
.header("X-Yoyuzh-Client", "desktop")
|
||||
.content(loginRequest))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.accessToken").isNotEmpty())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
String mobileLoginResponse = mockMvc.perform(post("/api/auth/login")
|
||||
.contentType("application/json")
|
||||
.header("X-Yoyuzh-Client", "mobile")
|
||||
.content(loginRequest))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.accessToken").isNotEmpty())
|
||||
.andReturn()
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
String desktopAccessToken = JsonPath.read(desktopLoginResponse, "$.data.accessToken");
|
||||
String mobileAccessToken = JsonPath.read(mobileLoginResponse, "$.data.accessToken");
|
||||
|
||||
mockMvc.perform(get("/api/user/profile")
|
||||
.header("Authorization", "Bearer " + desktopAccessToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.username").value("alice"));
|
||||
|
||||
mockMvc.perform(get("/api/user/profile")
|
||||
.header("Authorization", "Bearer " + mobileAccessToken))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.username").value("alice"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInvalidatePreviousAccessTokenAfterLoggingInAgainOnTheSameClientType() throws Exception {
|
||||
User user = new User();
|
||||
user.setUsername("alice");
|
||||
user.setDisplayName("Alice");
|
||||
@@ -68,6 +130,7 @@ class AuthSingleDeviceIntegrationTest {
|
||||
|
||||
String firstLoginResponse = mockMvc.perform(post("/api/auth/login")
|
||||
.contentType("application/json")
|
||||
.header("X-Yoyuzh-Client", "desktop")
|
||||
.content(loginRequest))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.accessToken").isNotEmpty())
|
||||
@@ -77,6 +140,7 @@ class AuthSingleDeviceIntegrationTest {
|
||||
|
||||
String secondLoginResponse = mockMvc.perform(post("/api/auth/login")
|
||||
.contentType("application/json")
|
||||
.header("X-Yoyuzh-Client", "desktop")
|
||||
.content(loginRequest))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.accessToken").isNotEmpty())
|
||||
|
||||
@@ -59,7 +59,7 @@ class JwtTokenProviderTest {
|
||||
JwtTokenProvider provider = new JwtTokenProvider(properties);
|
||||
provider.init();
|
||||
|
||||
String token = provider.generateAccessToken(7L, "alice", "session-1");
|
||||
String token = provider.generateAccessToken(7L, "alice", "session-1", AuthClientType.MOBILE);
|
||||
SecretKey secretKey = Keys.hmacShaKeyFor(properties.getSecret().getBytes(StandardCharsets.UTF_8));
|
||||
Instant expiration = Jwts.parser().verifyWith(secretKey).build()
|
||||
.parseSignedClaims(token)
|
||||
@@ -71,6 +71,7 @@ class JwtTokenProviderTest {
|
||||
assertThat(provider.getUsername(token)).isEqualTo("alice");
|
||||
assertThat(provider.getUserId(token)).isEqualTo(7L);
|
||||
assertThat(provider.getSessionId(token)).isEqualTo("session-1");
|
||||
assertThat(provider.getClientType(token)).isEqualTo(AuthClientType.MOBILE);
|
||||
assertThat(provider.hasMatchingSession(token, "session-1")).isTrue();
|
||||
assertThat(provider.hasMatchingSession(token, "session-2")).isFalse();
|
||||
assertThat(expiration).isAfter(Instant.now().plusSeconds(850));
|
||||
|
||||
Reference in New Issue
Block a user