ssh_vault/
cache.rs

1use crate::tools::get_home;
2use anyhow::{Result, anyhow};
3use std::{
4    fs,
5    path::{Path, PathBuf},
6    time::{Duration, SystemTime},
7};
8
9// Load the response from a cache file ~/.ssh/vault/keys/`<key>`
10/// # Errors
11/// Return an error if the cache is older than 30 days
12pub fn get(key: &str) -> Result<String> {
13    let cache = get_cache_path(key)?;
14    if cache.exists() {
15        let metadata = fs::metadata(&cache);
16        let last_modified = metadata.map_or_else(
17            |_| SystemTime::now(),
18            |meta| meta.modified().unwrap_or_else(|_| SystemTime::now()),
19        );
20
21        // Calculate the duration since the file was last modified
22        let duration_since_modified = SystemTime::now()
23            .duration_since(last_modified)
24            .unwrap_or(Duration::from_secs(0));
25
26        // Return an error if the cache is older than 30 days
27        if duration_since_modified > Duration::from_secs(30 * 24 * 60 * 60) {
28            Err(anyhow!("cache expired"))
29        } else {
30            Ok(fs::read_to_string(cache)?)
31        }
32    } else {
33        Err(anyhow!("cache not found"))
34    }
35}
36
37/// Save the response to a cache file ~/.ssh/vault/keys/`<key>`
38/// # Errors
39/// Return an error if the cache file can't be created
40pub fn put(key: &str, response: &str) -> Result<()> {
41    let cache = get_cache_path(key)?;
42    // Create parent directories if they don't exist
43    if let Some(parent_dir) = std::path::Path::new(&cache).parent() {
44        fs::create_dir_all(parent_dir)?;
45    }
46    Ok(fs::write(cache, response)?)
47}
48
49/// Get the path to the cache file ~/.ssh/vault/keys/`<key>`
50/// # Errors
51/// Return an error if we can't get the path to the cache file
52fn get_cache_path(key: &str) -> Result<PathBuf> {
53    let ssh_vault = get_ssh_vault_path()?;
54    Ok(ssh_vault.join("keys").join(key))
55}
56
57/// Get the path to the ssh-vault directory ~/.ssh/vault
58/// # Errors
59/// Return an error if we can't get the path to the ssh-vault directory
60fn get_ssh_vault_path() -> Result<PathBuf> {
61    let home = get_home()?;
62    Ok(Path::new(&home).join(".ssh").join("vault"))
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use std::fs;
69
70    #[test]
71    fn test_get_cache_path() -> Result<(), Box<dyn std::error::Error>> {
72        let cache = get_cache_path("test")?;
73        assert!(!cache.is_dir());
74        assert_eq!(
75            cache.to_str(),
76            get_home()?
77                .join(".ssh")
78                .join("vault")
79                .join("keys")
80                .join("test")
81                .to_str()
82        );
83        Ok(())
84    }
85
86    #[test]
87    fn test_get_ssh_vault_path() -> Result<(), Box<dyn std::error::Error>> {
88        let ssh_vault = get_ssh_vault_path()?;
89        assert!(!ssh_vault.is_file());
90        assert_eq!(
91            ssh_vault.to_str(),
92            get_home()?.join(".ssh").join("vault").to_str()
93        );
94        Ok(())
95    }
96
97    #[test]
98    fn test_put() -> Result<(), Box<dyn std::error::Error>> {
99        let cache = get_cache_path("test-2")?;
100        put("test-2", "test")?;
101
102        assert!(cache.is_file());
103        assert!(!cache.is_dir());
104        assert!(cache.exists());
105        assert_eq!(
106            cache.to_str(),
107            get_home()?
108                .join(".ssh")
109                .join("vault")
110                .join("keys")
111                .join("test-2")
112                .to_str()
113        );
114        fs::remove_file(cache)?;
115        Ok(())
116    }
117
118    #[test]
119    fn test_get() -> Result<(), Box<dyn std::error::Error>> {
120        let cache = get_cache_path("test-3")?;
121        put("test-3", "test")?;
122        let response = get("test-3")?;
123        assert_eq!(response, "test");
124        fs::remove_file(cache)?;
125        Ok(())
126    }
127}