修复多端登录互相顶号
单设备退出只撤销当前 refresh session,不再提升账号级 token_version 认证中间件和 refresh 接口在本进程未命中会话时按需刷新 SpacetimeDB 认证工作集 补充多端登录与跨进程会话补水回归测试 同步项目文档和 Hermes 共享决策记录
This commit is contained in:
@@ -3844,6 +3844,111 @@ mod tests {
|
||||
assert_eq!(me_response.status(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn logout_current_device_keeps_other_device_session_alive() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
seed_phone_user_with_password(&state, "13800138031", TEST_PASSWORD).await;
|
||||
let app = build_router(state);
|
||||
|
||||
let first_login_response = password_login_request_with_client(
|
||||
app.clone(),
|
||||
"13800138031",
|
||||
TEST_PASSWORD,
|
||||
"logout-current-device",
|
||||
"203.0.113.41",
|
||||
)
|
||||
.await;
|
||||
let first_refresh_cookie = first_login_response
|
||||
.headers()
|
||||
.get("set-cookie")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.expect("first refresh cookie should exist")
|
||||
.to_string();
|
||||
let first_login_body = first_login_response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.expect("first login body should collect")
|
||||
.to_bytes();
|
||||
let first_access_token = read_access_token(&first_login_body);
|
||||
|
||||
let second_login_response = password_login_request_with_client(
|
||||
app.clone(),
|
||||
"13800138031",
|
||||
TEST_PASSWORD,
|
||||
"logout-other-device",
|
||||
"203.0.113.42",
|
||||
)
|
||||
.await;
|
||||
let second_refresh_cookie = second_login_response
|
||||
.headers()
|
||||
.get("set-cookie")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.expect("second refresh cookie should exist")
|
||||
.to_string();
|
||||
let second_login_body = second_login_response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.expect("second login body should collect")
|
||||
.to_bytes();
|
||||
let second_access_token = read_access_token(&second_login_body);
|
||||
|
||||
let logout_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/auth/logout")
|
||||
.header("authorization", format!("Bearer {first_access_token}"))
|
||||
.header("cookie", first_refresh_cookie)
|
||||
.body(Body::empty())
|
||||
.expect("logout request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("logout request should succeed");
|
||||
assert_eq!(logout_response.status(), StatusCode::OK);
|
||||
|
||||
let first_me_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/auth/me")
|
||||
.header("authorization", format!("Bearer {first_access_token}"))
|
||||
.body(Body::empty())
|
||||
.expect("first me request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("first me request should succeed");
|
||||
assert_eq!(first_me_response.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
let second_me_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/auth/me")
|
||||
.header("authorization", format!("Bearer {second_access_token}"))
|
||||
.body(Body::empty())
|
||||
.expect("second me request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("second me request should succeed");
|
||||
assert_eq!(second_me_response.status(), StatusCode::OK);
|
||||
|
||||
let second_refresh_response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/auth/refresh")
|
||||
.header("cookie", second_refresh_cookie)
|
||||
.body(Body::empty())
|
||||
.expect("second refresh request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("second refresh request should succeed");
|
||||
assert_eq!(second_refresh_response.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn logout_succeeds_without_refresh_cookie_when_bearer_token_is_valid() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
|
||||
Reference in New Issue
Block a user