use axum::{ Json, extract::{Extension, State}, http::StatusCode, }; use platform_auth::hash_refresh_session_token; use shared_contracts::auth::{AuthSessionSummaryPayload, AuthSessionsResponse}; use time::OffsetDateTime; use crate::{ api_response::json_success_body, auth::{AuthenticatedAccessToken, RefreshSessionToken}, http_error::AppError, request_context::RequestContext, session_client::mask_ip, state::AppState, }; pub async fn auth_sessions( State(state): State, Extension(request_context): Extension, Extension(authenticated): Extension, maybe_refresh_token: Option>, ) -> Result, AppError> { // 当前设备识别仍然依赖 refresh cookie 命中的原始 token,对旧前端行为保持兼容。 let user_id = authenticated.claims().user_id().to_string(); let current_refresh_token_hash = maybe_refresh_token.and_then(|token| { let token = token.0.token().trim(); if token.is_empty() { return None; } Some(hash_refresh_session_token(token)) }); let sessions = state .refresh_session_service() .list_active_sessions_by_user(&user_id, OffsetDateTime::now_utc()) .map_err(map_refresh_session_list_error)?; Ok(json_success_body( Some(&request_context), AuthSessionsResponse { sessions: sessions .sessions .into_iter() .map(|session| { let is_current = current_refresh_token_hash .as_ref() .is_some_and(|hash| session.refresh_token_hash == *hash); let client_label = session.client_info.device_display_name.clone(); AuthSessionSummaryPayload { session_id: session.session_id, client_type: session.client_info.client_type, client_runtime: session.client_info.client_runtime, client_platform: session.client_info.client_platform, client_label, device_display_name: session.client_info.device_display_name, mini_program_app_id: session.client_info.mini_program_app_id, mini_program_env: session.client_info.mini_program_env, user_agent: session.client_info.user_agent, ip_masked: mask_ip(session.client_info.ip.as_deref()), is_current, created_at: session.created_at, last_seen_at: session.last_seen_at, expires_at: session.expires_at, } }) .collect(), }, )) } fn map_refresh_session_list_error(error: module_auth::RefreshSessionError) -> AppError { match error { module_auth::RefreshSessionError::UserNotFound => { AppError::from_status(StatusCode::UNAUTHORIZED) .with_message("当前登录态已失效,请重新登录") } module_auth::RefreshSessionError::MissingToken | module_auth::RefreshSessionError::SessionNotFound | module_auth::RefreshSessionError::SessionExpired => { AppError::from_status(StatusCode::UNAUTHORIZED).with_message(error.to_string()) } module_auth::RefreshSessionError::Store(message) => { AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(message) } } }