迁移后端认证与拆分 Spacetime 客户端
This commit is contained in:
@@ -29,10 +29,12 @@
|
||||
7. 基础 `TraceLayer` 挂载
|
||||
8. 接入 `shared-logging` 完成 `tracing subscriber` 初始化
|
||||
9. 接入 `POST /api/auth/entry` 首版密码登录链路
|
||||
10. 接入 `POST /api/assets/direct-upload-tickets` 直传票据接口
|
||||
11. 接入 `GET /api/auth/me` 当前用户查询链路
|
||||
12. 接入 `POST /api/auth/refresh` refresh token 轮换链路
|
||||
13. 接入 `POST /api/auth/logout` 当前设备退出链路
|
||||
10. 接入 `POST /api/auth/password/change` 登录后修改密码链路
|
||||
11. 接入 `POST /api/auth/password/reset` 手机验证码重置密码链路
|
||||
12. 接入 `POST /api/assets/direct-upload-tickets` 直传票据接口
|
||||
13. 接入 `GET /api/auth/me` 当前用户查询链路
|
||||
14. 接入 `POST /api/auth/refresh` refresh token 轮换链路
|
||||
15. 接入 `POST /api/auth/logout` 当前设备退出链路
|
||||
14. 接入 `POST /api/assets/objects/confirm` 上传完成确认链路
|
||||
15. 接入 `GET /api/auth/login-options` 登录方式探测链路
|
||||
16. 接入 `POST /api/auth/phone/send-code` 手机验证码发送链路
|
||||
|
||||
@@ -73,6 +73,7 @@ use crate::{
|
||||
logout::logout,
|
||||
logout_all::logout_all,
|
||||
password_entry::password_entry,
|
||||
password_management::{change_password, reset_password},
|
||||
phone_auth::{phone_login, send_phone_code},
|
||||
puzzle::{
|
||||
advance_puzzle_next_level, create_puzzle_agent_session, drag_puzzle_piece_or_group,
|
||||
@@ -887,6 +888,14 @@ pub fn build_router(state: AppState) -> Router {
|
||||
)),
|
||||
)
|
||||
.route("/api/auth/entry", post(password_entry))
|
||||
.route(
|
||||
"/api/auth/password/change",
|
||||
post(change_password).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route("/api/auth/password/reset", post(reset_password))
|
||||
// 错误归一化层放在 tracing 里侧,让 tracing 记录到最终对外返回的状态与错误体形态。
|
||||
.layer(middleware::from_fn(normalize_error_response))
|
||||
// 响应头回写放在错误归一化外侧,确保最终写回的是归一化后的最终响应。
|
||||
|
||||
@@ -66,7 +66,8 @@ fn map_public_user_search_error(error: module_auth::PasswordEntryError) -> AppEr
|
||||
| module_auth::PasswordEntryError::PasswordHash(_)
|
||||
| module_auth::PasswordEntryError::InvalidUsername
|
||||
| module_auth::PasswordEntryError::InvalidPasswordLength
|
||||
| module_auth::PasswordEntryError::InvalidCredentials => {
|
||||
| module_auth::PasswordEntryError::InvalidCredentials
|
||||
| module_auth::PasswordEntryError::UserNotFound => {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use platform_llm::{
|
||||
|
||||
const DEFAULT_LLM_MODEL: &str = "doubao-1-5-pro-32k-character-250715";
|
||||
const DEFAULT_INTERNAL_API_SECRET: &str = "genarrative-dev-internal-bridge";
|
||||
const DEFAULT_AUTH_STORE_PATH: &str = "server-rs/.data/auth-store.json";
|
||||
const SPACETIME_LOCAL_CONFIG_FILE: &str = "spacetime.local.json";
|
||||
|
||||
// 集中管理 api-server 的启动配置,避免入口层直接散落环境变量解析逻辑。
|
||||
@@ -27,6 +28,7 @@ pub struct AppConfig {
|
||||
pub refresh_cookie_secure: bool,
|
||||
pub refresh_cookie_same_site: String,
|
||||
pub refresh_session_ttl_days: u32,
|
||||
pub auth_store_path: PathBuf,
|
||||
pub sms_auth_enabled: bool,
|
||||
pub sms_auth_provider: String,
|
||||
pub sms_endpoint: String,
|
||||
@@ -109,6 +111,7 @@ impl Default for AppConfig {
|
||||
refresh_cookie_secure: false,
|
||||
refresh_cookie_same_site: "Lax".to_string(),
|
||||
refresh_session_ttl_days: 30,
|
||||
auth_store_path: PathBuf::from(DEFAULT_AUTH_STORE_PATH),
|
||||
sms_auth_enabled: false,
|
||||
sms_auth_provider: "mock".to_string(),
|
||||
sms_endpoint: "dypnsapi.aliyuncs.com".to_string(),
|
||||
@@ -255,6 +258,10 @@ impl AppConfig {
|
||||
config.refresh_session_ttl_days = refresh_session_ttl_days;
|
||||
}
|
||||
|
||||
if let Some(auth_store_path) = read_first_non_empty_env(&["GENARRATIVE_AUTH_STORE_PATH"]) {
|
||||
config.auth_store_path = PathBuf::from(auth_store_path);
|
||||
}
|
||||
|
||||
if let Some(sms_auth_enabled) = read_first_bool_env(&["SMS_AUTH_ENABLED"]) {
|
||||
config.sms_auth_enabled = sms_auth_enabled;
|
||||
}
|
||||
|
||||
@@ -988,10 +988,10 @@ pub async fn execute_custom_world_agent_action(
|
||||
let result = state
|
||||
.spacetime_client()
|
||||
.execute_custom_world_agent_action(CustomWorldAgentActionExecuteRecordInput {
|
||||
session_id,
|
||||
owner_user_id,
|
||||
session_id: session_id.clone(),
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
operation_id: build_prefixed_uuid_id("operation-"),
|
||||
action,
|
||||
action: action.clone(),
|
||||
payload_json: Some(payload_json),
|
||||
submitted_at_micros,
|
||||
})
|
||||
@@ -1179,7 +1179,7 @@ fn log_custom_world_publish_gate_diagnostics(
|
||||
blocker_codes = %blocker_codes,
|
||||
has_draft_profile = session.draft_profile.as_object().map(|value| !value.is_empty()).unwrap_or(false),
|
||||
has_result_preview = session.result_preview.is_some(),
|
||||
preview_source = session.result_preview.as_ref().and_then(|value| value.get("source")).and_then(Value::as_str).unwrap_or(""),
|
||||
preview_source = session.result_preview.as_ref().and_then(|value| value.get("source")).and_then(serde_json::Value::as_str).unwrap_or(""),
|
||||
has_world_hook = has_custom_world_publish_text(profile, &["worldHook", "creatorIntent.worldHook", "anchorContent.worldPromise.hook", "settingText"]),
|
||||
has_player_premise = has_custom_world_publish_text(profile, &["playerPremise", "creatorIntent.playerPremise", "anchorContent.playerEntryPoint.openingIdentity", "anchorContent.playerEntryPoint.openingProblem", "anchorContent.playerEntryPoint.entryMotivation"]),
|
||||
has_core_conflicts = has_custom_world_non_empty_text_array(profile, "coreConflicts"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::{
|
||||
extract::{Extension, State},
|
||||
http::HeaderMap,
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use module_auth::LogoutCurrentSessionInput;
|
||||
@@ -44,6 +44,13 @@ pub async fn logout(
|
||||
OffsetDateTime::now_utc(),
|
||||
)
|
||||
.map_err(map_logout_error)?;
|
||||
state
|
||||
.sync_auth_store_snapshot_to_spacetime()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_message(format!("同步认证快照失败:{error}"))
|
||||
})?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
attach_set_cookie_header(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::{
|
||||
extract::{Extension, State},
|
||||
http::HeaderMap,
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use module_auth::LogoutAllSessionsInput;
|
||||
@@ -32,6 +32,13 @@ pub async fn logout_all(
|
||||
OffsetDateTime::now_utc(),
|
||||
)
|
||||
.map_err(map_logout_error)?;
|
||||
state
|
||||
.sync_auth_store_snapshot_to_spacetime()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_message(format!("同步认证快照失败:{error}"))
|
||||
})?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
attach_set_cookie_header(
|
||||
|
||||
@@ -29,6 +29,7 @@ mod login_options;
|
||||
mod logout;
|
||||
mod logout_all;
|
||||
mod password_entry;
|
||||
mod password_management;
|
||||
mod phone_auth;
|
||||
mod puzzle;
|
||||
mod puzzle_agent_turn;
|
||||
@@ -67,7 +68,8 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
let bind_address = config.bind_socket_addr();
|
||||
let listener = TcpListener::bind(bind_address).await?;
|
||||
|
||||
let state = AppState::new(config)
|
||||
let state = AppState::try_restore_auth_store_from_spacetime(config)
|
||||
.await
|
||||
.map_err(|error| std::io::Error::other(format!("初始化应用状态失败:{error}")))?;
|
||||
let router = build_router(state);
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@ pub async fn password_entry(
|
||||
.map_err(map_password_entry_error)?;
|
||||
let session_client = resolve_session_client_context(&headers);
|
||||
let signed_session = create_password_auth_session(&state, &result.user, &session_client)?;
|
||||
state
|
||||
.sync_auth_store_snapshot_to_spacetime()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_message(format!("同步认证快照失败:{error}"))
|
||||
})?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
attach_set_cookie_header(
|
||||
@@ -75,6 +82,9 @@ fn map_password_entry_error(error: PasswordEntryError) -> AppError {
|
||||
PasswordEntryError::InvalidCredentials => {
|
||||
AppError::from_status(StatusCode::UNAUTHORIZED).with_message("用户名或密码错误")
|
||||
}
|
||||
PasswordEntryError::UserNotFound => {
|
||||
AppError::from_status(StatusCode::UNAUTHORIZED).with_message("用户名或密码错误")
|
||||
}
|
||||
PasswordEntryError::Store(_) | PasswordEntryError::PasswordHash(_) => {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(error.to_string())
|
||||
}
|
||||
|
||||
117
server-rs/crates/api-server/src/password_management.rs
Normal file
117
server-rs/crates/api-server/src/password_management.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use module_auth::{ChangePasswordInput, PasswordEntryError, ResetPasswordInput};
|
||||
use shared_contracts::auth::{
|
||||
PasswordChangeRequest, PasswordChangeResponse, PasswordResetRequest, PasswordResetResponse,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
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,
|
||||
phone_auth::map_phone_auth_error,
|
||||
request_context::RequestContext,
|
||||
session_client::resolve_session_client_context,
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
pub async fn change_password(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
Json(payload): Json<PasswordChangeRequest>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let result = state
|
||||
.password_entry_service()
|
||||
.change_password(ChangePasswordInput {
|
||||
user_id: authenticated.claims().user_id().to_string(),
|
||||
current_password: payload.current_password,
|
||||
new_password: payload.new_password,
|
||||
})
|
||||
.await
|
||||
.map_err(map_password_management_error)?;
|
||||
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
PasswordChangeResponse {
|
||||
user: map_auth_user_payload(result.user),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn reset_password(
|
||||
State(state): State<AppState>,
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
headers: HeaderMap,
|
||||
Json(payload): Json<PasswordResetRequest>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
if !state.config.sms_auth_enabled {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_message("手机号登录暂未启用")
|
||||
);
|
||||
}
|
||||
|
||||
let result = state
|
||||
.phone_auth_service()
|
||||
.reset_password(
|
||||
ResetPasswordInput {
|
||||
phone_number: payload.phone,
|
||||
verify_code: payload.code,
|
||||
new_password: payload.new_password,
|
||||
},
|
||||
OffsetDateTime::now_utc(),
|
||||
)
|
||||
.await
|
||||
.map_err(map_phone_auth_error)?;
|
||||
let session_client = resolve_session_client_context(&headers);
|
||||
let signed_session = create_auth_session(
|
||||
&state,
|
||||
&result.user,
|
||||
&session_client,
|
||||
module_auth::AuthLoginMethod::Password,
|
||||
)?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
attach_set_cookie_header(
|
||||
&mut headers,
|
||||
build_refresh_session_cookie_header(&state, &signed_session.refresh_token)?,
|
||||
);
|
||||
|
||||
Ok((
|
||||
headers,
|
||||
json_success_body(
|
||||
Some(&request_context),
|
||||
PasswordResetResponse {
|
||||
token: signed_session.access_token,
|
||||
user: map_auth_user_payload(result.user),
|
||||
},
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn map_password_management_error(error: PasswordEntryError) -> AppError {
|
||||
match error {
|
||||
PasswordEntryError::InvalidUsername | PasswordEntryError::InvalidPublicUserCode => {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_message(error.to_string())
|
||||
}
|
||||
PasswordEntryError::InvalidPasswordLength => AppError::from_status(StatusCode::BAD_REQUEST)
|
||||
.with_message("密码长度需要在 6 到 128 位之间"),
|
||||
PasswordEntryError::InvalidCredentials => {
|
||||
AppError::from_status(StatusCode::UNAUTHORIZED).with_message("当前密码错误")
|
||||
}
|
||||
PasswordEntryError::UserNotFound => AppError::from_status(StatusCode::UNAUTHORIZED)
|
||||
.with_message("当前登录态已失效,请重新登录"),
|
||||
PasswordEntryError::Store(_) | PasswordEntryError::PasswordHash(_) => {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,13 @@ pub async fn phone_login(
|
||||
&session_client,
|
||||
AuthLoginMethod::Phone,
|
||||
)?;
|
||||
state
|
||||
.sync_auth_store_snapshot_to_spacetime()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_message(format!("同步认证快照失败:{error}"))
|
||||
})?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
attach_set_cookie_header(
|
||||
@@ -177,6 +184,7 @@ fn map_phone_auth_scene(raw_scene: Option<&str>) -> Result<PhoneAuthScene, AppEr
|
||||
"login" => Ok(PhoneAuthScene::Login),
|
||||
"bind_phone" => Ok(PhoneAuthScene::BindPhone),
|
||||
"change_phone" => Ok(PhoneAuthScene::ChangePhone),
|
||||
"reset_password" => Ok(PhoneAuthScene::ResetPassword),
|
||||
_ => Err(AppError::from_status(StatusCode::BAD_REQUEST)
|
||||
.with_message("短信验证码场景不合法")
|
||||
.with_details(json!({ "field": "scene" }))),
|
||||
@@ -214,7 +222,7 @@ fn mask_phone_digits(value: &str) -> String {
|
||||
masked
|
||||
}
|
||||
|
||||
fn map_phone_auth_error(error: PhoneAuthError) -> AppError {
|
||||
pub fn map_phone_auth_error(error: PhoneAuthError) -> AppError {
|
||||
match error {
|
||||
PhoneAuthError::InvalidPhoneNumber
|
||||
| PhoneAuthError::InvalidVerifyCode
|
||||
|
||||
@@ -54,6 +54,13 @@ pub async fn refresh_session(
|
||||
&rotated.session.session_id,
|
||||
Some(&rotated.session.issued_by_provider),
|
||||
)?;
|
||||
state
|
||||
.sync_auth_store_snapshot_to_spacetime()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
AppError::from_status(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_message(format!("同步认证快照失败:{error}"))
|
||||
})?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
attach_set_cookie_header(
|
||||
|
||||
@@ -21,6 +21,7 @@ use platform_oss::{OssClient, OssConfig, OssError};
|
||||
use serde_json::Value;
|
||||
use spacetime_client::{SpacetimeClient, SpacetimeClientConfig, SpacetimeClientError};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::config::AppConfig;
|
||||
use crate::wechat_provider::{WechatProvider, build_wechat_provider};
|
||||
@@ -37,6 +38,7 @@ pub struct AppState {
|
||||
admin_runtime: Option<AdminRuntime>,
|
||||
refresh_cookie_config: RefreshCookieConfig,
|
||||
oss_client: Option<OssClient>,
|
||||
auth_store: InMemoryAuthStore,
|
||||
password_entry_service: PasswordEntryService,
|
||||
refresh_session_service: RefreshSessionService,
|
||||
auth_user_service: AuthUserService,
|
||||
@@ -86,6 +88,7 @@ pub struct AdminSession {
|
||||
pub enum AppStateInitError {
|
||||
Jwt(JwtError),
|
||||
RefreshCookie(RefreshCookieError),
|
||||
AuthStore(String),
|
||||
SmsProvider(SmsProviderError),
|
||||
Oss(OssError),
|
||||
Llm(LlmError),
|
||||
@@ -93,6 +96,15 @@ pub enum AppStateInitError {
|
||||
|
||||
impl AppState {
|
||||
pub fn new(config: AppConfig) -> Result<Self, AppStateInitError> {
|
||||
let auth_store = InMemoryAuthStore::from_persistence_path(config.auth_store_path.clone())
|
||||
.map_err(AppStateInitError::AuthStore)?;
|
||||
Self::new_with_auth_store(config, auth_store)
|
||||
}
|
||||
|
||||
fn new_with_auth_store(
|
||||
config: AppConfig,
|
||||
auth_store: InMemoryAuthStore,
|
||||
) -> Result<Self, AppStateInitError> {
|
||||
let auth_jwt_config = JwtConfig::new(
|
||||
config.jwt_issuer.clone(),
|
||||
config.jwt_secret.clone(),
|
||||
@@ -111,7 +123,6 @@ impl AppState {
|
||||
config.refresh_session_ttl_days,
|
||||
)?;
|
||||
let oss_client = build_oss_client(&config)?;
|
||||
let auth_store = InMemoryAuthStore::default();
|
||||
let sms_provider = SmsAuthProvider::new(SmsAuthConfig::new(
|
||||
SmsAuthProviderKind::parse(&config.sms_auth_provider).ok_or_else(|| {
|
||||
SmsProviderError::InvalidConfig("短信 provider 配置非法".to_string())
|
||||
@@ -141,7 +152,7 @@ impl AppState {
|
||||
let wechat_auth_service = WechatAuthService::new(auth_store.clone());
|
||||
let wechat_provider = build_wechat_provider(&config);
|
||||
let refresh_session_service =
|
||||
RefreshSessionService::new(auth_store, config.refresh_session_ttl_days);
|
||||
RefreshSessionService::new(auth_store.clone(), config.refresh_session_ttl_days);
|
||||
// AI 编排服务当前先挂接内存态 store,后续再按 task table / procedure 接到 SpacetimeDB 真相源。
|
||||
let ai_task_service = AiTaskService::new(InMemoryAiTaskStore::default());
|
||||
let spacetime_client = SpacetimeClient::new(SpacetimeClientConfig {
|
||||
@@ -158,6 +169,7 @@ impl AppState {
|
||||
admin_runtime,
|
||||
refresh_cookie_config,
|
||||
oss_client,
|
||||
auth_store,
|
||||
password_entry_service,
|
||||
refresh_session_service,
|
||||
auth_user_service,
|
||||
@@ -193,6 +205,49 @@ impl AppState {
|
||||
&self.password_entry_service
|
||||
}
|
||||
|
||||
pub async fn sync_auth_store_snapshot_to_spacetime(&self) -> Result<(), SpacetimeClientError> {
|
||||
let snapshot_json = self
|
||||
.auth_store
|
||||
.export_snapshot_json()
|
||||
.map_err(SpacetimeClientError::Runtime)?;
|
||||
let updated_at_micros = i64::try_from(
|
||||
OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000,
|
||||
)
|
||||
.map_err(|_| SpacetimeClientError::Runtime("认证快照更新时间超出 i64 范围".to_string()))?;
|
||||
self.spacetime_client
|
||||
.upsert_auth_store_snapshot(snapshot_json, updated_at_micros)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn try_restore_auth_store_from_spacetime(
|
||||
config: AppConfig,
|
||||
) -> Result<Self, AppStateInitError> {
|
||||
let spacetime_client = SpacetimeClient::new(SpacetimeClientConfig {
|
||||
server_url: config.spacetime_server_url.clone(),
|
||||
database: config.spacetime_database.clone(),
|
||||
token: config.spacetime_token.clone(),
|
||||
pool_size: config.spacetime_pool_size,
|
||||
});
|
||||
match spacetime_client.get_auth_store_snapshot().await {
|
||||
Ok(snapshot) => {
|
||||
if let Some(snapshot_json) = snapshot.snapshot_json {
|
||||
if !snapshot_json.trim().is_empty() {
|
||||
let auth_store = InMemoryAuthStore::from_snapshot_json(&snapshot_json)
|
||||
.map_err(AppStateInitError::AuthStore)?;
|
||||
info!("已从 SpacetimeDB 恢复认证快照");
|
||||
return Self::new_with_auth_store(config, auth_store);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(error = %error, "从 SpacetimeDB 恢复认证快照失败,回退到本地快照");
|
||||
}
|
||||
}
|
||||
|
||||
Self::new(config)
|
||||
}
|
||||
|
||||
pub fn refresh_session_service(&self) -> &RefreshSessionService {
|
||||
&self.refresh_session_service
|
||||
}
|
||||
@@ -392,6 +447,7 @@ impl fmt::Display for AppStateInitError {
|
||||
match self {
|
||||
Self::Jwt(error) => write!(f, "{error}"),
|
||||
Self::RefreshCookie(error) => write!(f, "{error}"),
|
||||
Self::AuthStore(error) => write!(f, "{error}"),
|
||||
Self::SmsProvider(error) => write!(f, "{error}"),
|
||||
Self::Oss(error) => write!(f, "{error}"),
|
||||
Self::Llm(error) => write!(f, "{error}"),
|
||||
|
||||
@@ -135,6 +135,13 @@ pub async fn handle_wechat_callback(
|
||||
&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,
|
||||
&[
|
||||
@@ -187,6 +194,13 @@ pub async fn bind_wechat_phone(
|
||||
&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(
|
||||
|
||||
Reference in New Issue
Block a user