Skip to main content

futu_backend/auth/
device.rs

1//! device_id 持久化 + credentials 文件管理
2//!
3//! 统一存储根目录 `~/.futu-opend-rs/`(对齐 C++ `~/.com.futunn.FutuOpenD/`)。
4//! v1.4.17 起把 credentials 从 cwd 下的 `.futu_credentials_{account}` 搬过来,
5//! 并把 device_id 单独持久化到 `device-<hash>.dat`。
6//!
7//! 生命周期(见 CLAUDE.md "device_id 生命周期"):
8//! - 首次启动 → 随机生成 16-hex → 写文件
9//! - 后续启动 → 读文件
10//! - `--device-id <hex>` → 覆盖文件 + 用这个值
11//! - `--reset-device` → 删 device + credentials 文件
12//! - SMS `error_code=21` → `authenticate_with_callback` 自动 reset + 重试
13
14mod credentials_load;
15mod credentials_lock;
16mod credentials_schema;
17mod credentials_store;
18mod identity;
19mod permission_tighten;
20mod storage;
21mod ticket_status;
22mod time;
23mod verify_cache;
24mod websig_store;
25
26#[cfg(test)]
27pub(super) use credentials_load::CredentialsSnapshotError;
28pub(super) use credentials_load::{
29    ConditionalCleanupError, discard_credentials_if_generation_matches, load_credentials,
30    load_credentials_snapshot,
31};
32pub(super) use credentials_schema::{CURRENT_CREDENTIALS_SCHEMA_VERSION, SavedCredentials};
33#[cfg(test)]
34use credentials_store::save_credentials_to_path;
35pub(in crate::auth) use credentials_store::{CredentialsStoreError, save_credentials};
36pub use identity::{DeviceStoreError, read_or_generate_device_id, reset_device_state};
37pub use permission_tighten::tighten_secret_files_at_startup;
38#[cfg(test)]
39use storage::cleanup_remove_file;
40pub(super) use storage::ensure_dir_0700;
41pub(crate) use storage::{try_futu_opend_dir, write_secret_file};
42pub(super) use ticket_status::credential_ticket_status;
43#[cfg(test)]
44use ticket_status::credential_ticket_status_from_saved;
45pub(super) use time::auth_device_now_secs_or_zero;
46#[cfg(test)]
47pub(super) use verify_cache::device_verify_binding_sha256;
48pub(super) use verify_cache::{
49    DEVICE_CODE_SIG_TTL_SECS, DEVICE_VERIFY_SIG_TTL_SECS, FirstAuthContext,
50    cached_device_verify_binding_matches, clear_cached_device_verification_state,
51    discard_cached_credentials_for_clean_auth, fresh_cached_device_code_sig,
52    fresh_cached_device_verify_sig, persist_device_code_sig, persist_device_verify_sig,
53};
54
55/// Remove only the persisted remember-login ticket for `account` while keeping
56/// the stable device identity. Interactive login uses this after a successful
57/// one-shot session when the operator answers "do not remember".
58pub fn forget_cached_credentials(account: &str) -> Result<(), String> {
59    discard_cached_credentials_for_clean_auth(account)
60}
61pub use websig_store::{WebSigPersistenceError, persist_broker_websig, persist_platform_websigs};
62
63#[cfg(test)]
64pub(super) use credentials_lock::{
65    lock_path_for as credentials_lock_path_for_test,
66    with_credentials_exclusive_path as with_credentials_exclusive_path_for_test,
67};
68#[cfg(test)]
69pub(super) use storage::{account_key, credentials_path, device_id_path, futu_opend_dir};
70
71#[cfg(all(test, unix))]
72pub(super) use storage::cleanup_set_permissions_0600;
73
74#[cfg(test)]
75mod tests;