|
|
@ -3,77 +3,149 @@ use std::sync::RwLock; |
|
|
|
|
|
|
|
use handlebars::Handlebars; |
|
|
|
|
|
|
|
use crate::error::Error; |
|
|
|
use crate::util::IntoResult; |
|
|
|
|
|
|
|
lazy_static! { |
|
|
|
pub static ref CONFIG: Config = Config::load(); |
|
|
|
pub static ref CONFIG: Config = Config::load().unwrap_or_else(|e| { |
|
|
|
println!("Error loading config:\n\t{:?}\n", e); |
|
|
|
exit(12) |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
macro_rules! make_config { |
|
|
|
( $( $name:ident: $ty:ty ),+ $(,)* ) => { |
|
|
|
( $( $name:ident : $ty:ty $(, $default_fn:expr)? );+ $(;)* ) => { |
|
|
|
|
|
|
|
pub struct Config { inner: RwLock<_Config> } |
|
|
|
pub struct Config { inner: RwLock<Inner> } |
|
|
|
|
|
|
|
#[derive(Default)] |
|
|
|
struct _Config { |
|
|
|
_templates: Handlebars, |
|
|
|
$(pub $name: $ty),+ |
|
|
|
struct Inner { |
|
|
|
templates: Handlebars, |
|
|
|
config: _Config, |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Debug, Default, Serialize, Deserialize)] |
|
|
|
struct _Config { $(pub $name: $ty),+ } |
|
|
|
|
|
|
|
paste::item! { |
|
|
|
#[allow(unused)] |
|
|
|
impl Config { |
|
|
|
#[allow(unused)] |
|
|
|
impl Config { |
|
|
|
$( |
|
|
|
pub fn $name(&self) -> $ty { |
|
|
|
self.inner.read().unwrap().config.$name.clone() |
|
|
|
} |
|
|
|
pub fn [<set_ $name>](&self, value: $ty) { |
|
|
|
self.inner.write().unwrap().config.$name = value; |
|
|
|
} |
|
|
|
)+ |
|
|
|
|
|
|
|
pub fn load() -> Result<Self, Error> { |
|
|
|
use crate::util::get_env; |
|
|
|
dotenv::dotenv().ok(); |
|
|
|
|
|
|
|
let mut config = _Config::default(); |
|
|
|
|
|
|
|
$( |
|
|
|
pub fn $name(&self) -> $ty { |
|
|
|
self.inner.read().unwrap().$name.clone() |
|
|
|
} |
|
|
|
pub fn [<set_ $name>](&self, value: $ty) { |
|
|
|
self.inner.write().unwrap().$name = value; |
|
|
|
} |
|
|
|
config.$name = make_config!{ @expr &stringify!($name).to_uppercase(), $ty, &config, $($default_fn)? }; |
|
|
|
)+ |
|
|
|
|
|
|
|
Ok(Config { |
|
|
|
inner: RwLock::new(Inner { |
|
|
|
templates: load_templates(&config.templates_folder), |
|
|
|
config, |
|
|
|
}), |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
make_config! { |
|
|
|
database_url: String, |
|
|
|
icon_cache_folder: String, |
|
|
|
attachments_folder: String, |
|
|
|
|
|
|
|
icon_cache_ttl: u64, |
|
|
|
icon_cache_negttl: u64, |
|
|
|
|
|
|
|
private_rsa_key: String, |
|
|
|
private_rsa_key_pem: String, |
|
|
|
public_rsa_key: String, |
|
|
|
|
|
|
|
web_vault_folder: String, |
|
|
|
web_vault_enabled: bool, |
|
|
|
|
|
|
|
websocket_enabled: bool, |
|
|
|
websocket_url: String, |
|
|
|
|
|
|
|
extended_logging: bool, |
|
|
|
log_file: Option<String>, |
|
|
|
( @expr $name:expr, $ty:ty, $config:expr, $default_fn:expr ) => {{ |
|
|
|
match get_env($name) { |
|
|
|
Some(v) => v, |
|
|
|
None => { |
|
|
|
let f: &Fn(&_Config) -> _ = &$default_fn; |
|
|
|
f($config).into_result()? |
|
|
|
} |
|
|
|
} |
|
|
|
}}; |
|
|
|
|
|
|
|
disable_icon_download: bool, |
|
|
|
signups_allowed: bool, |
|
|
|
invitations_allowed: bool, |
|
|
|
admin_token: Option<String>, |
|
|
|
password_iterations: i32, |
|
|
|
show_password_hint: bool, |
|
|
|
( @expr $name:expr, $ty:ty, $config:expr, ) => { |
|
|
|
get_env($name) |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
domain: String, |
|
|
|
domain_set: bool, |
|
|
|
make_config! { |
|
|
|
data_folder: String, |_| "data".to_string(); |
|
|
|
database_url: String, |c| format!("{}/{}", c.data_folder, "db.sqlite3"); |
|
|
|
icon_cache_folder: String, |c| format!("{}/{}", c.data_folder, "icon_cache"); |
|
|
|
attachments_folder: String, |c| format!("{}/{}", c.data_folder, "attachments"); |
|
|
|
templates_folder: String, |c| format!("{}/{}", c.data_folder, "templates"); |
|
|
|
|
|
|
|
rsa_key_filename: String, |c| format!("{}/{}", c.data_folder, "rsa_key"); |
|
|
|
private_rsa_key: String, |c| format!("{}.der", c.rsa_key_filename); |
|
|
|
private_rsa_key_pem: String, |c| format!("{}.pem", c.rsa_key_filename); |
|
|
|
public_rsa_key: String, |c| format!("{}.pub.der", c.rsa_key_filename); |
|
|
|
|
|
|
|
websocket_enabled: bool, |_| false; |
|
|
|
websocket_address: String, |_| "0.0.0.0".to_string(); |
|
|
|
websocket_port: u16, |_| 3012; |
|
|
|
|
|
|
|
web_vault_folder: String, |_| "web-vault/".to_string(); |
|
|
|
web_vault_enabled: bool, |_| true; |
|
|
|
|
|
|
|
icon_cache_ttl: u64, |_| 2_592_000; |
|
|
|
icon_cache_negttl: u64, |_| 259_200; |
|
|
|
|
|
|
|
disable_icon_download: bool, |_| false; |
|
|
|
signups_allowed: bool, |_| true; |
|
|
|
invitations_allowed: bool, |_| true; |
|
|
|
password_iterations: i32, |_| 100_000; |
|
|
|
show_password_hint: bool, |_| true; |
|
|
|
|
|
|
|
domain: String, |_| "http://localhost".to_string(); |
|
|
|
domain_set: bool, |_| false; |
|
|
|
|
|
|
|
reload_templates: bool, |_| false; |
|
|
|
|
|
|
|
extended_logging: bool, |_| true; |
|
|
|
log_file: Option<String>; |
|
|
|
|
|
|
|
admin_token: Option<String>; |
|
|
|
|
|
|
|
yubico_client_id: Option<String>; |
|
|
|
yubico_secret_key: Option<String>; |
|
|
|
yubico_server: Option<String>; |
|
|
|
|
|
|
|
// Mail settings
|
|
|
|
smtp_host: Option<String>; |
|
|
|
smtp_ssl: bool, |_| true; |
|
|
|
smtp_port: u16, |c| if c.smtp_ssl {587} else {25}; |
|
|
|
smtp_from: String, |c| if c.smtp_host.is_some() { err!("Please specify SMTP_FROM to enable SMTP support") } else { Ok(String::new() )}; |
|
|
|
smtp_from_name: String, |_| "Bitwarden_RS".to_string(); |
|
|
|
smtp_username: Option<String>; |
|
|
|
smtp_password: Option<String>; |
|
|
|
} |
|
|
|
|
|
|
|
yubico_cred_set: bool, |
|
|
|
yubico_client_id: String, |
|
|
|
yubico_secret_key: String, |
|
|
|
yubico_server: Option<String>, |
|
|
|
impl Config { |
|
|
|
pub fn mail_enabled(&self) -> bool { |
|
|
|
self.inner.read().unwrap().config.smtp_host.is_some() |
|
|
|
} |
|
|
|
|
|
|
|
mail: Option<MailConfig>, |
|
|
|
templates_folder: String, |
|
|
|
reload_templates: bool, |
|
|
|
pub fn render_template<T: serde::ser::Serialize>( |
|
|
|
&self, |
|
|
|
name: &str, |
|
|
|
data: &T, |
|
|
|
) -> Result<String, crate::error::Error> { |
|
|
|
if CONFIG.reload_templates() { |
|
|
|
warn!("RELOADING TEMPLATES"); |
|
|
|
let hb = load_templates(CONFIG.templates_folder().as_ref()); |
|
|
|
hb.render(name, data).map_err(Into::into) |
|
|
|
} else { |
|
|
|
let hb = &CONFIG.inner.read().unwrap().templates; |
|
|
|
hb.render(name, data).map_err(Into::into) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn load_templates(path: &str) -> Handlebars { |
|
|
@ -106,140 +178,3 @@ fn load_templates(path: &str) -> Handlebars { |
|
|
|
|
|
|
|
hb |
|
|
|
} |
|
|
|
|
|
|
|
impl Config { |
|
|
|
pub fn render_template<T: serde::ser::Serialize>( |
|
|
|
&self, |
|
|
|
name: &str, |
|
|
|
data: &T, |
|
|
|
) -> Result<String, crate::error::Error> { |
|
|
|
if CONFIG.reload_templates() { |
|
|
|
warn!("RELOADING TEMPLATES"); |
|
|
|
let hb = load_templates(CONFIG.templates_folder().as_ref()); |
|
|
|
hb.render(name, data).map_err(Into::into) |
|
|
|
} else { |
|
|
|
let hb = &CONFIG.inner.read().unwrap()._templates; |
|
|
|
hb.render(name, data).map_err(Into::into) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn load() -> Self { |
|
|
|
use crate::util::{get_env, get_env_or}; |
|
|
|
dotenv::dotenv().ok(); |
|
|
|
|
|
|
|
let df = get_env_or("DATA_FOLDER", "data".to_string()); |
|
|
|
let key = get_env_or("RSA_KEY_FILENAME", format!("{}/{}", &df, "rsa_key")); |
|
|
|
|
|
|
|
let domain = get_env("DOMAIN"); |
|
|
|
|
|
|
|
let yubico_client_id = get_env("YUBICO_CLIENT_ID"); |
|
|
|
let yubico_secret_key = get_env("YUBICO_SECRET_KEY"); |
|
|
|
|
|
|
|
let templates_folder = get_env_or("TEMPLATES_FOLDER", format!("{}/{}", &df, "templates")); |
|
|
|
|
|
|
|
let cfg = _Config { |
|
|
|
database_url: get_env_or("DATABASE_URL", format!("{}/{}", &df, "db.sqlite3")), |
|
|
|
icon_cache_folder: get_env_or("ICON_CACHE_FOLDER", format!("{}/{}", &df, "icon_cache")), |
|
|
|
attachments_folder: get_env_or("ATTACHMENTS_FOLDER", format!("{}/{}", &df, "attachments")), |
|
|
|
_templates: load_templates(&templates_folder), |
|
|
|
templates_folder, |
|
|
|
reload_templates: get_env_or("RELOAD_TEMPLATES", false), |
|
|
|
|
|
|
|
// icon_cache_ttl defaults to 30 days (30 * 24 * 60 * 60 seconds)
|
|
|
|
icon_cache_ttl: get_env_or("ICON_CACHE_TTL", 2_592_000), |
|
|
|
// icon_cache_negttl defaults to 3 days (3 * 24 * 60 * 60 seconds)
|
|
|
|
icon_cache_negttl: get_env_or("ICON_CACHE_NEGTTL", 259_200), |
|
|
|
|
|
|
|
private_rsa_key: format!("{}.der", &key), |
|
|
|
private_rsa_key_pem: format!("{}.pem", &key), |
|
|
|
public_rsa_key: format!("{}.pub.der", &key), |
|
|
|
|
|
|
|
web_vault_folder: get_env_or("WEB_VAULT_FOLDER", "web-vault/".into()), |
|
|
|
web_vault_enabled: get_env_or("WEB_VAULT_ENABLED", true), |
|
|
|
|
|
|
|
websocket_enabled: get_env_or("WEBSOCKET_ENABLED", false), |
|
|
|
websocket_url: format!( |
|
|
|
"{}:{}", |
|
|
|
get_env_or("WEBSOCKET_ADDRESS", "0.0.0.0".to_string()), |
|
|
|
get_env_or("WEBSOCKET_PORT", 3012) |
|
|
|
), |
|
|
|
|
|
|
|
extended_logging: get_env_or("EXTENDED_LOGGING", true), |
|
|
|
log_file: get_env("LOG_FILE"), |
|
|
|
|
|
|
|
disable_icon_download: get_env_or("DISABLE_ICON_DOWNLOAD", false), |
|
|
|
signups_allowed: get_env_or("SIGNUPS_ALLOWED", true), |
|
|
|
admin_token: get_env("ADMIN_TOKEN"), |
|
|
|
invitations_allowed: get_env_or("INVITATIONS_ALLOWED", true), |
|
|
|
password_iterations: get_env_or("PASSWORD_ITERATIONS", 100_000), |
|
|
|
show_password_hint: get_env_or("SHOW_PASSWORD_HINT", true), |
|
|
|
|
|
|
|
domain_set: domain.is_some(), |
|
|
|
domain: domain.unwrap_or("http://localhost".into()), |
|
|
|
|
|
|
|
yubico_cred_set: yubico_client_id.is_some() && yubico_secret_key.is_some(), |
|
|
|
yubico_client_id: yubico_client_id.unwrap_or("00000".into()), |
|
|
|
yubico_secret_key: yubico_secret_key.unwrap_or("AAAAAAA".into()), |
|
|
|
yubico_server: get_env("YUBICO_SERVER"), |
|
|
|
|
|
|
|
mail: MailConfig::load(), |
|
|
|
}; |
|
|
|
|
|
|
|
Config { |
|
|
|
inner: RwLock::new(cfg), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Debug, Clone)] |
|
|
|
pub struct MailConfig { |
|
|
|
pub smtp_host: String, |
|
|
|
pub smtp_port: u16, |
|
|
|
pub smtp_ssl: bool, |
|
|
|
pub smtp_from: String, |
|
|
|
pub smtp_from_name: String, |
|
|
|
pub smtp_username: Option<String>, |
|
|
|
pub smtp_password: Option<String>, |
|
|
|
} |
|
|
|
|
|
|
|
impl MailConfig { |
|
|
|
fn load() -> Option<Self> { |
|
|
|
use crate::util::{get_env, get_env_or}; |
|
|
|
|
|
|
|
// When SMTP_HOST is absent, we assume the user does not want to enable it.
|
|
|
|
let smtp_host = match get_env("SMTP_HOST") { |
|
|
|
Some(host) => host, |
|
|
|
None => return None, |
|
|
|
}; |
|
|
|
|
|
|
|
let smtp_from = get_env("SMTP_FROM").unwrap_or_else(|| { |
|
|
|
error!("Please specify SMTP_FROM to enable SMTP support."); |
|
|
|
exit(1); |
|
|
|
}); |
|
|
|
|
|
|
|
let smtp_from_name = get_env_or("SMTP_FROM_NAME", "Bitwarden_RS".into()); |
|
|
|
|
|
|
|
let smtp_ssl = get_env_or("SMTP_SSL", true); |
|
|
|
let smtp_port = get_env("SMTP_PORT").unwrap_or_else(|| if smtp_ssl { 587u16 } else { 25u16 }); |
|
|
|
|
|
|
|
let smtp_username = get_env("SMTP_USERNAME"); |
|
|
|
let smtp_password = get_env("SMTP_PASSWORD").or_else(|| { |
|
|
|
if smtp_username.as_ref().is_some() { |
|
|
|
error!("SMTP_PASSWORD is mandatory when specifying SMTP_USERNAME."); |
|
|
|
exit(1); |
|
|
|
} else { |
|
|
|
None |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
Some(MailConfig { |
|
|
|
smtp_host, |
|
|
|
smtp_port, |
|
|
|
smtp_ssl, |
|
|
|
smtp_from, |
|
|
|
smtp_from_name, |
|
|
|
smtp_username, |
|
|
|
smtp_password, |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|