Skip to main content

futu_backend/auth/
credentials_refresh.rs

1use crate::conn::BackendProtocolIdentity;
2use futu_core::error::FutuError;
3
4mod remember_session;
5
6use remember_session::{CachedRememberLoginSessionPurpose, load_cached_remember_login_session};
7
8use super::device::save_credentials;
9use super::redact::{account_log_fingerprint, uid_log_fingerprint};
10use super::remember::{RememberLoginInput, remember_login};
11use super::{AuthResult, UserAttribution};
12
13/// Existing authority refresh network budget, moved verbatim from the retired
14/// `auth/refresh.rs` owner. This is an operational bound rather than a protocol
15/// constant: networks taking longer than 10s fail this attempt and rely on the
16/// caller's existing backoff; replace it when authority timeout config becomes
17/// server-driven.
18pub const REFRESH_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
19
20/// v1.4.34: daemon-reload 升级(A' 方案)的产出。
21///
22/// 走一次 `remember_login` 用缓存凭据刷新 tgtgt,并把同一 authority 响应解析出的
23/// 完整 [`AuthResult`] 交给上层中央 runtime 发布。磁盘与内存更新必须来自同一响应,
24/// 不允许为了取得新 webSig 再发第二次 authority 请求。
25#[derive(Debug, Clone)]
26pub struct RefreshCredentialsReport {
27    /// 是否把新凭据成功写回了 credentials 文件
28    pub credentials_refreshed: bool,
29    /// 服务端返回的新 uid(大部分场景等于旧 uid,拿来 sanity check)
30    pub uid: u64,
31    /// 同一次 authority 成功响应里的完整平台票据。
32    pub auth_result: AuthResult,
33}
34
35/// v1.4.34: 给 `daemon-reload` 升级用——用磁盘缓存的 `(uid, tgtgt, device_sig,
36/// rand_key)` 走一次 `remember_login`,成功则把新 tgtgt 写回 credentials 文件。
37///
38/// **安全边界**:不保留 plaintext 密码;本层返回完整 authority 结果,由
39/// gateway 中央 runtime 负责原子发布,backend 不持有运行时状态。
40///
41/// **失败场景**:
42/// - credentials 文件不存在 → `Err`(调用方应回退 "shutdown + restart")
43/// - tgtgt 过期(服务端拒)→ `Err`(同上)
44/// - 服务端返 code=20(重新 SMS 验证)→ `Err`(daemon 运行中不可能交互 SMS)
45pub async fn refresh_credentials_on_disk(
46    http: &reqwest::Client,
47    account: &str,
48    device_id: &str,
49    protocol_identity: BackendProtocolIdentity,
50    region_code: Option<&str>,
51    attribution: UserAttribution,
52) -> std::result::Result<RefreshCredentialsReport, FutuError> {
53    let session = load_cached_remember_login_session(
54        account,
55        device_id,
56        protocol_identity,
57        CachedRememberLoginSessionPurpose::AdminReload { region_code },
58    )?;
59
60    let (auth_result, new_cred_opt) = remember_login(
61        http,
62        RememberLoginInput {
63            config: &session.config,
64            region_code: session.region_code.as_deref(),
65            attribution,
66            uid: session.credentials.uid,
67            device_id,
68            device_sig: &session.credentials.device_sig,
69            tgtgt: &session.credentials.tgtgt,
70            rand_key: &session.rand_key,
71            web_sig: &session.credentials.web_sig,
72            moomoo_web_sig: &session.credentials.moomoo_web_sig,
73            verify_cb: None,
74            primary_webtcp: None,
75        },
76    )
77    .await?;
78    let credentials_refreshed = if let Some(new_cred) = new_cred_opt {
79        let uid = new_cred.uid;
80        // v1.4.106 codex 0558 F1: write IO 错 propagate, 不 silent drop
81        save_credentials(account, &new_cred).map_err(|e| {
82            FutuError::Codec(format!(
83                "admin reload: save_credentials failed — {e} (cred not on disk; \
84                 next startup will re-auth via password)"
85            ))
86        })?;
87        // v1.4.106 codex 0558 F2+F3: log fingerprint 替代 raw account/uid
88        tracing::info!(
89            account_fp = %account_log_fingerprint(account),
90            uid_fp = %uid_log_fingerprint(uid),
91            "admin reload: credentials refreshed on disk"
92        );
93        true
94    } else {
95        // 服务端成功但没返新凭据——老的仍然有效,算不刷
96        tracing::debug!(
97            account_fp = %account_log_fingerprint(account),
98            "admin reload: remember_login ok but no fresh credentials (still valid)"
99        );
100        false
101    };
102    Ok(RefreshCredentialsReport {
103        credentials_refreshed,
104        uid: auth_result.user_id,
105        auth_result,
106    })
107}