1use crate::tools::get_home;
2use anyhow::{Result, anyhow};
3use std::{
4 fs,
5 path::{Path, PathBuf},
6 time::{Duration, SystemTime},
7};
8
9pub 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 let duration_since_modified = SystemTime::now()
23 .duration_since(last_modified)
24 .unwrap_or(Duration::from_secs(0));
25
26 if duration_since_modified > Duration::from_hours(30 * 24) {
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
37pub fn put(key: &str, response: &str) -> Result<()> {
41 let cache = get_cache_path(key)?;
42
43 if let Some(parent_dir) = cache.parent() {
49 fs::create_dir_all(parent_dir)?;
50 #[cfg(unix)]
51 {
52 use std::os::unix::fs::PermissionsExt;
53 let vault_dir = get_ssh_vault_path()?;
56 for dir in [vault_dir.join("keys"), vault_dir] {
57 if dir.is_dir() {
58 fs::set_permissions(&dir, fs::Permissions::from_mode(0o700))?;
59 }
60 }
61 }
62 }
63
64 #[cfg(unix)]
65 {
66 use std::io::Write;
67 use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
68 let mut file = fs::OpenOptions::new()
69 .write(true)
70 .create(true)
71 .truncate(true)
72 .mode(0o600)
73 .open(&cache)?;
74 file.write_all(response.as_bytes())?;
75 fs::set_permissions(&cache, fs::Permissions::from_mode(0o600))?;
77 }
78 #[cfg(not(unix))]
79 fs::write(&cache, response)?;
80
81 Ok(())
82}
83
84fn get_cache_path(key: &str) -> Result<PathBuf> {
88 let ssh_vault = get_ssh_vault_path()?;
89 Ok(ssh_vault.join("keys").join(key))
90}
91
92fn get_ssh_vault_path() -> Result<PathBuf> {
96 let home = get_home()?;
97 Ok(Path::new(&home).join(".ssh").join("vault"))
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use std::fs;
104
105 #[test]
106 fn test_get_cache_path() -> Result<(), Box<dyn std::error::Error>> {
107 let cache = get_cache_path("test")?;
108 assert!(!cache.is_dir());
109 assert_eq!(
110 cache.to_str(),
111 get_home()?
112 .join(".ssh")
113 .join("vault")
114 .join("keys")
115 .join("test")
116 .to_str()
117 );
118 Ok(())
119 }
120
121 #[test]
122 fn test_get_ssh_vault_path() -> Result<(), Box<dyn std::error::Error>> {
123 let ssh_vault = get_ssh_vault_path()?;
124 assert!(!ssh_vault.is_file());
125 assert_eq!(
126 ssh_vault.to_str(),
127 get_home()?.join(".ssh").join("vault").to_str()
128 );
129 Ok(())
130 }
131
132 #[test]
133 fn test_put() -> Result<(), Box<dyn std::error::Error>> {
134 let cache = get_cache_path("test-2")?;
135 put("test-2", "test")?;
136
137 assert!(cache.is_file());
138 assert!(!cache.is_dir());
139 assert!(cache.exists());
140 assert_eq!(
141 cache.to_str(),
142 get_home()?
143 .join(".ssh")
144 .join("vault")
145 .join("keys")
146 .join("test-2")
147 .to_str()
148 );
149 fs::remove_file(cache)?;
150 Ok(())
151 }
152
153 #[test]
154 fn test_get() -> Result<(), Box<dyn std::error::Error>> {
155 let cache = get_cache_path("test-3")?;
156 put("test-3", "test")?;
157 let response = get("test-3")?;
158 assert_eq!(response, "test");
159 fs::remove_file(cache)?;
160 Ok(())
161 }
162
163 #[cfg(unix)]
164 #[test]
165 fn test_put_permissions() -> Result<(), Box<dyn std::error::Error>> {
166 use std::os::unix::fs::PermissionsExt;
167
168 let cache = get_cache_path("test-perms")?;
169 put("test-perms", "test")?;
170
171 let file_mode = fs::metadata(&cache)?.permissions().mode() & 0o777;
173 assert_eq!(file_mode, 0o600);
174
175 if let Some(parent) = cache.parent() {
177 let dir_mode = fs::metadata(parent)?.permissions().mode() & 0o777;
178 assert_eq!(dir_mode, 0o700);
179 }
180
181 fs::remove_file(cache)?;
182 Ok(())
183 }
184}