1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
use super::TokenAlgorithm; use std::collections::HashMap; use std::default::Default; use std::io::Result as IoResult; use std::path::{Path, PathBuf}; use crate::{TotpConfigError, TotpResult}; use serde::{self, Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Config { pub totp: std::collections::HashMap<String, TotpOptions>, } #[derive(Serialize, Deserialize, Debug, Clone)] pub enum SecretLocation { #[serde(rename = "config")] Config, #[cfg(feature = "keychain")] #[serde(rename = "keychain")] KeyChain, } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct TotpOptions { storage: Option<SecretLocation>, secret: Option<String>, algorithm: Option<TokenAlgorithm>, } impl TotpOptions { pub fn storage(&self) -> Option<&SecretLocation> { self.storage.as_ref() } pub fn secret(&self) -> Option<&String> { self.secret.as_ref() } pub fn algorithm(&self) -> TokenAlgorithm { self.algorithm.unwrap_or(TokenAlgorithm::TotpSha1) } pub fn new_config_stored_secret(secret: String, algorithm: TokenAlgorithm) -> Self { TotpOptions { storage: Some(SecretLocation::Config), secret: Some(secret), algorithm: Some(algorithm), } } #[cfg(feature = "keychain")] pub fn new_keychain_stored_secret(algorithm: TokenAlgorithm) -> Self { TotpOptions { storage: Some(SecretLocation::KeyChain), secret: None, algorithm: Some(algorithm), } } } impl Default for Config { fn default() -> Self { Config { totp: HashMap::new(), } } } impl Config { pub fn lookup(&self, name: &str) -> TotpResult<&TotpOptions> { Ok(self .totp .get(name) .ok_or_else(|| TotpConfigError(format!("Unable to find config named '{}'", name)))?) } } pub fn load_config<P: AsRef<Path>>(config_dir: P) -> IoResult<Config> { let config_path: PathBuf = config_dir.as_ref().join("config.toml"); let config: Config = if config_path.exists() { let config = std::fs::read_to_string(config_path)?; toml::from_str(&config).expect("Unable to read config as TOML") } else { Config::default() }; Ok(config) } fn make_config_dir<P: AsRef<Path>>(config_dir: P) -> IoResult<()> { std::fs::create_dir_all(config_dir) } pub fn default_config_dir() -> PathBuf { let home_dir = dirs::home_dir().expect("Can't load users home directory"); home_dir.join(".config").join("otpcli") } pub fn ensure_config_dir<P: AsRef<Path>>(config_dir: P) -> IoResult<()> { match std::fs::metadata(config_dir.as_ref()) { Err(_) => make_config_dir(config_dir.as_ref()), Ok(ref md) if !md.is_dir() => make_config_dir(config_dir.as_ref()), Ok(_) => Ok(()), } }