Skip to main content

futu_backend/auth/device/
identity.rs

1use super::credentials_lock::with_credentials_exclusive_path;
2use super::storage::{DirEnforceError, try_credentials_path, try_device_id_path};
3use super::write_secret_file;
4
5#[derive(Debug)]
6pub enum DeviceStoreError {
7    Dir(String),
8}
9
10impl std::fmt::Display for DeviceStoreError {
11    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12        match self {
13            DeviceStoreError::Dir(message) => write!(f, "{message}"),
14        }
15    }
16}
17
18impl std::error::Error for DeviceStoreError {}
19
20fn device_store_dir_error(err: DirEnforceError) -> DeviceStoreError {
21    DeviceStoreError::Dir(err.to_string())
22}
23
24fn dir_enforce_to_io_error(err: DirEnforceError) -> std::io::Error {
25    std::io::Error::other(err.to_string())
26}
27
28/// 读取 device_id;文件不存在则生成新的 16-hex 随机值并持久化。
29///
30/// 如果 `override_value` 是 `Some(hex)`(来自 `--device-id` CLI 参数),
31/// 直接用并更新文件。
32pub fn read_or_generate_device_id(
33    account: &str,
34    override_value: Option<&str>,
35) -> Result<String, DeviceStoreError> {
36    let path = try_device_id_path(account).map_err(device_store_dir_error)?;
37
38    if let Some(explicit) = override_value {
39        // 用户显式指定 —— 更新持久化文件 (v1.4.102 BUG-012: 0600 secret-file)
40        if write_secret_file(&path, explicit.as_bytes()).is_err() {
41            tracing::warn!(path = %path.display(), "failed to persist --device-id override");
42        } else {
43            // v1.4.106 codex 0558 F2: log fingerprint, 不写 raw device_id
44            tracing::info!(
45                path = %path.display(),
46                device_id_fp = %super::super::redact::device_id_log_fingerprint(explicit),
47                "device_id overridden by --device-id and persisted"
48            );
49        }
50        return Ok(explicit.to_string());
51    }
52
53    // 文件已存在 → 读取
54    if let Ok(content) = std::fs::read_to_string(&path) {
55        let trimmed = content.trim().to_string();
56        if trimmed.len() == 16 && trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
57            tracing::debug!(path = %path.display(), "loaded existing device_id");
58            return Ok(trimmed);
59        }
60        tracing::warn!(
61            path = %path.display(),
62            "device_id file contents invalid, regenerating"
63        );
64    }
65
66    // 首次运行 / 文件损坏 → 生成随机 16-hex
67    let device_id = {
68        let bytes: [u8; 8] = rand::random();
69        format!("{:x}", u64::from_ne_bytes(bytes))
70            .chars()
71            .chain(std::iter::repeat('0'))
72            .take(16)
73            .collect::<String>()
74    };
75    // v1.4.102 BUG-012: device_id 也是 secret (用于设备识别), 0600
76    if write_secret_file(&path, device_id.as_bytes()).is_err() {
77        tracing::warn!(path = %path.display(), "failed to persist device_id");
78    } else {
79        // v1.4.106 codex 0558 F2: log fingerprint, 不写 raw device_id
80        tracing::info!(
81            path = %path.display(),
82            device_id_fp = %super::super::redact::device_id_log_fingerprint(&device_id),
83            "generated and persisted new device_id"
84        );
85    }
86    Ok(device_id)
87}
88
89/// 删除 device_id 文件 + credentials 文件(`--reset-device` 使用)。
90///
91/// device_id 被服务端锁定后必须换新的 —— 单纯改密码无法恢复。
92pub fn reset_device_state(account: &str) -> std::io::Result<()> {
93    let dev_path = try_device_id_path(account).map_err(dir_enforce_to_io_error)?;
94    let cred_path = try_credentials_path(account).map_err(dir_enforce_to_io_error)?;
95    let mut removed = Vec::new();
96    if dev_path.exists() {
97        std::fs::remove_file(&dev_path)?;
98        removed.push(dev_path.display().to_string());
99    }
100    let credential_removed =
101        with_credentials_exclusive_path(&cred_path, || match std::fs::remove_file(&cred_path) {
102            Ok(()) => Ok(true),
103            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
104            Err(error) => Err(error),
105        })
106        .map_err(|error| std::io::Error::other(error.to_string()))??;
107    if credential_removed {
108        removed.push(cred_path.display().to_string());
109    }
110    if removed.is_empty() {
111        tracing::info!("reset_device: no existing device/credentials files to remove");
112    } else {
113        tracing::info!(files = ?removed, "reset_device: removed device and credentials files");
114    }
115    Ok(())
116}