未检测到可用项目
您的账号尚未关联至任何研究项目,请联系系统管理员进行项目授权与分配。
diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py
index b6824db5..14d7690d 100644
--- a/backend/app/api/v1/auth.py
+++ b/backend/app/api/v1/auth.py
@@ -9,7 +9,7 @@ import uuid
from app.core.config import settings
from app.core.login_crypto import create_login_challenge, decrypt_login_payload, get_public_key_pem
-from app.core.request_context import resolve_ctms_client_type
+from app.core.request_context import resolve_client_ip, resolve_ctms_client_type
from app.core.security import create_access_token, decode_token, decode_token_allow_expired, oauth2_scheme, verify_password
from app.core.deps import get_current_user, get_db_session
from app.crud import user as user_crud
@@ -379,6 +379,7 @@ async def heartbeat_login_session(
return {
"status": "online",
"last_seen_at": session.last_seen_at.isoformat(),
+ "client_ip": resolve_client_ip(request),
}
diff --git a/backend/tests/test_user_login_sessions.py b/backend/tests/test_user_login_sessions.py
index 343ace6e..e1420621 100644
--- a/backend/tests/test_user_login_sessions.py
+++ b/backend/tests/test_user_login_sessions.py
@@ -1,5 +1,6 @@
from datetime import datetime, timedelta, timezone
import uuid
+from pathlib import Path
import pytest
@@ -20,6 +21,13 @@ def test_legacy_session_id_is_stable_without_storing_a_token():
assert isinstance(first, uuid.UUID)
+def test_session_heartbeat_returns_server_observed_client_ip():
+ auth_source = (Path(__file__).resolve().parents[1] / "app" / "api" / "v1" / "auth.py").read_text()
+
+ assert "resolve_client_ip(request)" in auth_source
+ assert '"client_ip": resolve_client_ip(request)' in auth_source
+
+
@pytest.mark.asyncio
async def test_login_summary_marks_recent_unended_sessions_online(db_session, monkeypatch):
now = datetime.now(timezone.utc)
diff --git a/frontend/src/api/authClient.ts b/frontend/src/api/authClient.ts
index e7d6da99..39a07541 100644
--- a/frontend/src/api/authClient.ts
+++ b/frontend/src/api/authClient.ts
@@ -36,6 +36,7 @@ export type LoginKeyResponse = {
export type SessionHeartbeatResponse = {
status: "online";
last_seen_at: string;
+ client_ip: string | null;
};
export type EncryptedPasswordRequest = {
diff --git a/frontend/src/components/AccountConnectionStatus.test.ts b/frontend/src/components/AccountConnectionStatus.test.ts
index 5b30f13b..d2d59402 100644
--- a/frontend/src/components/AccountConnectionStatus.test.ts
+++ b/frontend/src/components/AccountConnectionStatus.test.ts
@@ -14,6 +14,22 @@ describe("account connection status", () => {
expect(component).toContain("当前账号与连接状态");
expect(component).toContain("API 延迟");
expect(component).toContain("会话状态");
+ expect(component).toContain("mode === 'network'");
+ expect(component).toContain("当前 IP");
+ expect(component).toContain("clientIpLabel");
+ expect(component).toContain("连接安全检查");
+ expect(component).toContain("runSecurityCheck");
+ expect(component).toContain("evaluateConnectionSecurity");
+ expect(component).toContain("const securityChecked = ref(true)");
+ expect(component).toContain("const securityCheckExpanded = ref(true)");
+ expect(component).toContain("const nowTs = ref(Date.now())");
+ expect(component).toContain("const tokenExpiryAt = computed");
+ expect(component).toContain("const credentialDeadlineAt = computed");
+ expect(component).toContain("return session.lastUserActiveAt + IDLE_TIMEOUT_MINUTES * 60 * 1000");
+ expect(component).toContain("sessionDeadlineAt: credentialDeadlineAt.value");
+ expect(component).toContain("Math.min(IDLE_TIMEOUT_MINUTES, calculatedMinutes)");
+ expect(component).toContain("sessionClockTimer !== undefined");
+ expect(component).toContain("window.clearInterval(sessionClockTimer)");
expect(component).toContain("不代表系统健康评分");
expect(component).not.toContain("ip_address");
expect(component).not.toContain("access_token");
diff --git a/frontend/src/components/AccountConnectionStatus.vue b/frontend/src/components/AccountConnectionStatus.vue
index 72cca195..add7dab4 100644
--- a/frontend/src/components/AccountConnectionStatus.vue
+++ b/frontend/src/components/AccountConnectionStatus.vue
@@ -9,6 +9,43 @@
{{ statusLabel }}
+
+
+
+
+
+
您的账号尚未关联至任何研究项目,请联系系统管理员进行项目授权与分配。