1use anyhow::{anyhow, Context, Result};
7use serde::{Deserialize, Serialize};
8use std::path::{Path, PathBuf};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct Secrets {
12 pub access_key_id: String,
13 pub secret_access_key: String,
14 #[serde(default)]
17 pub gate_secret: Option<String>,
18}
19
20impl Secrets {
21 pub fn load() -> Result<Self> {
22 let path = secrets_path()?;
23 let text = std::fs::read_to_string(&path).map_err(|_| {
24 anyhow!(
25 "no dove credentials at {} — run `dove provision` first",
26 path.display()
27 )
28 })?;
29 toml::from_str(&text).map_err(|e| anyhow!("parsing dove secrets: {e}"))
30 }
31
32 pub fn save(&self) -> Result<()> {
33 let path = secrets_path()?;
34 std::fs::create_dir_all(path.parent().unwrap())
35 .with_context(|| format!("creating {}", path.parent().unwrap().display()))?;
36 let text = toml::to_string_pretty(self).context("serializing dove secrets")?;
37 std::fs::write(&path, text).with_context(|| format!("writing {}", path.display()))?;
38 set_private(&path)
39 }
40
41 pub fn exists() -> bool {
42 secrets_path().map(|p| p.exists()).unwrap_or(false)
43 }
44}
45
46#[cfg(unix)]
48fn set_private(path: &Path) -> Result<()> {
49 use std::os::unix::fs::PermissionsExt;
50 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
51 .with_context(|| format!("chmod 600 {}", path.display()))
52}
53#[cfg(not(unix))]
54fn set_private(_path: &Path) -> Result<()> {
55 Ok(())
56}
57
58fn secrets_path() -> Result<PathBuf> {
59 if let Ok(x) = std::env::var("XDG_CONFIG_HOME") {
60 if !x.is_empty() {
61 return Ok(PathBuf::from(x).join("dove/secrets.toml"));
62 }
63 }
64 let home = std::env::var("HOME").map_err(|_| anyhow!("HOME is not set"))?;
65 Ok(PathBuf::from(home).join(".config/dove/secrets.toml"))
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn round_trips_through_toml() {
74 let s = Secrets {
75 access_key_id: "AKIAEXAMPLE".into(),
76 secret_access_key: "shhh".into(),
77 gate_secret: Some("deadbeef".into()),
78 };
79 let text = toml::to_string(&s).unwrap();
80 assert_eq!(toml::from_str::<Secrets>(&text).unwrap(), s);
81 }
82}