Files
Genarrative/server-rs/crates/api-server/src/wechat_auth.rs
kdletters 8f4ca9abfa Merge remote-tracking branch 'origin/master' into codex/ddd
# Conflicts:
#	docs/technical/README.md
#	docs/technical/RUST_API_SERVER_ROUTE_INDEX_2026-04-22.md
#	docs/technical/SPACETIMEDB_TABLE_CATALOG.md
#	scripts/generate-spacetime-bindings.mjs
#	server-rs/crates/api-server/src/app.rs
#	server-rs/crates/api-server/src/assets.rs
#	server-rs/crates/api-server/src/big_fish.rs
#	server-rs/crates/api-server/src/custom_world_ai.rs
#	server-rs/crates/api-server/src/llm.rs
#	server-rs/crates/api-server/src/main.rs
#	server-rs/crates/api-server/src/puzzle.rs
#	server-rs/crates/api-server/src/runtime_profile.rs
#	server-rs/crates/api-server/src/runtime_story/compat/ai.rs
#	server-rs/crates/api-server/src/runtime_story/compat/npc_actions.rs
#	server-rs/crates/api-server/src/runtime_story/compat/presentation.rs
#	server-rs/crates/api-server/src/runtime_story/compat/tests.rs
#	server-rs/crates/api-server/src/state.rs
#	server-rs/crates/module-auth/src/lib.rs
#	server-rs/crates/module-big-fish/src/lib.rs
#	server-rs/crates/module-custom-world/src/lib.rs
#	server-rs/crates/module-puzzle/src/lib.rs
#	server-rs/crates/module-runtime/src/lib.rs
#	server-rs/crates/spacetime-client/src/big_fish.rs
#	server-rs/crates/spacetime-client/src/lib.rs
#	server-rs/crates/spacetime-client/src/mapper.rs
#	server-rs/crates/spacetime-client/src/module_bindings/admin_disable_profile_redeem_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/admin_upsert_profile_redeem_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/advance_puzzle_next_level_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/append_ai_text_chunk_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/apply_chapter_progression_ledger_entry_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/attach_ai_result_reference_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/authorize_database_migration_operator_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/begin_story_session_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/big_fish_runtime_run_type.rs
#	server-rs/crates/spacetime-client/src/module_bindings/bind_asset_object_to_entity_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/cancel_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/clear_platform_browse_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_big_fish_draft_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_custom_world_published_profile_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/compile_puzzle_agent_draft_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/complete_ai_stage_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/complete_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/confirm_asset_object_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/consume_profile_wallet_points_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/continue_story_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_battle_state_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_big_fish_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_profile_recharge_order_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/create_puzzle_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_big_fish_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/delete_runtime_snapshot_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/drag_puzzle_piece_or_group_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/execute_custom_world_agent_action_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/export_auth_store_snapshot_from_tables_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/export_database_migration_to_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/fail_ai_task_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_big_fish_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_custom_world_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/finalize_puzzle_agent_message_turn_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/generate_big_fish_asset_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_battle_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_big_fish_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_chapter_progression_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_card_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_operation_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_by_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_gallery_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_custom_world_library_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_player_progression_or_default_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_dashboard_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_play_stats_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_recharge_center_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_profile_referral_invite_center_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_agent_session_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_gallery_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_run_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_puzzle_work_detail_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_inventory_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_setting_or_default_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_runtime_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/get_story_session_state_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/grant_player_progression_experience_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_from_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/import_database_migration_incremental_from_file_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_asset_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_big_fish_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_gallery_entries_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_profiles_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_custom_world_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_platform_browse_history_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_profile_save_archives_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_profile_wallet_ledger_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_gallery_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/list_puzzle_works_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/mod.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_big_fish_game_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_custom_world_world_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/publish_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/record_big_fish_play_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_referral_invite_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/redeem_profile_reward_code_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/refund_profile_wallet_points_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_combat_action_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_battle_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_npc_social_action_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resolve_treasure_interaction_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/resume_profile_save_archive_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/revoke_database_migration_operator_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/save_puzzle_generated_images_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/select_puzzle_cover_image_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/start_puzzle_run_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_big_fish_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_custom_world_agent_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_agent_message_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/submit_puzzle_leaderboard_entry_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/swap_puzzle_pieces_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/unpublish_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/update_puzzle_work_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_auth_store_snapshot_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_chapter_progression_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_agent_operation_progress_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_custom_world_profile_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_npc_state_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_platform_browse_history_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_setting_and_return_procedure.rs
#	server-rs/crates/spacetime-client/src/module_bindings/upsert_runtime_snapshot_and_return_procedure.rs
#	server-rs/crates/spacetime-module/src/auth/procedures.rs
#	server-rs/crates/spacetime-module/src/custom_world/mod.rs
#	server-rs/crates/spacetime-module/src/lib.rs
#	server-rs/crates/spacetime-module/src/migration.rs
#	server-rs/crates/spacetime-module/src/puzzle.rs
#	server-rs/crates/spacetime-module/src/runtime/profile.rs
#	src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
#	src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
#	src/services/aiService.ts
#	src/services/puzzle-runtime/puzzleRuntimeClient.ts
2026-05-02 03:35:59 +08:00

