|
|
|
@ -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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|