feat(auth): support username or phone login

This commit is contained in:
2026-09-01 19:15:32 +08:00
parent 0ba3df2fce
commit 222c3790dc
32 changed files with 486 additions and 68 deletions
@@ -6,6 +6,7 @@ import com.baiyue.auth.dto.AccountManagementDtos.CreateAccountRequest;
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountProfileRequest;
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountStatusRequest;
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountScopeRequest;
import com.baiyue.auth.dto.AccountManagementDtos.ChangeLoginIdentifiersRequest;
import com.baiyue.auth.entity.AuthAccount;
import com.baiyue.auth.repository.AuthAccountRepository;
import com.baiyue.common.core.exception.BizException;
@@ -157,6 +158,33 @@ class AccountManagementServiceTest {
verify(sessions).revokeAll(10L);
}
@Test void changeLoginIdentifiersRequiresCurrentPasswordAndRevokesSessions() {
account.setUsername("old.user");
account.setPhone("13800138000");
when(repository.existsByUsernameForOtherAccount("new.user", 10L)).thenReturn(false);
when(repository.existsByPhoneForOtherAccount("13900139000", 10L)).thenReturn(false);
var result = service.changeLoginIdentifiers(10L,
new ChangeLoginIdentifiersRequest("new.user", "13900139000", "OldPass1"));
assertEquals("new.user", result.username());
assertEquals("13900139000", result.phone());
assertEquals(3, account.getTokenVersion());
verify(sessions).revokeAll(10L);
}
@Test void duplicatePhoneDoesNotChangeLoginIdentifiers() {
account.setUsername("old.user");
account.setPhone("13800138000");
when(repository.existsByUsernameForOtherAccount("new.user", 10L)).thenReturn(false);
when(repository.existsByPhoneForOtherAccount("13900139000", 10L)).thenReturn(true);
assertThrows(BizException.class, () -> service.changeLoginIdentifiers(10L,
new ChangeLoginIdentifiersRequest("new.user", "13900139000", "OldPass1")));
verify(repository, never()).save(any());
verify(sessions, never()).revokeAll(any());
}
@Test void rbacChangeRevokesAccessAndRefreshSessionsAndBumpsTokenVersions() {
AuthAccount second = new AuthAccount();
second.setId(11L); second.setTokenVersion(7); second.setAccountStatus("ACTIVE");
@@ -100,6 +100,34 @@ class AuthServiceSmallJwtTest {
assertThat(response.overseasExpiresIn()).isEqualTo(900);
}
@Test
void mainlandPhoneIsResolvedAsTheSinglePhoneLoginIdentifier() {
AuthAccount account = new AuthAccount();
account.setId(42L);
account.setUsername("operator");
account.setPhone("13800138000");
account.setPasswordHash("hash");
account.setAccountStatus("ACTIVE");
account.setDefaultTenantId(0L);
account.setOperatorTenantId(0L);
account.setDefaultMembershipId(84L);
account.setDefaultAppCode("PLATFORM");
account.setTokenVersion(1);
AuthorizationSnapshot snapshot = new AuthorizationSnapshot(42L, 0L, 84L, "PLATFORM",
"WEB", Set.of(), Set.of(), 1L);
Jwt encoded = org.mockito.Mockito.mock(Jwt.class);
when(repository.findByPhone("13800138000")).thenReturn(account);
when(passwordEncoder.matches("secret", "hash")).thenReturn(true);
when(authorizationLoader.load(account)).thenReturn(snapshot);
when(jwtEncoder.encode(any())).thenReturn(encoded);
when(encoded.getTokenValue()).thenReturn("small-token");
service.login(new LoginRequest("13800138000", "secret"));
verify(repository).findByPhone("13800138000");
verify(repository, never()).findByUsername("13800138000");
}
@Test
void refreshRotatesDomesticAndOverseasTokensFromOneAuthorizationSnapshot() {
AuthAccount account = new AuthAccount();