feat: add puzzle clear template runtime
This commit is contained in:
@@ -992,6 +992,36 @@ impl InMemoryAuthStore {
|
||||
.map_err(RefreshSessionError::Store)
|
||||
}
|
||||
|
||||
fn resolve_phone_user_locked(
|
||||
state: &mut InMemoryAuthStoreState,
|
||||
phone_number: &str,
|
||||
) -> Option<StoredPasswordUser> {
|
||||
if let Some(user_id) = state.phone_to_user_id.get(phone_number).cloned() {
|
||||
if let Some(stored_user) = state
|
||||
.users_by_username
|
||||
.values()
|
||||
.find(|stored_user| stored_user.user.id == user_id)
|
||||
.cloned()
|
||||
{
|
||||
return Some(stored_user);
|
||||
}
|
||||
state.phone_to_user_id.remove(phone_number);
|
||||
}
|
||||
|
||||
let Some(stored_user) = state
|
||||
.users_by_username
|
||||
.values()
|
||||
.find(|stored_user| stored_user.phone_number.as_deref() == Some(phone_number))
|
||||
.cloned()
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
state
|
||||
.phone_to_user_id
|
||||
.insert(phone_number.to_string(), stored_user.user.id.clone());
|
||||
Some(stored_user)
|
||||
}
|
||||
|
||||
fn find_by_user_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
@@ -1086,36 +1116,22 @@ impl InMemoryAuthStore {
|
||||
&self,
|
||||
phone_number: &str,
|
||||
) -> Result<Option<StoredPasswordUser>, PhoneAuthError> {
|
||||
let state = self
|
||||
let mut state = self
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
let Some(user_id) = state.phone_to_user_id.get(phone_number) else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(state
|
||||
.users_by_username
|
||||
.values()
|
||||
.find(|stored_user| stored_user.user.id == *user_id)
|
||||
.cloned())
|
||||
Ok(Self::resolve_phone_user_locked(&mut state, phone_number))
|
||||
}
|
||||
|
||||
fn find_by_phone_number_for_password(
|
||||
&self,
|
||||
phone_number: &str,
|
||||
) -> Result<Option<StoredPasswordUser>, PasswordEntryError> {
|
||||
let state = self
|
||||
let mut state = self
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| PasswordEntryError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
let Some(user_id) = state.phone_to_user_id.get(phone_number) else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(state
|
||||
.users_by_username
|
||||
.values()
|
||||
.find(|stored_user| stored_user.user.id == *user_id)
|
||||
.cloned())
|
||||
Ok(Self::resolve_phone_user_locked(&mut state, phone_number))
|
||||
}
|
||||
|
||||
fn update_user_profile(
|
||||
@@ -1158,7 +1174,7 @@ impl InMemoryAuthStore {
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
if state.phone_to_user_id.contains_key(&phone_number.e164) {
|
||||
if Self::resolve_phone_user_locked(&mut state, &phone_number.e164).is_some() {
|
||||
return Err(PhoneAuthError::Store(
|
||||
"手机号已存在,无法重复创建账号".to_string(),
|
||||
));
|
||||
@@ -1212,7 +1228,7 @@ impl InMemoryAuthStore {
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| PasswordEntryError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
if state.phone_to_user_id.contains_key(&phone_number.e164) {
|
||||
if Self::resolve_phone_user_locked(&mut state, &phone_number.e164).is_some() {
|
||||
return Err(PasswordEntryError::InvalidCredentials);
|
||||
}
|
||||
|
||||
@@ -1662,7 +1678,9 @@ impl InMemoryAuthStore {
|
||||
.lock()
|
||||
.map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
|
||||
let existing_phone_user_id = state.phone_to_user_id.get(&phone_number.e164).cloned();
|
||||
let existing_phone_user_id =
|
||||
Self::resolve_phone_user_locked(&mut state, &phone_number.e164)
|
||||
.map(|stored_user| stored_user.user.id);
|
||||
if let Some(target_user_id) = existing_phone_user_id
|
||||
&& target_user_id != pending_user_id
|
||||
{
|
||||
@@ -2048,10 +2066,8 @@ impl InMemoryAuthStore {
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
let user_id = state
|
||||
.phone_to_user_id
|
||||
.get(phone_number)
|
||||
.cloned()
|
||||
let user_id = Self::resolve_phone_user_locked(&mut state, phone_number)
|
||||
.map(|stored_user| stored_user.user.id)
|
||||
.ok_or(PhoneAuthError::UserNotFound)?;
|
||||
|
||||
for stored_user in state.users_by_username.values_mut() {
|
||||
@@ -2572,6 +2588,90 @@ mod tests {
|
||||
assert_eq!(error, PhoneAuthError::UserNotFound);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dev_password_registration_ignores_orphan_phone_index() {
|
||||
let snapshot = PersistentAuthStoreSnapshot {
|
||||
next_user_id: 7,
|
||||
users_by_username: HashMap::new(),
|
||||
phone_to_user_id: HashMap::from([(
|
||||
"+8613800138004".to_string(),
|
||||
"user_deleted".to_string(),
|
||||
)]),
|
||||
sessions_by_id: HashMap::new(),
|
||||
session_id_by_refresh_token_hash: HashMap::new(),
|
||||
wechat_identity_by_provider_uid: HashMap::new(),
|
||||
user_id_by_provider_union_id: HashMap::new(),
|
||||
};
|
||||
let snapshot_json =
|
||||
serde_json::to_string(&snapshot).expect("snapshot json should serialize");
|
||||
let service = build_password_service(
|
||||
InMemoryAuthStore::from_snapshot_json(&snapshot_json).expect("snapshot should restore"),
|
||||
);
|
||||
|
||||
let created = service
|
||||
.execute_with_dev_registration(PasswordEntryInput {
|
||||
phone_number: "13800138004".to_string(),
|
||||
password: "secret123".to_string(),
|
||||
})
|
||||
.await
|
||||
.expect("orphan phone index should not block dev registration");
|
||||
|
||||
assert!(created.created);
|
||||
assert_eq!(
|
||||
created.user.phone_number_masked.as_deref(),
|
||||
Some("138****8004")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn phone_login_ignores_orphan_phone_index_after_code_verification() {
|
||||
let snapshot = PersistentAuthStoreSnapshot {
|
||||
next_user_id: 8,
|
||||
users_by_username: HashMap::new(),
|
||||
phone_to_user_id: HashMap::from([(
|
||||
"+8613800138005".to_string(),
|
||||
"user_deleted".to_string(),
|
||||
)]),
|
||||
sessions_by_id: HashMap::new(),
|
||||
session_id_by_refresh_token_hash: HashMap::new(),
|
||||
wechat_identity_by_provider_uid: HashMap::new(),
|
||||
user_id_by_provider_union_id: HashMap::new(),
|
||||
};
|
||||
let snapshot_json =
|
||||
serde_json::to_string(&snapshot).expect("snapshot json should serialize");
|
||||
let phone_service = build_phone_service(
|
||||
InMemoryAuthStore::from_snapshot_json(&snapshot_json).expect("snapshot should restore"),
|
||||
);
|
||||
let now = OffsetDateTime::now_utc();
|
||||
phone_service
|
||||
.send_code(
|
||||
SendPhoneCodeInput {
|
||||
phone_number: "13800138005".to_string(),
|
||||
scene: PhoneAuthScene::Login,
|
||||
},
|
||||
now,
|
||||
)
|
||||
.await
|
||||
.expect("phone code should send");
|
||||
|
||||
let created = phone_service
|
||||
.login(
|
||||
PhoneLoginInput {
|
||||
phone_number: "13800138005".to_string(),
|
||||
verify_code: "123456".to_string(),
|
||||
},
|
||||
now + Duration::seconds(1),
|
||||
)
|
||||
.await
|
||||
.expect("orphan phone index should not turn login into duplicate create");
|
||||
|
||||
assert!(created.created);
|
||||
assert_eq!(
|
||||
created.user.phone_number_masked.as_deref(),
|
||||
Some("138****8005")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn snapshot_json_restores_user_and_refresh_session_after_roundtrip() {
|
||||
let store = InMemoryAuthStore::default();
|
||||
|
||||
Reference in New Issue
Block a user