steam_vent/auth/
guard_data.rs

1use directories::ProjectDirs;
2use std::collections::HashMap;
3use std::convert::Infallible;
4use std::error::Error;
5use std::fs::{create_dir_all, read_to_string, write};
6use std::path::PathBuf;
7use thiserror::Error;
8
9/// Trait for storing steam guard machine tokens
10pub trait GuardDataStore {
11    type Err: Error;
12
13    /// Store a machine token for an account
14    fn store(
15        &mut self,
16        account: &str,
17        machine_token: String,
18    ) -> impl std::future::Future<Output = Result<(), Self::Err>> + Send;
19
20    /// Retrieve the stored token for an account
21    fn load(
22        &mut self,
23        account: &str,
24    ) -> impl std::future::Future<Output = Result<Option<String>, Self::Err>> + Send;
25}
26
27/// Error while storing or loading guard data from json file
28#[derive(Debug, Error)]
29#[non_exhaustive]
30pub enum FileStoreError {
31    /// Error while reading the json file
32    #[error("error while reading tokens from {}: {:#}", path.display(), err)]
33    Read { err: std::io::Error, path: PathBuf },
34    /// Error while writing the json file
35    #[error("error while writing tokens to {}: {:#}", path.display(), err)]
36    Write { err: std::io::Error, path: PathBuf },
37    /// Error when encoding or decoding the tokens
38    #[error("error while parsing tokens from {}: {:#}", path.display(), err)]
39    Json {
40        err: serde_json::error::Error,
41        path: PathBuf,
42    },
43    /// Error while creating the parent directory of the file
44    #[error("error while directory {} for tokens: {:#}", path.display(), err)]
45    DirCreation { err: std::io::Error, path: PathBuf },
46}
47
48/// Store the steam guard data in a json file
49pub struct FileGuardDataStore {
50    path: PathBuf,
51}
52
53impl FileGuardDataStore {
54    /// Store the machine token at the provided path
55    pub fn new(path: PathBuf) -> Self {
56        FileGuardDataStore { path }
57    }
58
59    /// Store the machine tokens in the user's cache directory
60    ///
61    /// This will be
62    /// - `$XDG_CACHE_HOME/steam-vent/machine_token.json` (where `$XDG_CACHE_HOME` defaults to `$HOME/.cache`) on Linux
63    /// - `$HOME/Library/Caches/steam-vent/steam-vent.steam-vent/machine_token.json` on macOS
64    /// - `%LocalAppData%/steam-vent/steam-vent/cache/machine_token.json` on Windows
65    pub fn user_cache() -> Self {
66        let project_dirs = ProjectDirs::from("", "steam-vent", "steam-vent")
67            .expect("user cache not supported on this platform");
68        Self::new(project_dirs.cache_dir().join("machine_tokens.json"))
69    }
70
71    fn all_tokens(&self) -> Result<HashMap<String, String>, FileStoreError> {
72        if !self.path.exists() {
73            return Ok(HashMap::default());
74        }
75        let raw = read_to_string(&self.path).map_err(|err| FileStoreError::Read {
76            err,
77            path: self.path.clone(),
78        })?;
79        serde_json::from_str(&raw).map_err(|err| FileStoreError::Json {
80            err,
81            path: self.path.clone(),
82        })
83    }
84
85    fn save(&self, tokens: HashMap<String, String>) -> Result<(), FileStoreError> {
86        if let Some(parent) = self.path.parent() {
87            create_dir_all(parent).map_err(|err| FileStoreError::DirCreation {
88                err,
89                path: parent.into(),
90            })?;
91        }
92
93        let raw = serde_json::to_string(&tokens).map_err(|err| FileStoreError::Json {
94            err,
95            path: self.path.clone(),
96        })?;
97        write(&self.path, raw).map_err(|err| FileStoreError::Write {
98            err,
99            path: self.path.clone(),
100        })?;
101        Ok(())
102    }
103}
104
105impl GuardDataStore for FileGuardDataStore {
106    type Err = FileStoreError;
107
108    async fn store(&mut self, account: &str, machine_token: String) -> Result<(), Self::Err> {
109        if !machine_token.is_empty() {
110            let mut tokens = self.all_tokens()?;
111            tokens.insert(account.into(), machine_token);
112            self.save(tokens)
113        } else {
114            Ok(())
115        }
116    }
117
118    async fn load(&mut self, account: &str) -> Result<Option<String>, Self::Err> {
119        let mut tokens = self.all_tokens()?;
120        Ok(tokens.remove(account).filter(|token| !token.is_empty()))
121    }
122}
123
124/// Don't store guard data
125pub struct NullGuardDataStore;
126
127impl GuardDataStore for NullGuardDataStore {
128    type Err = Infallible;
129
130    async fn store(&mut self, _account: &str, _machine_token: String) -> Result<(), Self::Err> {
131        Ok(())
132    }
133
134    async fn load(&mut self, _account: &str) -> Result<Option<String>, Self::Err> {
135        Ok(None)
136    }
137}