feat: add multi-device session identity
This commit is contained in:
@@ -16,6 +16,7 @@ use crate::{
|
||||
require_bearer_auth,
|
||||
},
|
||||
auth_me::auth_me,
|
||||
auth_sessions::auth_sessions,
|
||||
error_middleware::normalize_error_response,
|
||||
health::health_check,
|
||||
logout::logout,
|
||||
@@ -56,6 +57,18 @@ pub fn build_router(state: AppState) -> Router {
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/auth/sessions",
|
||||
get(auth_sessions)
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
attach_refresh_session_token,
|
||||
))
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/auth/refresh",
|
||||
post(refresh_session).route_layer(middleware::from_fn_with_state(
|
||||
@@ -417,6 +430,121 @@ mod tests {
|
||||
assert!(payload["token"].as_str().is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn auth_sessions_returns_multi_device_session_fields() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
let first_login_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/auth/entry")
|
||||
.header("content-type", "application/json")
|
||||
.header(
|
||||
"user-agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/123.0 Safari/537.36",
|
||||
)
|
||||
.header("x-client-instance-id", "chrome-instance-001")
|
||||
.body(Body::from(
|
||||
serde_json::json!({
|
||||
"username": "guest_sessions_api",
|
||||
"password": "secret123"
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("first login request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("first login should succeed");
|
||||
let first_cookie = first_login_response
|
||||
.headers()
|
||||
.get("set-cookie")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.expect("first cookie should exist")
|
||||
.to_string();
|
||||
let first_body = first_login_response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.expect("first login body should collect")
|
||||
.to_bytes();
|
||||
let first_payload: Value =
|
||||
serde_json::from_slice(&first_body).expect("first login payload should be json");
|
||||
let access_token = first_payload["token"]
|
||||
.as_str()
|
||||
.expect("access token should exist")
|
||||
.to_string();
|
||||
|
||||
let _second_login_response = app
|
||||
.clone()
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/auth/entry")
|
||||
.header("content-type", "application/json")
|
||||
.header("x-client-type", "mini_program")
|
||||
.header("x-client-runtime", "wechat_mini_program")
|
||||
.header("x-client-platform", "android")
|
||||
.header("x-client-instance-id", "mini-instance-001")
|
||||
.header("x-mini-program-app-id", "wx-session-test")
|
||||
.header("x-mini-program-env", "release")
|
||||
.header("user-agent", "Mozilla/5.0 Chrome/123.0 MicroMessenger")
|
||||
.body(Body::from(
|
||||
serde_json::json!({
|
||||
"username": "guest_sessions_api",
|
||||
"password": "secret123"
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.expect("second login request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("second login should succeed");
|
||||
|
||||
let sessions_response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/api/auth/sessions")
|
||||
.header("authorization", format!("Bearer {access_token}"))
|
||||
.header("cookie", first_cookie)
|
||||
.body(Body::empty())
|
||||
.expect("sessions request should build"),
|
||||
)
|
||||
.await
|
||||
.expect("sessions request should succeed");
|
||||
|
||||
assert_eq!(sessions_response.status(), StatusCode::OK);
|
||||
let sessions_body = sessions_response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.expect("sessions body should collect")
|
||||
.to_bytes();
|
||||
let sessions_payload: Value =
|
||||
serde_json::from_slice(&sessions_body).expect("sessions payload should be json");
|
||||
let sessions = sessions_payload["sessions"]
|
||||
.as_array()
|
||||
.expect("sessions should be array");
|
||||
|
||||
assert_eq!(sessions.len(), 2);
|
||||
assert!(sessions.iter().any(|session| {
|
||||
session["clientType"] == Value::String("web_browser".to_string())
|
||||
&& session["clientRuntime"] == Value::String("chrome".to_string())
|
||||
&& session["clientPlatform"] == Value::String("windows".to_string())
|
||||
&& session["deviceDisplayName"] == Value::String("Windows / Chrome".to_string())
|
||||
&& session["isCurrent"] == Value::Bool(true)
|
||||
}));
|
||||
assert!(sessions.iter().any(|session| {
|
||||
session["clientType"] == Value::String("mini_program".to_string())
|
||||
&& session["clientRuntime"] == Value::String("wechat_mini_program".to_string())
|
||||
&& session["miniProgramAppId"] == Value::String("wx-session-test".to_string())
|
||||
&& session["miniProgramEnv"] == Value::String("release".to_string())
|
||||
&& session["deviceDisplayName"] == Value::String("微信小程序 / Android".to_string())
|
||||
&& session["isCurrent"] == Value::Bool(false)
|
||||
}));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn password_entry_reuses_same_user_for_same_credentials() {
|
||||
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
|
||||
|
||||
Reference in New Issue
Block a user