futu_backend/auth/device/
identity.rs1use 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
28pub 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 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 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 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 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 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 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
89pub 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}