battlecommand_forge/
secrets.rs1use std::fs::OpenOptions;
8use std::io::{self, Write};
9use std::path::Path;
10
11#[cfg(unix)]
12use std::os::unix::fs::OpenOptionsExt;
13
14pub fn write_secret_file(path: &Path, contents: &[u8]) -> io::Result<()> {
17 if let Some(parent) = path.parent() {
18 if !parent.as_os_str().is_empty() {
19 std::fs::create_dir_all(parent)?;
20 }
21 }
22
23 let tmp_path = match path.file_name() {
24 Some(name) => {
25 let mut tmp_name = name.to_os_string();
26 tmp_name.push(".tmp");
27 path.with_file_name(tmp_name)
28 }
29 None => {
30 return Err(io::Error::new(
31 io::ErrorKind::InvalidInput,
32 "write_secret_file: path has no file name",
33 ));
34 }
35 };
36
37 {
38 let mut opts = OpenOptions::new();
39 opts.write(true).create(true).truncate(true);
40 #[cfg(unix)]
41 opts.mode(0o600);
42 let mut f = opts.open(&tmp_path)?;
43 f.write_all(contents)?;
44 f.sync_all()?;
45 }
46
47 std::fs::rename(&tmp_path, path)?;
48 Ok(())
49}
50
51pub fn ensure_secret_file(path: &Path) -> io::Result<()> {
55 if path.exists() {
56 return Ok(());
57 }
58 write_secret_file(path, b"")
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use std::io::Read;
65
66 #[test]
67 fn test_write_secret_file_roundtrip() {
68 let dir = std::env::temp_dir().join(format!("bcf-secrets-{}", std::process::id()));
69 let path = dir.join("token");
70 write_secret_file(&path, b"hunter2").unwrap();
71
72 let mut s = String::new();
73 std::fs::File::open(&path)
74 .unwrap()
75 .read_to_string(&mut s)
76 .unwrap();
77 assert_eq!(s, "hunter2");
78
79 std::fs::remove_dir_all(&dir).ok();
80 }
81
82 #[test]
83 fn test_write_secret_file_overwrites() {
84 let dir = std::env::temp_dir().join(format!("bcf-secrets2-{}", std::process::id()));
85 let path = dir.join("data");
86 write_secret_file(&path, b"first").unwrap();
87 write_secret_file(&path, b"second").unwrap();
88
89 let s = std::fs::read_to_string(&path).unwrap();
90 assert_eq!(s, "second");
91
92 std::fs::remove_dir_all(&dir).ok();
93 }
94
95 #[cfg(unix)]
96 #[test]
97 fn test_write_secret_file_mode_0600() {
98 use std::os::unix::fs::PermissionsExt;
99 let dir = std::env::temp_dir().join(format!("bcf-secrets3-{}", std::process::id()));
100 let path = dir.join("locked");
101 write_secret_file(&path, b"x").unwrap();
102
103 let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
104 assert_eq!(mode, 0o600, "expected 0600, got {:o}", mode);
105
106 std::fs::remove_dir_all(&dir).ok();
107 }
108}