Refine play type integration flow and docs
This commit is contained in:
@@ -11,7 +11,7 @@ pub use errors::*;
|
||||
pub use events::*;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
@@ -918,16 +918,47 @@ impl Default for InMemoryAuthStoreState {
|
||||
|
||||
impl InMemoryAuthStoreState {
|
||||
fn from_persistent_snapshot(snapshot: PersistentAuthStoreSnapshot) -> Self {
|
||||
let existing_user_ids = snapshot
|
||||
.users_by_username
|
||||
.values()
|
||||
.map(|stored| stored.user.id.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
let phone_to_user_id = snapshot
|
||||
.phone_to_user_id
|
||||
.into_iter()
|
||||
.filter(|(_, user_id)| existing_user_ids.contains(user_id))
|
||||
.collect();
|
||||
let sessions_by_id = snapshot
|
||||
.sessions_by_id
|
||||
.into_iter()
|
||||
.filter(|(_, stored)| existing_user_ids.contains(&stored.session.user_id))
|
||||
.collect::<HashMap<_, _>>();
|
||||
let session_id_by_refresh_token_hash = snapshot
|
||||
.session_id_by_refresh_token_hash
|
||||
.into_iter()
|
||||
.filter(|(_, session_id)| sessions_by_id.contains_key(session_id))
|
||||
.collect();
|
||||
let wechat_identity_by_provider_uid = snapshot
|
||||
.wechat_identity_by_provider_uid
|
||||
.into_iter()
|
||||
.filter(|(_, identity)| existing_user_ids.contains(&identity.user_id))
|
||||
.collect();
|
||||
let user_id_by_provider_union_id = snapshot
|
||||
.user_id_by_provider_union_id
|
||||
.into_iter()
|
||||
.filter(|(_, user_id)| existing_user_ids.contains(user_id))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
next_user_id: snapshot.next_user_id,
|
||||
users_by_username: snapshot.users_by_username,
|
||||
phone_to_user_id: snapshot.phone_to_user_id,
|
||||
sessions_by_id: snapshot.sessions_by_id,
|
||||
session_id_by_refresh_token_hash: snapshot.session_id_by_refresh_token_hash,
|
||||
phone_to_user_id,
|
||||
sessions_by_id,
|
||||
session_id_by_refresh_token_hash,
|
||||
phone_codes_by_key: HashMap::new(),
|
||||
wechat_states_by_token: HashMap::new(),
|
||||
wechat_identity_by_provider_uid: snapshot.wechat_identity_by_provider_uid,
|
||||
user_id_by_provider_union_id: snapshot.user_id_by_provider_union_id,
|
||||
wechat_identity_by_provider_uid,
|
||||
user_id_by_provider_union_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1159,10 +1190,17 @@ impl InMemoryAuthStore {
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| PhoneAuthError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
if state.phone_to_user_id.contains_key(&phone_number.e164) {
|
||||
return Err(PhoneAuthError::Store(
|
||||
"手机号已存在,无法重复创建账号".to_string(),
|
||||
));
|
||||
if let Some(existing_user_id) = state.phone_to_user_id.get(&phone_number.e164).cloned() {
|
||||
let existing_user_exists = state
|
||||
.users_by_username
|
||||
.values()
|
||||
.any(|stored_user| stored_user.user.id == existing_user_id);
|
||||
if existing_user_exists {
|
||||
return Err(PhoneAuthError::Store(
|
||||
"手机号已存在,无法重复创建账号".to_string(),
|
||||
));
|
||||
}
|
||||
state.phone_to_user_id.remove(&phone_number.e164);
|
||||
}
|
||||
|
||||
let created_at = format_rfc3339(OffsetDateTime::now_utc()).map_err(|message| {
|
||||
@@ -1213,8 +1251,15 @@ impl InMemoryAuthStore {
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| PasswordEntryError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
if state.phone_to_user_id.contains_key(&phone_number.e164) {
|
||||
return Err(PasswordEntryError::InvalidCredentials);
|
||||
if let Some(existing_user_id) = state.phone_to_user_id.get(&phone_number.e164).cloned() {
|
||||
let existing_user_exists = state
|
||||
.users_by_username
|
||||
.values()
|
||||
.any(|stored_user| stored_user.user.id == existing_user_id);
|
||||
if existing_user_exists {
|
||||
return Err(PasswordEntryError::InvalidCredentials);
|
||||
}
|
||||
state.phone_to_user_id.remove(&phone_number.e164);
|
||||
}
|
||||
|
||||
let created_at = format_rfc3339(OffsetDateTime::now_utc()).map_err(|message| {
|
||||
@@ -2629,6 +2674,54 @@ mod tests {
|
||||
assert_eq!(rotated.user.id, user.id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn snapshot_json_drops_orphan_phone_index_before_phone_login() {
|
||||
let snapshot = PersistentAuthStoreSnapshot {
|
||||
next_user_id: 9,
|
||||
users_by_username: HashMap::new(),
|
||||
phone_to_user_id: HashMap::from([(
|
||||
"+8613800138032".to_string(),
|
||||
"user_missing_phone_owner".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 should serialize");
|
||||
let restored_store = InMemoryAuthStore::from_snapshot_json(&snapshot_json)
|
||||
.expect("snapshot json should restore");
|
||||
let phone_service = build_phone_service(restored_store);
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
phone_service
|
||||
.send_code(
|
||||
SendPhoneCodeInput {
|
||||
phone_number: "13800138032".to_string(),
|
||||
scene: PhoneAuthScene::Login,
|
||||
},
|
||||
now,
|
||||
)
|
||||
.await
|
||||
.expect("phone code should send");
|
||||
let result = phone_service
|
||||
.login(
|
||||
PhoneLoginInput {
|
||||
phone_number: "13800138032".to_string(),
|
||||
verify_code: DEFAULT_SMS_MOCK_VERIFY_CODE.to_string(),
|
||||
},
|
||||
now + Duration::seconds(1),
|
||||
)
|
||||
.await
|
||||
.expect("orphan phone index should not block phone login");
|
||||
|
||||
assert!(result.created);
|
||||
assert_eq!(
|
||||
result.user.phone_number_masked.as_deref(),
|
||||
Some("138****8032")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn password_entry_rejects_email_or_username_identifier() {
|
||||
let service = build_password_service(build_store());
|
||||
|
||||
Reference in New Issue
Block a user