Skip to main content

eero_api/credential/
file.rs

1use std::path::PathBuf;
2
3use crate::error::{Error, Result};
4use crate::credential::CredentialStore;
5
6/// File-based credential store. Stores tokens as plain text files in the
7/// platform config directory (e.g. `~/.config/eero/` on Linux).
8pub struct FileStore {
9    dir: PathBuf,
10}
11
12impl FileStore {
13    /// Create a new FileStore using the default config directory.
14    pub fn new() -> Result<Self> {
15        let dir = dirs::config_dir()
16            .ok_or_else(|| Error::CredentialStore("could not determine config directory".into()))?
17            .join("eero");
18        Ok(Self { dir })
19    }
20
21    /// Create a new FileStore with a custom directory.
22    pub fn with_dir(dir: PathBuf) -> Self {
23        Self { dir }
24    }
25
26    fn session_path(&self) -> PathBuf {
27        self.dir.join("session_token")
28    }
29
30    fn user_path(&self) -> PathBuf {
31        self.dir.join("user_token")
32    }
33
34    async fn read_token(&self, path: PathBuf) -> Result<Option<String>> {
35        match tokio::fs::read_to_string(&path).await {
36            Ok(contents) => {
37                let trimmed = contents.trim().to_string();
38                if trimmed.is_empty() {
39                    Ok(None)
40                } else {
41                    Ok(Some(trimmed))
42                }
43            }
44            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
45            Err(e) => Err(e.into()),
46        }
47    }
48
49    async fn write_token(&self, path: PathBuf, token: &str) -> Result<()> {
50        tokio::fs::create_dir_all(&self.dir).await?;
51        tokio::fs::write(&path, token).await?;
52        Ok(())
53    }
54
55    async fn delete_token(&self, path: PathBuf) -> Result<()> {
56        match tokio::fs::remove_file(&path).await {
57            Ok(()) => Ok(()),
58            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
59            Err(e) => Err(e.into()),
60        }
61    }
62}
63
64#[async_trait::async_trait]
65impl CredentialStore for FileStore {
66    async fn get_session_token(&self) -> Result<Option<String>> {
67        self.read_token(self.session_path()).await
68    }
69
70    async fn set_session_token(&self, token: &str) -> Result<()> {
71        self.write_token(self.session_path(), token).await
72    }
73
74    async fn delete_session_token(&self) -> Result<()> {
75        self.delete_token(self.session_path()).await
76    }
77
78    async fn get_user_token(&self) -> Result<Option<String>> {
79        self.read_token(self.user_path()).await
80    }
81
82    async fn set_user_token(&self, token: &str) -> Result<()> {
83        self.write_token(self.user_path(), token).await
84    }
85
86    async fn delete_user_token(&self) -> Result<()> {
87        self.delete_token(self.user_path()).await
88    }
89}