use std::process::exit; use std::sync::RwLock; use handlebars::Handlebars; lazy_static! { pub static ref CONFIG: Config = Config::load(); } macro_rules! make_config { ( $( $name:ident: $ty:ty ),+ $(,)* ) => { pub struct Config { inner: RwLock<_Config> } #[derive(Default)] struct _Config { _templates: Handlebars, $(pub $name: $ty),+ } paste::item! { #[allow(unused)] impl Config { $( pub fn $name(&self) -> $ty { self.inner.read().unwrap().$name.clone() } pub fn [](&self, value: $ty) { self.inner.write().unwrap().$name = value; } )+ } } }; } 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, signups_allowed: bool, invitations_allowed: bool, admin_token: Option, password_iterations: i32, show_password_hint: bool, domain: String, domain_set: bool, yubico_cred_set: bool, yubico_client_id: String, yubico_secret_key: String, yubico_server: Option, mail: Option, templates_folder: String, reload_templates: bool, } fn load_templates(path: &str) -> Handlebars { let mut hb = Handlebars::new(); // Error on missing params hb.set_strict_mode(true); macro_rules! reg { ($name:expr) => {{ let template = include_str!(concat!("static/templates/", $name, ".hbs")); hb.register_template_string($name, template).unwrap(); }}; } // First register default templates here reg!("email/invite_accepted"); reg!("email/invite_confirmed"); reg!("email/pw_hint_none"); reg!("email/pw_hint_some"); reg!("email/send_org_invite"); reg!("admin/base"); reg!("admin/login"); reg!("admin/page"); // And then load user templates to overwrite the defaults // Use .hbs extension for the files // Templates get registered with their relative name hb.register_templates_directory(".hbs", path).unwrap(); hb } impl Config { pub fn render_template( &self, name: &str, data: &T, ) -> Result { 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"), 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, pub smtp_password: Option, } impl MailConfig { fn load() -> Option { 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, }) } }