fix: repair api server merge fallout
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
mod application;
|
||||
mod application;
|
||||
mod commands;
|
||||
mod domain;
|
||||
mod errors;
|
||||
@@ -203,6 +203,30 @@ impl PasswordEntryService {
|
||||
.map(|maybe_user| maybe_user.map(|stored| PublicUserSearchResult { user: stored.user }))
|
||||
}
|
||||
|
||||
pub fn update_profile(
|
||||
&self,
|
||||
input: UpdateProfileInput,
|
||||
) -> Result<UpdateProfileResult, PasswordEntryError> {
|
||||
let display_name = match input.display_name {
|
||||
Some(value) => Some(normalize_profile_display_name(value.as_str())?),
|
||||
None => None,
|
||||
};
|
||||
let avatar_url = match input.avatar_url {
|
||||
Some(value) => Some(normalize_profile_avatar_url(value.as_str())?),
|
||||
None => None,
|
||||
};
|
||||
if display_name.is_none() && avatar_url.is_none() {
|
||||
return Err(PasswordEntryError::EmptyProfileUpdate);
|
||||
}
|
||||
|
||||
let user = self
|
||||
.store
|
||||
.update_user_profile(&input.user_id, display_name, avatar_url)?
|
||||
.ok_or(PasswordEntryError::UserNotFound)?;
|
||||
|
||||
Ok(UpdateProfileResult { user })
|
||||
}
|
||||
|
||||
pub async fn change_password(
|
||||
&self,
|
||||
input: ChangePasswordInput,
|
||||
@@ -594,11 +618,14 @@ impl PhoneAuthService {
|
||||
return Err(PhoneAuthError::UserStateMismatch);
|
||||
}
|
||||
|
||||
let merged_user = self
|
||||
let (merged_user, activated_new_user) = self
|
||||
.store
|
||||
.bind_wechat_phone_to_user(&input.user_id, normalized_phone)?;
|
||||
|
||||
Ok(BindWechatPhoneResult { user: merged_user })
|
||||
Ok(BindWechatPhoneResult {
|
||||
user: merged_user,
|
||||
activated_new_user,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -985,6 +1012,36 @@ impl InMemoryAuthStore {
|
||||
.cloned())
|
||||
}
|
||||
|
||||
fn update_user_profile(
|
||||
&self,
|
||||
user_id: &str,
|
||||
display_name: Option<String>,
|
||||
avatar_url: Option<String>,
|
||||
) -> Result<Option<AuthUser>, PasswordEntryError> {
|
||||
let mut state = self
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| PasswordEntryError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
|
||||
for stored_user in state.users_by_username.values_mut() {
|
||||
if stored_user.user.id != user_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(display_name) = display_name {
|
||||
stored_user.user.display_name = display_name;
|
||||
}
|
||||
if let Some(avatar_url) = avatar_url {
|
||||
stored_user.user.avatar_url = Some(avatar_url);
|
||||
}
|
||||
let next_user = stored_user.user.clone();
|
||||
self.persist_password_state(&state)?;
|
||||
return Ok(Some(next_user));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn create_phone_user(
|
||||
&self,
|
||||
phone_number: PhoneNumberSnapshot,
|
||||
@@ -1001,6 +1058,9 @@ impl InMemoryAuthStore {
|
||||
));
|
||||
}
|
||||
|
||||
let created_at = format_rfc3339(OffsetDateTime::now_utc()).map_err(|message| {
|
||||
PhoneAuthError::Store(format!("用户创建时间格式化失败:{message}"))
|
||||
})?;
|
||||
let sequence = state.next_user_id;
|
||||
let user_id = format!("user_{sequence:08}");
|
||||
let public_user_code = build_public_user_code(sequence);
|
||||
@@ -1011,11 +1071,13 @@ impl InMemoryAuthStore {
|
||||
public_user_code,
|
||||
username: username.clone(),
|
||||
display_name,
|
||||
avatar_url: None,
|
||||
phone_number_masked: Some(phone_number.masked_national_number.clone()),
|
||||
login_method: AuthLoginMethod::Phone,
|
||||
binding_status: AuthBindingStatus::Active,
|
||||
wechat_bound: false,
|
||||
token_version: 1,
|
||||
created_at,
|
||||
};
|
||||
state
|
||||
.phone_to_user_id
|
||||
@@ -1048,6 +1110,9 @@ impl InMemoryAuthStore {
|
||||
return Err(PasswordEntryError::InvalidCredentials);
|
||||
}
|
||||
|
||||
let created_at = format_rfc3339(OffsetDateTime::now_utc()).map_err(|message| {
|
||||
PasswordEntryError::Store(format!("用户创建时间格式化失败:{message}"))
|
||||
})?;
|
||||
let sequence = state.next_user_id;
|
||||
let user_id = format!("user_{sequence:08}");
|
||||
let public_user_code = build_public_user_code(sequence);
|
||||
@@ -1058,11 +1123,13 @@ impl InMemoryAuthStore {
|
||||
public_user_code,
|
||||
username: username.clone(),
|
||||
display_name,
|
||||
avatar_url: None,
|
||||
phone_number_masked: Some(phone_number.masked_national_number.clone()),
|
||||
login_method: AuthLoginMethod::Password,
|
||||
binding_status: AuthBindingStatus::Active,
|
||||
wechat_bound: false,
|
||||
token_version: 1,
|
||||
created_at,
|
||||
};
|
||||
state
|
||||
.phone_to_user_id
|
||||
@@ -1091,11 +1158,15 @@ impl InMemoryAuthStore {
|
||||
.lock()
|
||||
.map_err(|_| WechatAuthError::Store("用户仓储锁已中毒".to_string()))?;
|
||||
|
||||
let created_at = format_rfc3339(OffsetDateTime::now_utc()).map_err(|message| {
|
||||
WechatAuthError::Store(format!("用户创建时间格式化失败:{message}"))
|
||||
})?;
|
||||
let sequence = state.next_user_id;
|
||||
let user_id = format!("user_{sequence:08}");
|
||||
let public_user_code = build_public_user_code(sequence);
|
||||
state.next_user_id += 1;
|
||||
let username = build_system_username("wechat", state.next_user_id);
|
||||
let avatar_url = normalize_optional_string(profile.avatar_url.clone());
|
||||
let display_name = profile
|
||||
.display_name
|
||||
.as_deref()
|
||||
@@ -1108,11 +1179,13 @@ impl InMemoryAuthStore {
|
||||
public_user_code,
|
||||
username: username.clone(),
|
||||
display_name,
|
||||
avatar_url: avatar_url.clone(),
|
||||
phone_number_masked: None,
|
||||
login_method: AuthLoginMethod::Wechat,
|
||||
binding_status: AuthBindingStatus::PendingBindPhone,
|
||||
wechat_bound: true,
|
||||
token_version: 1,
|
||||
created_at,
|
||||
};
|
||||
state.users_by_username.insert(
|
||||
username,
|
||||
@@ -1128,7 +1201,7 @@ impl InMemoryAuthStore {
|
||||
provider_uid: normalize_required_string(&profile.provider_uid).unwrap_or_default(),
|
||||
provider_union_id: normalize_optional_string(profile.provider_union_id),
|
||||
display_name: normalize_optional_string(profile.display_name),
|
||||
avatar_url: normalize_optional_string(profile.avatar_url),
|
||||
avatar_url,
|
||||
};
|
||||
if let Some(provider_union_id) = identity.provider_union_id.clone() {
|
||||
state
|
||||
@@ -1454,7 +1527,7 @@ impl InMemoryAuthStore {
|
||||
&self,
|
||||
pending_user_id: &str,
|
||||
phone_number: PhoneNumberSnapshot,
|
||||
) -> Result<AuthUser, PhoneAuthError> {
|
||||
) -> Result<(AuthUser, bool), PhoneAuthError> {
|
||||
let mut state = self
|
||||
.inner
|
||||
.lock()
|
||||
@@ -1501,7 +1574,7 @@ impl InMemoryAuthStore {
|
||||
let next_user = target_user.user.clone();
|
||||
self.persist_phone_state(&state)?;
|
||||
|
||||
return Ok(next_user);
|
||||
return Ok((next_user, false));
|
||||
}
|
||||
|
||||
state
|
||||
@@ -1520,7 +1593,7 @@ impl InMemoryAuthStore {
|
||||
let next_user = stored_user.user.clone();
|
||||
self.persist_phone_state(&state)?;
|
||||
|
||||
Ok(next_user)
|
||||
Ok((next_user, true))
|
||||
}
|
||||
|
||||
fn find_session_by_refresh_token_hash(
|
||||
@@ -1819,6 +1892,40 @@ async fn verify_stored_password_user(
|
||||
})
|
||||
}
|
||||
|
||||
fn normalize_profile_display_name(value: &str) -> Result<String, PasswordEntryError> {
|
||||
let Some(display_name) = normalize_required_string(value) else {
|
||||
return Err(PasswordEntryError::InvalidDisplayName);
|
||||
};
|
||||
let length = display_name.chars().count();
|
||||
if !(2..=20).contains(&length) {
|
||||
return Err(PasswordEntryError::InvalidDisplayName);
|
||||
}
|
||||
if !display_name.chars().all(|character| {
|
||||
character == '_'
|
||||
|| character.is_ascii_alphanumeric()
|
||||
|| is_common_chinese_character(character)
|
||||
}) {
|
||||
return Err(PasswordEntryError::InvalidDisplayName);
|
||||
}
|
||||
|
||||
Ok(display_name)
|
||||
}
|
||||
|
||||
fn normalize_profile_avatar_url(value: &str) -> Result<String, PasswordEntryError> {
|
||||
let Some(avatar_url) = normalize_required_string(value) else {
|
||||
return Err(PasswordEntryError::InvalidAvatarDataUrl);
|
||||
};
|
||||
if !avatar_url.starts_with("data:image/") || !avatar_url.contains(";base64,") {
|
||||
return Err(PasswordEntryError::InvalidAvatarDataUrl);
|
||||
}
|
||||
|
||||
Ok(avatar_url)
|
||||
}
|
||||
|
||||
fn is_common_chinese_character(character: char) -> bool {
|
||||
('\u{4e00}'..='\u{9fff}').contains(&character)
|
||||
}
|
||||
|
||||
fn build_random_password_seed() -> String {
|
||||
format!(
|
||||
"seed_{}_{}",
|
||||
|
||||
Reference in New Issue
Block a user