Skip to main content

indieweb_cli_common/
token.rs

1use directories::ProjectDirs;
2use secrecy::{ExposeSecret, SecretString};
3use std::fs;
4use std::io;
5use std::path::PathBuf;
6
7use crate::error::{CliError, Result};
8
9const TOKEN_FILE_PERMISSIONS: u32 = 0o600;
10
11pub struct TokenStore {
12    service_name: &'static str,
13}
14
15impl TokenStore {
16    pub fn new(service_name: &'static str) -> Self {
17        Self { service_name }
18    }
19
20    pub fn micropub() -> Self {
21        Self::new("micropub")
22    }
23
24    pub fn indieauth() -> Self {
25        Self::new("indieauth")
26    }
27
28    fn token_path(&self) -> Option<PathBuf> {
29        ProjectDirs::from("org", "indieweb", "indieweb").map(|dirs| {
30            dirs.data_local_dir()
31                .join(format!("{}-token", self.service_name))
32        })
33    }
34
35    pub fn load(&self) -> Result<Option<SecretString>> {
36        let path = self.token_path().ok_or_else(|| {
37            CliError::Config(Box::new(io::Error::new(
38                io::ErrorKind::NotFound,
39                "Could not determine token storage path",
40            )))
41        })?;
42
43        if !path.exists() {
44            return Ok(None);
45        }
46
47        let contents = fs::read_to_string(&path)?;
48        let token = contents.trim().to_string();
49
50        if token.is_empty() {
51            Ok(None)
52        } else {
53            Ok(Some(SecretString::new(token.into_boxed_str())))
54        }
55    }
56
57    pub fn save(&self, token: &SecretString) -> Result<()> {
58        let path = self.token_path().ok_or_else(|| {
59            CliError::Config(Box::new(io::Error::new(
60                io::ErrorKind::NotFound,
61                "Could not determine token storage path",
62            )))
63        })?;
64
65        if let Some(parent) = path.parent() {
66            fs::create_dir_all(parent)?;
67        }
68
69        fs::write(&path, token.expose_secret())?;
70
71        #[cfg(unix)]
72        {
73            use std::os::unix::fs::PermissionsExt;
74            fs::set_permissions(&path, fs::Permissions::from_mode(TOKEN_FILE_PERMISSIONS))?;
75        }
76
77        Ok(())
78    }
79
80    pub fn delete(&self) -> Result<()> {
81        if let Some(path) = self.token_path() {
82            if path.exists() {
83                fs::remove_file(&path)?;
84            }
85        }
86        Ok(())
87    }
88
89    pub fn exists(&self) -> bool {
90        self.token_path().map(|p| p.exists()).unwrap_or(false)
91    }
92
93    pub fn resolve_token(
94        &self,
95        cli_token: Option<&String>,
96        env_token: Option<&String>,
97    ) -> Result<Option<SecretString>> {
98        if let Some(token) = cli_token {
99            return Ok(Some(SecretString::new(token.clone().into_boxed_str())));
100        }
101
102        if let Some(token) = env_token {
103            return Ok(Some(SecretString::new(token.clone().into_boxed_str())));
104        }
105
106        self.load()
107    }
108}