steam_client/
persistence.rs1use std::{collections::HashMap, fs, path::PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::LogOnDetails;
6
7#[derive(Serialize, Deserialize, Clone)]
8struct SavedSession {
9 account_name: String,
10 refresh_token: String,
11 steam_id: u64,
12}
13
14impl std::fmt::Debug for SavedSession {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 f.debug_struct("SavedSession")
17 .field("account_name", &self.account_name)
18 .field("steam_id", &self.steam_id)
19 .field("refresh_token", &"<redacted>")
21 .finish()
22 }
23}
24
25pub struct TokenStore {
26 path: PathBuf,
27}
28
29impl TokenStore {
30 pub fn new(filename: &str) -> Self {
31 Self { path: PathBuf::from(filename) }
32 }
33
34 fn load_all(&self) -> HashMap<String, SavedSession> {
36 if !self.path.exists() {
37 return HashMap::new();
38 }
39 let data = match fs::read_to_string(&self.path) {
40 Ok(d) => d,
41 Err(_) => return HashMap::new(),
42 };
43 serde_json::from_str(&data).unwrap_or_default()
44 }
45
46 pub fn load(&self, account_name: Option<&str>) -> Option<LogOnDetails> {
49 let sessions = self.load_all();
50
51 let session = if let Some(name) = account_name {
52 sessions.get(name)?
53 } else {
54 sessions.values().next()?
56 };
57
58 Some(LogOnDetails {
59 account_name: Some(session.account_name.clone()),
60 refresh_token: Some(session.refresh_token.clone()),
61 ..Default::default()
63 })
64 }
65
66 pub fn save(&self, account_name: String, token: String, steam_id: u64) -> std::io::Result<()> {
72 let mut sessions = self.load_all();
73
74 sessions.insert(
75 account_name.clone(),
76 SavedSession { account_name: account_name.clone(), refresh_token: token, steam_id },
77 );
78
79 let data = serde_json::to_string_pretty(&sessions)?;
80
81 let mut tmp_path = self.path.clone();
83 let mut tmp_name = self.path
84 .file_name()
85 .map(|n| n.to_os_string())
86 .unwrap_or_default();
87 tmp_name.push(".tmp");
88 tmp_path.set_file_name(tmp_name);
89
90 fs::write(&tmp_path, &data)?;
92
93 #[cfg(unix)]
95 {
96 use std::os::unix::fs::PermissionsExt;
97 fs::set_permissions(&tmp_path, fs::Permissions::from_mode(0o600))?;
98 }
99
100 #[cfg(windows)]
106 {
107 tracing::debug!(
108 path = %self.path.display(),
109 "TokenStore::save: ACL restriction on Windows is left to the caller; \
110 rename is still atomic to prevent partial-write exposure"
111 );
112 }
113
114 fs::rename(&tmp_path, &self.path)
117 }
118}