From 86ed75bf7c95d35b41b4e5553b5aece708b4a3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Sat, 2 Feb 2019 01:09:21 +0100 Subject: [PATCH] Config can now be serialized / deserialized --- src/api/admin.rs | 20 ++- src/api/core/accounts.rs | 4 +- src/api/core/organizations.rs | 26 ++- src/api/core/two_factor.rs | 44 +++-- src/api/notifications.rs | 2 +- src/config.rs | 297 +++++++++++++--------------------- src/mail.rs | 34 ++-- src/util.rs | 41 ++--- 8 files changed, 207 insertions(+), 261 deletions(-) diff --git a/src/api/admin.rs b/src/api/admin.rs index 5b690af..01d7669 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -24,6 +24,8 @@ pub fn routes() -> Vec { invite_user, delete_user, deauth_user, + get_config, + post_config, ] } @@ -136,11 +138,11 @@ fn invite_user(data: JsonUpcase, _token: AdminToken, conn: DbConn) - err!("Invitations are not allowed") } - if let Some(ref mail_config) = CONFIG.mail() { + if CONFIG.mail_enabled() { let mut user = User::new(email); user.save(&conn)?; let org_name = "bitwarden_rs"; - mail::send_invite(&user.email, &user.uuid, None, None, &org_name, None, mail_config) + mail::send_invite(&user.email, &user.uuid, None, None, &org_name, None) } else { let mut invitation = Invitation::new(data.Email); invitation.save(&conn) @@ -169,6 +171,20 @@ fn deauth_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { user.save(&conn) } +#[get("/config")] +fn get_config(_token: AdminToken) -> EmptyResult { + unimplemented!("Get config") +} + +#[post("/config", data = "")] +fn post_config(data: JsonUpcase, _token: AdminToken) -> EmptyResult { + let data: Value = data.into_inner().data; + + info!("CONFIG: {:#?}", data); + + unimplemented!("Update config") +} + pub struct AdminToken {} impl<'a, 'r> FromRequest<'a, 'r> for AdminToken { diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 770f1da..beeb74b 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -419,8 +419,8 @@ fn password_hint(data: JsonUpcase, conn: DbConn) -> EmptyResul None => return Ok(()), }; - if let Some(ref mail_config) = CONFIG.mail() { - mail::send_password_hint(&data.Email, hint, mail_config)?; + if CONFIG.mail_enabled() { + mail::send_password_hint(&data.Email, hint)?; } else if CONFIG.show_password_hint() { if let Some(hint) = hint { err!(format!("Your password hint is: {}", &hint)); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 4bc2282..5fee26e 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -486,9 +486,9 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade } for email in data.Emails.iter() { - let mut user_org_status = match CONFIG.mail() { - Some(_) => UserOrgStatus::Invited as i32, - None => UserOrgStatus::Accepted as i32, // Automatically mark user as accepted if no email invites + let mut user_org_status = match CONFIG.mail_enabled() { + true => UserOrgStatus::Invited as i32, + false => UserOrgStatus::Accepted as i32, // Automatically mark user as accepted if no email invites }; let user = match User::find_by_mail(&email, &conn) { None => { @@ -496,7 +496,7 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade err!(format!("User email does not exist: {}", email)) } - if CONFIG.mail().is_none() { + if !CONFIG.mail_enabled() { let mut invitation = Invitation::new(email.clone()); invitation.save(&conn)?; } @@ -535,7 +535,7 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade new_user.save(&conn)?; - if let Some(ref mail_config) = CONFIG.mail() { + if CONFIG.mail_enabled() { let org_name = match Organization::find_by_uuid(&org_id, &conn) { Some(org) => org.name, None => err!("Error looking up organization"), @@ -548,7 +548,6 @@ fn send_invite(org_id: String, data: JsonUpcase, headers: AdminHeade Some(new_user.uuid), &org_name, Some(headers.user.email.clone()), - mail_config, )?; } } @@ -562,7 +561,7 @@ fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: err!("Invitations are not allowed.") } - if CONFIG.mail().is_none() { + if !CONFIG.mail_enabled() { err!("SMTP is not configured.") } @@ -585,7 +584,7 @@ fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: None => err!("Error looking up organization."), }; - if let Some(ref mail_config) = CONFIG.mail() { + if CONFIG.mail_enabled() { mail::send_invite( &user.email, &user.uuid, @@ -593,7 +592,6 @@ fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: Some(user_org.uuid), &org_name, Some(headers.user.email), - mail_config, )?; } else { let mut invitation = Invitation::new(user.email.clone()); @@ -637,7 +635,7 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase err!("Invited user not found"), } - if let Some(ref mail_config) = CONFIG.mail() { + if CONFIG.mail_enabled() { let mut org_name = String::from("bitwarden_rs"); if let Some(org_id) = &claims.org_id { org_name = match Organization::find_by_uuid(&org_id, &conn) { @@ -647,10 +645,10 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase err!("Invalid key provided"), }; - if let Some(ref mail_config) = CONFIG.mail() { + if CONFIG.mail_enabled() { let org_name = match Organization::find_by_uuid(&org_id, &conn) { Some(org) => org.name, None => err!("Error looking up organization."), @@ -695,7 +693,7 @@ fn confirm_invite( Some(user) => user.email, None => err!("Error looking up user."), }; - mail::send_invite_confirmed(&address, &org_name, mail_config)?; + mail::send_invite_confirmed(&address, &org_name)?; } user_to_confirm.save(&conn) diff --git a/src/api/core/two_factor.rs b/src/api/core/two_factor.rs index 5682d9f..ba4d01a 100644 --- a/src/api/core/two_factor.rs +++ b/src/api/core/two_factor.rs @@ -3,15 +3,14 @@ use rocket_contrib::json::Json; use serde_json; use serde_json::Value; +use crate::api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordData}; +use crate::auth::Headers; +use crate::crypto; use crate::db::{ models::{TwoFactor, TwoFactorType, User}, DbConn, }; - -use crate::crypto; - -use crate::api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordData}; -use crate::auth::Headers; +use crate::error::{Error, MapResult}; use rocket::Route; @@ -508,32 +507,31 @@ fn jsonify_yubikeys(yubikeys: Vec) -> serde_json::Value { result } -fn verify_yubikey_otp(otp: String) -> JsonResult { - if !CONFIG.yubico_cred_set() { - err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. Yubikey OTP Disabled") +fn get_yubico_credentials() -> Result<(String, String), Error> { + match (CONFIG.yubico_client_id(), CONFIG.yubico_secret_key()) { + (Some(id), Some(secret)) => Ok((id, secret)), + _ => err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. Yubikey OTP Disabled"), } +} + +fn verify_yubikey_otp(otp: String) -> EmptyResult { + let (yubico_id, yubico_secret) = get_yubico_credentials()?; let yubico = Yubico::new(); - let config = Config::default() - .set_client_id(CONFIG.yubico_client_id()) - .set_key(CONFIG.yubico_secret_key()); + let config = Config::default().set_client_id(yubico_id).set_key(yubico_secret); - let result = match CONFIG.yubico_server() { + match CONFIG.yubico_server() { Some(server) => yubico.verify(otp, config.set_api_hosts(vec![server])), None => yubico.verify(otp, config), - }; - - match result { - Ok(_answer) => Ok(Json(json!({}))), - Err(_e) => err!("Failed to verify OTP"), } + .map_res("Failed to verify OTP") + .and(Ok(())) } #[post("/two-factor/get-yubikey", data = "")] fn generate_yubikey(data: JsonUpcase, headers: Headers, conn: DbConn) -> JsonResult { - if !CONFIG.yubico_cred_set() { - err!("`YUBICO_CLIENT_ID` or `YUBICO_SECRET_KEY` environment variable is not set. Yubikey OTP Disabled") - } + // Make sure the credentials are set + get_yubico_credentials()?; let data: PasswordData = data.into_inner().data; let user = headers.user; @@ -597,11 +595,7 @@ fn activate_yubikey(data: JsonUpcase, headers: Headers, conn: continue; } - let result = verify_yubikey_otp(yubikey.to_owned()); - - if let Err(_e) = result { - err!("Invalid Yubikey OTP provided"); - } + verify_yubikey_otp(yubikey.to_owned()).map_res("Invalid Yubikey OTP provided")?; } let yubikey_ids: Vec = yubikeys.into_iter().map(|x| (&x[..12]).to_owned()).collect(); diff --git a/src/api/notifications.rs b/src/api/notifications.rs index d5f8606..c790e32 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -355,7 +355,7 @@ pub fn start_notification_server() -> WebSocketUsers { thread::spawn(move || { WebSocket::new(factory) .unwrap() - .listen(&CONFIG.websocket_url()) + .listen((CONFIG.websocket_address().as_str(), CONFIG.websocket_port())) .unwrap(); }); } diff --git a/src/config.rs b/src/config.rs index 2c230bd..46f69ff 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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 } - #[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 [](&self, value: $ty) { + self.inner.write().unwrap().config.$name = value; + } + )+ + + pub fn load() -> Result { + 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 [](&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, + }), + }) } } + } }; + + ( @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()? + } + } + }}; + + ( @expr $name:expr, $ty:ty, $config:expr, ) => { + get_env($name) + }; } make_config! { - database_url: String, - icon_cache_folder: String, - attachments_folder: String, + 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"); - icon_cache_ttl: u64, - icon_cache_negttl: u64, + 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); - private_rsa_key: String, - private_rsa_key_pem: String, - public_rsa_key: String, + websocket_enabled: bool, |_| false; + websocket_address: String, |_| "0.0.0.0".to_string(); + websocket_port: u16, |_| 3012; - web_vault_folder: String, - web_vault_enabled: bool, + web_vault_folder: String, |_| "web-vault/".to_string(); + web_vault_enabled: bool, |_| true; - websocket_enabled: bool, - websocket_url: String, + icon_cache_ttl: u64, |_| 2_592_000; + icon_cache_negttl: u64, |_| 259_200; - extended_logging: bool, - log_file: Option, + disable_icon_download: bool, |_| false; + signups_allowed: bool, |_| true; + invitations_allowed: bool, |_| true; + password_iterations: i32, |_| 100_000; + show_password_hint: bool, |_| true; - disable_icon_download: bool, - signups_allowed: bool, - invitations_allowed: bool, - admin_token: Option, - password_iterations: i32, - show_password_hint: bool, + domain: String, |_| "http://localhost".to_string(); + domain_set: bool, |_| false; - domain: String, - domain_set: bool, + reload_templates: bool, |_| false; - yubico_cred_set: bool, - yubico_client_id: String, - yubico_secret_key: String, - yubico_server: Option, + extended_logging: bool, |_| true; + log_file: Option; - mail: Option, - templates_folder: String, - reload_templates: bool, + admin_token: Option; + + yubico_client_id: Option; + yubico_secret_key: Option; + yubico_server: Option; + + // Mail settings + smtp_host: Option; + 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; + smtp_password: Option; +} + +impl Config { + pub fn mail_enabled(&self) -> bool { + self.inner.read().unwrap().config.smtp_host.is_some() + } + + 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_templates(path: &str) -> Handlebars { @@ -106,140 +178,3 @@ fn load_templates(path: &str) -> Handlebars { 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"), - - 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, - 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, - }) - } -} diff --git a/src/mail.rs b/src/mail.rs index 0920f9d..8c0d65d 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -6,25 +6,26 @@ use native_tls::{Protocol, TlsConnector}; use crate::api::EmptyResult; use crate::auth::{encode_jwt, generate_invite_claims}; -use crate::config::MailConfig; use crate::error::Error; use crate::CONFIG; -fn mailer(config: &MailConfig) -> SmtpTransport { - let client_security = if config.smtp_ssl { +fn mailer() -> SmtpTransport { + let host = CONFIG.smtp_host().unwrap(); + + let client_security = if CONFIG.smtp_ssl() { let tls = TlsConnector::builder() .min_protocol_version(Some(Protocol::Tlsv11)) .build() .unwrap(); - ClientSecurity::Required(ClientTlsParameters::new(config.smtp_host.clone(), tls)) + ClientSecurity::Required(ClientTlsParameters::new(host.clone(), tls)) } else { ClientSecurity::None }; - let smtp_client = SmtpClient::new((config.smtp_host.as_str(), config.smtp_port), client_security).unwrap(); + let smtp_client = SmtpClient::new((host.as_str(), CONFIG.smtp_port()), client_security).unwrap(); - let smtp_client = match (&config.smtp_username, &config.smtp_password) { + let smtp_client = match (&CONFIG.smtp_username(), &CONFIG.smtp_password()) { (Some(user), Some(pass)) => smtp_client.credentials(Credentials::new(user.clone(), pass.clone())), _ => smtp_client, }; @@ -52,7 +53,7 @@ fn get_text(template_name: &'static str, data: serde_json::Value) -> Result<(Str Ok((subject, body)) } -pub fn send_password_hint(address: &str, hint: Option, config: &MailConfig) -> EmptyResult { +pub fn send_password_hint(address: &str, hint: Option) -> EmptyResult { let template_name = if hint.is_some() { "email/pw_hint_some" } else { @@ -61,7 +62,7 @@ pub fn send_password_hint(address: &str, hint: Option, config: &MailConf let (subject, body) = get_text(template_name, json!({ "hint": hint }))?; - send_email(&address, &subject, &body, &config) + send_email(&address, &subject, &body) } pub fn send_invite( @@ -71,7 +72,6 @@ pub fn send_invite( org_user_id: Option, org_name: &str, invited_by_email: Option, - config: &MailConfig, ) -> EmptyResult { let claims = generate_invite_claims( uuid.to_string(), @@ -94,10 +94,10 @@ pub fn send_invite( }), )?; - send_email(&address, &subject, &body, &config) + send_email(&address, &subject, &body) } -pub fn send_invite_accepted(new_user_email: &str, address: &str, org_name: &str, config: &MailConfig) -> EmptyResult { +pub fn send_invite_accepted(new_user_email: &str, address: &str, org_name: &str) -> EmptyResult { let (subject, body) = get_text( "email/invite_accepted", json!({ @@ -107,10 +107,10 @@ pub fn send_invite_accepted(new_user_email: &str, address: &str, org_name: &str, }), )?; - send_email(&address, &subject, &body, &config) + send_email(&address, &subject, &body) } -pub fn send_invite_confirmed(address: &str, org_name: &str, config: &MailConfig) -> EmptyResult { +pub fn send_invite_confirmed(address: &str, org_name: &str) -> EmptyResult { let (subject, body) = get_text( "email/invite_confirmed", json!({ @@ -119,20 +119,20 @@ pub fn send_invite_confirmed(address: &str, org_name: &str, config: &MailConfig) }), )?; - send_email(&address, &subject, &body, &config) + send_email(&address, &subject, &body) } -fn send_email(address: &str, subject: &str, body: &str, config: &MailConfig) -> EmptyResult { +fn send_email(address: &str, subject: &str, body: &str) -> EmptyResult { let email = EmailBuilder::new() .to(address) - .from((config.smtp_from.as_str(), config.smtp_from_name.as_str())) + .from((CONFIG.smtp_from().as_str(), CONFIG.smtp_from_name().as_str())) .subject(subject) .header(("Content-Type", "text/html")) .body(body) .build() .map_err(|e| Error::new("Error building email", e.to_string()))?; - mailer(config) + mailer() .send(email.into()) .map_err(|e| Error::new("Error sending email", e.to_string())) .and(Ok(())) diff --git a/src/util.rs b/src/util.rs index 06ea386..53d13a3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -140,18 +140,6 @@ where } } -pub fn try_parse_string_or(string: impl Try, default: T) -> T -where - S: AsRef, - T: FromStr, -{ - if let Ok(Ok(value)) = string.into_result().map(|s| s.as_ref().parse::()) { - value - } else { - default - } -} - // // Env methods // @@ -165,13 +153,6 @@ where try_parse_string(env::var(key)) } -pub fn get_env_or(key: &str, default: V) -> V -where - V: FromStr, -{ - try_parse_string_or(env::var(key), default) -} - // // Date util methods // @@ -303,3 +284,25 @@ where } } } + + +// +// Into Result +// +use crate::error::Error; + +pub trait IntoResult { + fn into_result(self) -> Result; +} + +impl IntoResult for Result { + fn into_result(self) -> Result { + self + } +} + +impl IntoResult for T { + fn into_result(self) -> Result { + Ok(self) + } +}