azalea_auth/
cache.rs

1//! Cache auth information
2
3use std::{
4    io,
5    path::Path,
6    time::{SystemTime, UNIX_EPOCH},
7};
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11use tokio::{
12    fs::{self, File},
13    io::{AsyncReadExt, AsyncWriteExt},
14};
15use tracing::{debug, trace};
16
17#[derive(Debug, Error)]
18pub enum CacheError {
19    #[error("Failed to read cache file: {0}")]
20    Read(io::Error),
21    #[error("Failed to write cache file: {0}")]
22    Write(io::Error),
23    #[error("Failed to create cache file directory: {0}")]
24    MkDir(io::Error),
25    #[error("Failed to parse cache file: {0}")]
26    Parse(serde_json::Error),
27}
28
29#[derive(Deserialize, Serialize, Debug)]
30pub struct CachedAccount {
31    #[serde(alias = "email")]
32    pub cache_key: String,
33    /// Microsoft auth
34    pub msa: ExpiringValue<crate::auth::AccessTokenResponse>,
35    /// Xbox Live auth
36    pub xbl: ExpiringValue<crate::auth::XboxLiveAuth>,
37    /// Minecraft auth
38    pub mca: ExpiringValue<crate::auth::MinecraftAuthResponse>,
39    /// The user's Minecraft profile (i.e. username, UUID, skin)
40    pub profile: crate::auth::ProfileResponse,
41}
42
43#[derive(Deserialize, Serialize, Debug)]
44pub struct ExpiringValue<T> {
45    /// Seconds since the UNIX epoch
46    pub expires_at: u64,
47    pub data: T,
48}
49
50impl<T> ExpiringValue<T> {
51    pub fn is_expired(&self) -> bool {
52        self.expires_at
53            < SystemTime::now()
54                .duration_since(UNIX_EPOCH)
55                .unwrap()
56                .as_secs()
57    }
58
59    /// Return the data if it's not expired, otherwise return `None`
60    pub fn get(&self) -> Option<&T> {
61        if self.is_expired() {
62            None
63        } else {
64            Some(&self.data)
65        }
66    }
67}
68
69impl<T: Clone> Clone for ExpiringValue<T> {
70    fn clone(&self) -> Self {
71        Self {
72            expires_at: self.expires_at,
73            data: self.data.clone(),
74        }
75    }
76}
77
78async fn get_entire_cache(cache_file: &Path) -> Result<Vec<CachedAccount>, CacheError> {
79    let mut cache: Vec<CachedAccount> = Vec::new();
80    if cache_file.exists() {
81        let mut cache_file = File::open(cache_file).await.map_err(CacheError::Read)?;
82        // read the file into a string
83        let mut contents = String::new();
84        cache_file
85            .read_to_string(&mut contents)
86            .await
87            .map_err(CacheError::Read)?;
88        cache = serde_json::from_str(&contents).map_err(CacheError::Parse)?;
89    }
90    Ok(cache)
91}
92async fn set_entire_cache(cache_file: &Path, cache: Vec<CachedAccount>) -> Result<(), CacheError> {
93    trace!("saving cache: {:?}", cache);
94
95    if !cache_file.exists() {
96        let cache_file_parent = cache_file
97            .parent()
98            .expect("Cache file is root directory and also doesn't exist.");
99        debug!(
100            "Making cache file parent directory at {}",
101            cache_file_parent.to_string_lossy()
102        );
103        fs::create_dir_all(cache_file_parent)
104            .await
105            .map_err(CacheError::MkDir)?;
106    }
107    let mut cache_file = File::create(cache_file).await.map_err(CacheError::Write)?;
108    let cache = serde_json::to_string_pretty(&cache).map_err(CacheError::Parse)?;
109    cache_file
110        .write_all(cache.as_bytes())
111        .await
112        .map_err(CacheError::Write)?;
113
114    Ok(())
115}
116
117/// Gets cached data for the given cache key.
118///
119/// As a convention, the cache key is usually the email of the account.
120pub async fn get_account_in_cache(cache_file: &Path, cache_key: &str) -> Option<CachedAccount> {
121    let cache = get_entire_cache(cache_file).await.unwrap_or_default();
122    cache
123        .into_iter()
124        .find(|account| account.cache_key == cache_key)
125}
126
127pub async fn set_account_in_cache(
128    cache_file: &Path,
129    cache_key: &str,
130    account: CachedAccount,
131) -> Result<(), CacheError> {
132    let mut cache = get_entire_cache(cache_file).await.unwrap_or_default();
133    cache.retain(|account| account.cache_key != cache_key);
134    cache.push(account);
135    set_entire_cache(cache_file, cache).await
136}