登录返回前同步认证投影
新增 create_auth_session_and_sync,统一密码、手机号和微信登录会话的投影同步时机。 避免登录接口签发的新 access token 在下一次请求被当前或其它节点的 SpacetimeDB 校验拒绝。 补充登录投影同步回归测试。
This commit is contained in:
@@ -21,12 +21,26 @@ pub struct SignedAuthSession {
|
||||
pub refresh_token: String,
|
||||
}
|
||||
|
||||
pub fn create_password_auth_session(
|
||||
/// 创建登录会话并立即同步认证投影。
|
||||
///
|
||||
/// `create_auth_session` 只更新 api-server 进程内的认证工作集。登录响应返回前必须
|
||||
/// 完成一次投影同步,否则客户端拿到的 access token 会在下一次请求经过
|
||||
/// `validate_auth_session` 时被其它节点或当前节点的 SpacetimeDB 校验拒绝。
|
||||
pub async fn create_auth_session_and_sync(
|
||||
state: &AppState,
|
||||
user: &AuthUser,
|
||||
session_client: &SessionClientContext,
|
||||
session_provider: AuthLoginMethod,
|
||||
) -> Result<SignedAuthSession, AppError> {
|
||||
create_auth_session(state, user, session_client, AuthLoginMethod::Password)
|
||||
let signed_session = create_auth_session(state, user, session_client, session_provider)?;
|
||||
state
|
||||
.sync_auth_store_tables_to_spacetime()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_message(format!("同步认证状态失败:{error}"))
|
||||
})?;
|
||||
Ok(signed_session)
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
@@ -193,3 +207,48 @@ fn map_access_token_device_info(client_info: &RefreshSessionClientInfo) -> Acces
|
||||
client_platform: client_info.client_platform.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use axum::http::HeaderMap;
|
||||
|
||||
use super::*;
|
||||
use crate::{config::AppConfig, session_client::resolve_session_client_context};
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_auth_session_and_sync_persists_the_new_session_projection() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let user = state
|
||||
.seed_test_phone_user_with_password("13800138099", "secret123")
|
||||
.await;
|
||||
let session_client = resolve_session_client_context(&HeaderMap::new());
|
||||
|
||||
assert!(
|
||||
!state.test_auth_projection_is_synced(),
|
||||
"the seeded user is a local change until the login path syncs it"
|
||||
);
|
||||
|
||||
let signed_session =
|
||||
create_auth_session_and_sync(&state, &user, &session_client, AuthLoginMethod::Password)
|
||||
.await
|
||||
.expect("auth session should be created and synchronized");
|
||||
|
||||
assert!(state.test_auth_projection_is_synced());
|
||||
let claims = platform_auth::verify_access_token(
|
||||
&signed_session.access_token,
|
||||
state.auth_jwt_config(),
|
||||
)
|
||||
.expect("access token should be valid");
|
||||
assert_eq!(claims.user_id(), user.id);
|
||||
assert!(
|
||||
state
|
||||
.refresh_session_service()
|
||||
.is_session_active_for_user(
|
||||
claims.user_id(),
|
||||
claims.session_id(),
|
||||
time::OffsetDateTime::now_utc(),
|
||||
)
|
||||
.expect("session state should be readable")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
auth_payload::map_auth_user_payload,
|
||||
auth_session::{
|
||||
attach_set_cookie_header, build_refresh_session_cookie_header,
|
||||
create_password_auth_session, record_daily_login_tracking_event_after_auth_success,
|
||||
create_auth_session_and_sync, record_daily_login_tracking_event_after_auth_success,
|
||||
},
|
||||
http_error::AppError,
|
||||
request_context::RequestContext,
|
||||
@@ -80,7 +80,13 @@ pub async fn password_entry(
|
||||
return Err(AppError::from_status(StatusCode::SERVICE_UNAVAILABLE)
|
||||
.with_message("账号服务暂不可用,请稍后重试"));
|
||||
}
|
||||
let signed_session = create_password_auth_session(&state, &result.user, &session_client)?;
|
||||
let signed_session = create_auth_session_and_sync(
|
||||
&state,
|
||||
&result.user,
|
||||
&session_client,
|
||||
AuthLoginMethod::Password,
|
||||
)
|
||||
.await?;
|
||||
record_daily_login_tracking_event_after_auth_success(
|
||||
&state,
|
||||
&request_context,
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
auth_payload::map_auth_user_payload,
|
||||
auth_session::{
|
||||
attach_set_cookie_header, build_clear_refresh_session_cookie_header,
|
||||
build_refresh_session_cookie_header, create_auth_session,
|
||||
build_refresh_session_cookie_header, create_auth_session_and_sync,
|
||||
record_daily_login_tracking_event_after_auth_success,
|
||||
},
|
||||
http_error::AppError,
|
||||
@@ -139,19 +139,13 @@ pub async fn reset_password(
|
||||
return Err(AppError::from_status(StatusCode::SERVICE_UNAVAILABLE)
|
||||
.with_message("账号服务暂不可用,请稍后重试"));
|
||||
}
|
||||
let signed_session = create_auth_session(
|
||||
let signed_session = create_auth_session_and_sync(
|
||||
&state,
|
||||
&result.user,
|
||||
&session_client,
|
||||
module_auth::AuthLoginMethod::Password,
|
||||
)?;
|
||||
state
|
||||
.sync_auth_store_tables_to_spacetime()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_message(format!("同步认证状态失败:{error}"))
|
||||
})?;
|
||||
)
|
||||
.await?;
|
||||
record_daily_login_tracking_event_after_auth_success(
|
||||
&state,
|
||||
&request_context,
|
||||
|
||||
@@ -19,8 +19,8 @@ use crate::{
|
||||
api_response::json_success_body,
|
||||
auth_payload::map_auth_user_payload,
|
||||
auth_session::{
|
||||
attach_set_cookie_header, build_refresh_session_cookie_header, create_auth_session,
|
||||
record_daily_login_tracking_event_after_auth_success,
|
||||
attach_set_cookie_header, build_refresh_session_cookie_header,
|
||||
create_auth_session_and_sync, record_daily_login_tracking_event_after_auth_success,
|
||||
},
|
||||
http_error::AppError,
|
||||
platform_errors::{attach_retry_after, map_phone_auth_platform_store_error},
|
||||
@@ -256,12 +256,13 @@ pub async fn phone_login(
|
||||
return Err(AppError::from_status(StatusCode::SERVICE_UNAVAILABLE)
|
||||
.with_message("账号服务暂不可用,请稍后重试"));
|
||||
}
|
||||
let signed_session = create_auth_session(
|
||||
let signed_session = create_auth_session_and_sync(
|
||||
&state,
|
||||
&result.user,
|
||||
&session_client,
|
||||
AuthLoginMethod::Phone,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
record_daily_login_tracking_event_after_auth_success(
|
||||
&state,
|
||||
&request_context,
|
||||
|
||||
@@ -1309,7 +1309,11 @@ impl AppState {
|
||||
|
||||
pub async fn sync_auth_store_tables_to_spacetime(&self) -> Result<(), SpacetimeClientError> {
|
||||
#[cfg(test)]
|
||||
return Ok(());
|
||||
{
|
||||
self.auth_projection_synced_revision
|
||||
.store(self.auth_store.revision(), Ordering::Release);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
let _sync_guard = self.auth_projection_sync_lock.lock().await;
|
||||
@@ -1769,6 +1773,10 @@ fn creation_entry_feature_gates_require_user_tags(
|
||||
|
||||
#[cfg(test)]
|
||||
impl AppState {
|
||||
pub(crate) fn test_auth_projection_is_synced(&self) -> bool {
|
||||
self.auth_projection_synced_revision.load(Ordering::Acquire) == self.auth_store.revision()
|
||||
}
|
||||
|
||||
pub(crate) fn seed_test_refresh_session_for_user(
|
||||
&self,
|
||||
user: &module_auth::AuthUser,
|
||||
|
||||
@@ -24,8 +24,8 @@ use crate::{
|
||||
auth::AuthenticatedAccessToken,
|
||||
auth_payload::map_auth_user_payload,
|
||||
auth_session::{
|
||||
attach_set_cookie_header, build_refresh_session_cookie_header, create_auth_session,
|
||||
record_daily_login_tracking_event_after_auth_success,
|
||||
attach_set_cookie_header, build_refresh_session_cookie_header,
|
||||
create_auth_session_and_sync, record_daily_login_tracking_event_after_auth_success,
|
||||
},
|
||||
http_error::AppError,
|
||||
platform_errors::{attach_retry_after, map_wechat_provider_error},
|
||||
@@ -318,12 +318,13 @@ pub async fn handle_wechat_callback(
|
||||
))
|
||||
.into_response());
|
||||
}
|
||||
let signed_session = create_auth_session(
|
||||
let signed_session = create_auth_session_and_sync(
|
||||
&state,
|
||||
&result.user,
|
||||
&session_client,
|
||||
session_provider.clone(),
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
record_daily_login_tracking_event_after_auth_success(
|
||||
&state,
|
||||
&request_context,
|
||||
@@ -451,12 +452,13 @@ pub async fn bind_wechat_phone(
|
||||
return Err(AppError::from_status(StatusCode::SERVICE_UNAVAILABLE)
|
||||
.with_message("账号服务暂不可用,请稍后重试"));
|
||||
}
|
||||
let signed_session = create_auth_session(
|
||||
let signed_session = create_auth_session_and_sync(
|
||||
&state,
|
||||
&result.user,
|
||||
&session_client,
|
||||
AuthLoginMethod::Wechat,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
if result.activated_new_user {
|
||||
crate::registration_reward::grant_new_user_registration_wallet_reward(
|
||||
&state,
|
||||
@@ -550,12 +552,13 @@ pub async fn login_wechat_mini_program(
|
||||
return Err(AppError::from_status(StatusCode::SERVICE_UNAVAILABLE)
|
||||
.with_message("账号服务暂不可用,请稍后重试"));
|
||||
}
|
||||
let signed_session = create_auth_session(
|
||||
let signed_session = create_auth_session_and_sync(
|
||||
&state,
|
||||
&result.user,
|
||||
&session_client,
|
||||
AuthLoginMethod::Wechat,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut response_headers = HeaderMap::new();
|
||||
attach_set_cookie_header(
|
||||
|
||||
Reference in New Issue
Block a user