kittynode_core/infra/
file.rs1use eyre::{Context, Result};
2use rand::RngCore;
3use std::{fs, path::PathBuf};
4use tracing::info;
5
6fn config_subdir_path(dir_name: &str) -> Result<PathBuf> {
7 home::home_dir()
8 .map(|home| home.join(".config").join(dir_name))
9 .ok_or_else(|| eyre::eyre!("Failed to determine the ~/.config/{} path", dir_name))
10}
11
12pub fn kittynode_path() -> Result<PathBuf> {
13 config_subdir_path("kittynode")
14}
15
16pub fn kittynode_cli_path() -> Result<PathBuf> {
17 config_subdir_path("kittynode-cli")
18}
19
20pub(crate) fn generate_jwt_secret_with_path(path: &PathBuf) -> Result<String> {
21 if !path.exists() {
22 info!("Creating directory at {:?}", path);
23 fs::create_dir_all(path).wrap_err("Failed to create directory")?;
24 }
25
26 info!("Generating JWT secret using a random number generator");
27
28 let mut buf = [0u8; 32];
30 rand::rng().fill_bytes(&mut buf);
31
32 let secret = hex::encode(buf);
34
35 fs::write(path.join("jwt.hex"), &secret).wrap_err("Failed to write JWT secret to file")?;
37
38 info!(
39 "JWT secret successfully generated and written to {:?}",
40 path.join("jwt.hex")
41 );
42
43 Ok(secret)
44}
45
46pub(crate) fn generate_jwt_secret() -> Result<String> {
47 let path = kittynode_path()?;
48 generate_jwt_secret_with_path(&path)
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use tempfile::tempdir;
55
56 #[test]
57 fn test_generate_jwt_secret() {
58 let temp_dir = tempdir().unwrap();
59 let temp_path = temp_dir.path().to_path_buf();
60
61 let result = generate_jwt_secret_with_path(&temp_path);
62 assert!(result.is_ok(), "Expected OK, got {result:?}");
63
64 let jwt_file_path = temp_path.join("jwt.hex");
65 assert!(jwt_file_path.exists(), "JWT secret file not found");
66
67 let secret = fs::read_to_string(jwt_file_path).unwrap();
68 assert_eq!(secret.len(), 64, "Expected 64 hex characters");
69 assert!(secret.chars().all(|c| c.is_ascii_hexdigit()));
70
71 assert_eq!(result.unwrap(), secret, "Secrets do not match");
72 }
73}