388 lines
13 KiB
Rust

use axum::{
Json,
extract::{Extension, Query, State},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Redirect, Response},
};
use module_auth::{
AuthLoginMethod, BindWechatPhoneInput, CreateWechatAuthStateInput, WechatAuthError,
};
use platform_auth::WechatAuthScene;
use shared_contracts::auth::{
WechatBindPhoneRequest, WechatBindPhoneResponse, WechatCallbackQuery, WechatStartQuery,
WechatStartResponse,
};
use time::OffsetDateTime;
use url::Url;
use crate::{
api_response::json_success_body,
auth::AuthenticatedAccessToken,
auth_payload::map_auth_user_payload,
auth_session::{
attach_set_cookie_header, build_refresh_session_cookie_header, create_auth_session,
},
http_error::AppError,
platform_errors::{attach_retry_after, map_wechat_provider_error},
request_context::RequestContext,
session_client::resolve_session_client_context,
state::AppState,
};
pub async fn start_wechat_login(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
headers: HeaderMap,
Query(query): Query<WechatStartQuery>,
) -> Result<Json<serde_json::Value>, AppError> {
if !state.config.wechat_auth_enabled {
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_message("微信登录暂未启用"));
}
let user_agent = headers
.get("user-agent")
.and_then(|value| value.to_str().ok())
.map(|value| value.to_string());
let scene = resolve_wechat_scene(user_agent.as_deref())?;
let state_record = state
.wechat_auth_state_service()
.create_state(
CreateWechatAuthStateInput {
redirect_path: normalize_redirect_path(
query.redirect_path.as_deref(),
&state.config.wechat_redirect_path,
),
scene: map_wechat_scene_to_domain(&scene),
request_user_agent: user_agent.clone(),
},
OffsetDateTime::now_utc(),
)
.map_err(map_wechat_auth_error)?;
let authorization_url = state
.wechat_provider()
.build_authorization_url(
&resolve_wechat_callback_url(&state, &headers)?,
&state_record.state.state_token,
&scene,
)
.map_err(map_wechat_provider_error)?;
Ok(json_success_body(
Some(&request_context),
WechatStartResponse { authorization_url },
))
}
pub async fn handle_wechat_callback(
State(state): State<AppState>,
headers: HeaderMap,
Query(query): Query<WechatCallbackQuery>,
) -> Result<impl IntoResponse, AppError> {
if !state.config.wechat_auth_enabled {
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_message("微信登录暂未启用"));
}
let fallback_redirect = state.config.wechat_redirect_path.clone();
let state_token = query
.state
.as_deref()
.unwrap_or_default()
.trim()
.to_string();
if state_token.is_empty() {
return Ok(Redirect::to(&build_auth_result_redirect_url(
&fallback_redirect,
&[
("auth_provider", "wechat"),
("auth_error", "微信登录状态已失效,请重新发起登录。"),
],
))
.into_response());
}
let consumed = match state
.wechat_auth_state_service()
.consume_state(&state_token, OffsetDateTime::now_utc())
{
Ok(value) => value,
Err(_) => {
return Ok(Redirect::to(&build_auth_result_redirect_url(
&fallback_redirect,
&[
("auth_provider", "wechat"),
("auth_error", "微信登录状态已失效,请重新发起登录。"),
],
))
.into_response());
}
};
let redirect_path = consumed.state.redirect_path.clone();
let session_client = resolve_session_client_context(&headers);
let result = match state
.wechat_provider()
.resolve_callback_profile(query.code.as_deref(), query.mock_code.as_deref())
.await
{
Ok(profile) => state
.wechat_auth_service()
.resolve_login(module_auth::ResolveWechatLoginInput {
profile: map_wechat_profile_to_domain(profile),
})
.await
.map_err(map_wechat_auth_error),
Err(error) => Err(map_wechat_provider_error(error)),
};
match result {
Ok(result) => {
let signed_session = create_auth_session(
&state,
&result.user,
&session_client,
AuthLoginMethod::Wechat,
)?;
state
.sync_auth_store_snapshot_to_spacetime()
.await
.map_err(|error| {
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
.with_message(format!("同步认证快照失败:{error}"))
})?;
let mut response = Redirect::to(&build_auth_result_redirect_url(
&redirect_path,
&[
("auth_provider", "wechat"),
("auth_token", signed_session.access_token.as_str()),
("auth_binding_status", result.user.binding_status.as_str()),
],
))
.into_response();
attach_set_cookie_header(
response.headers_mut(),
build_refresh_session_cookie_header(&state, &signed_session.refresh_token)?,
);
Ok(response)
}
Err(error) => Ok(Redirect::to(&build_auth_result_redirect_url(
&redirect_path,
&[("auth_provider", "wechat"), ("auth_error", error.message())],
))
.into_response()),
}
}
pub async fn bind_wechat_phone(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
headers: HeaderMap,
Json(payload): Json<WechatBindPhoneRequest>,
) -> Result<impl IntoResponse, AppError> {
if !state.config.wechat_auth_enabled {
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_message("微信登录暂未启用"));
}
let result = state
.phone_auth_service()
.bind_wechat_phone(
BindWechatPhoneInput {
user_id: authenticated.claims().user_id().to_string(),
phone_number: payload.phone,
verify_code: payload.code,
},
OffsetDateTime::now_utc(),
)
.await
.map_err(map_wechat_bind_phone_error)?;
if result.activated_new_user {
crate::registration_reward::grant_new_user_registration_wallet_reward(
&state,
&request_context,
&result.user.id,
)
.await;
}
let session_client = resolve_session_client_context(&headers);
let signed_session = create_auth_session(
&state,
&result.user,
&session_client,
AuthLoginMethod::Wechat,
)?;
state
.sync_auth_store_snapshot_to_spacetime()
.await
.map_err(|error| {
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
.with_message(format!("同步认证快照失败:{error}"))
})?;
let mut response_headers = HeaderMap::new();
attach_set_cookie_header(
&mut response_headers,
build_refresh_session_cookie_header(&state, &signed_session.refresh_token)?,
);
Ok((
response_headers,
json_success_body(
Some(&request_context),
WechatBindPhoneResponse {
token: signed_session.access_token,
user: map_auth_user_payload(result.user),
},
),
))
}
fn resolve_wechat_scene(user_agent: Option<&str>) -> Result<WechatAuthScene, AppError> {
let user_agent = user_agent.unwrap_or_default();
let is_wechat = user_agent.contains("MicroMessenger");
let is_mobile = user_agent.contains("Android")
|| user_agent.contains("iPhone")
|| user_agent.contains("iPad")
|| user_agent.contains("Mobile");
if is_wechat {
return Ok(WechatAuthScene::WechatInApp);
}
if is_mobile {
return Err(AppError::from_status(StatusCode::BAD_REQUEST)
.with_message("当前浏览器请使用手机号登录,或在微信内打开后再使用微信登录"));
}
Ok(WechatAuthScene::Desktop)
}
fn map_wechat_scene_to_domain(scene: &WechatAuthScene) -> module_auth::WechatAuthScene {
match scene {
WechatAuthScene::Desktop => module_auth::WechatAuthScene::Desktop,
WechatAuthScene::WechatInApp => module_auth::WechatAuthScene::WechatInApp,
}
}
fn map_wechat_profile_to_domain(
profile: platform_auth::WechatIdentityProfile,
) -> module_auth::WechatIdentityProfile {
module_auth::WechatIdentityProfile {
provider_uid: profile.provider_uid,
provider_union_id: profile.provider_union_id,
display_name: profile.display_name,
avatar_url: profile.avatar_url,
}
}
fn normalize_redirect_path(raw_value: Option<&str>, fallback: &str) -> String {
let Some(raw_value) = raw_value.map(str::trim).filter(|value| !value.is_empty()) else {
return fallback.to_string();
};
if raw_value.starts_with('/') {
return raw_value.to_string();
}
Url::parse(raw_value)
.map(|url| {
format!(
"{}{}{}",
url.path(),
url.query().map(|v| format!("?{v}")).unwrap_or_default(),
url.fragment().map(|v| format!("#{v}")).unwrap_or_default()
)
})
.unwrap_or_else(|_| fallback.to_string())
}
fn resolve_wechat_callback_url(state: &AppState, headers: &HeaderMap) -> Result<String, AppError> {
let proto = headers
.get("x-forwarded-proto")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.split(',').next())
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("http");
let host = headers
.get("x-forwarded-host")
.or_else(|| headers.get("host"))
.and_then(|value| value.to_str().ok())
.and_then(|value| value.split(',').next())
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("127.0.0.1:3000");
Ok(format!(
"{proto}://{host}{}",
state.config.wechat_callback_path
))
}
fn build_auth_result_redirect_url(redirect_path: &str, params: &[(&str, &str)]) -> String {
let hash = params
.iter()
.map(|(key, value)| {
format!(
"{}={}",
urlencoding::encode(key),
urlencoding::encode(value)
)
})
.collect::<Vec<_>>()
.join("&");
let path_without_hash = redirect_path.split('#').next().unwrap_or("/");
format!(
"{}#{}",
if path_without_hash.is_empty() {
"/"
} else {
path_without_hash
},
hash
)
}
#[allow(dead_code)]
fn _assert_response_type(_: Response) {}
fn map_wechat_auth_error(error: WechatAuthError) -> AppError {
match error {
WechatAuthError::MissingProfile
| WechatAuthError::StateNotFound
| WechatAuthError::StateExpired
| WechatAuthError::StateConsumed
| WechatAuthError::MissingWechatIdentity => {
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error.to_string())
}
WechatAuthError::UserNotFound => {
AppError::from_status(StatusCode::UNAUTHORIZED).with_message(error.to_string())
}
WechatAuthError::Store(_) | WechatAuthError::PasswordHash(_) => {
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(error.to_string())
}
}
}
fn map_wechat_bind_phone_error(error: module_auth::PhoneAuthError) -> AppError {
match error {
module_auth::PhoneAuthError::InvalidPhoneNumber
| module_auth::PhoneAuthError::InvalidVerifyCode
| module_auth::PhoneAuthError::VerifyCodeNotFound
| module_auth::PhoneAuthError::VerifyCodeExpired
| module_auth::PhoneAuthError::UserStateMismatch => {
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error.to_string())
}
module_auth::PhoneAuthError::SendCoolingDown {
retry_after_seconds,
} => {
let app_error = AppError::from_status(StatusCode::TOO_MANY_REQUESTS)
.with_message(error.to_string())
.with_details(serde_json::json!({ "retryAfterSeconds": retry_after_seconds }));
attach_retry_after(app_error, retry_after_seconds)
}
module_auth::PhoneAuthError::VerifyAttemptsExceeded => {
AppError::from_status(StatusCode::TOO_MANY_REQUESTS).with_message(error.to_string())
}
module_auth::PhoneAuthError::UserNotFound => {
AppError::from_status(StatusCode::UNAUTHORIZED).with_message(error.to_string())
}
module_auth::PhoneAuthError::Store(_) | module_auth::PhoneAuthError::PasswordHash(_) => {
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(error.to_string())
}
}
}