vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 86ed75bf7c95d35b41b4e5553b5aece708b4a3cd
parent 20d8d800f380ad5ade821f7485916f321fdda1cf
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Sat,  2 Feb 2019 01:09:21 +0100

Config can now be serialized / deserialized

Diffstat:
Msrc/api/admin.rs | 20++++++++++++++++++--
Msrc/api/core/accounts.rs | 4++--
Msrc/api/core/organizations.rs | 26++++++++++++--------------
Msrc/api/core/two_factor.rs | 44+++++++++++++++++++-------------------------
Msrc/api/notifications.rs | 2+-
Msrc/config.rs | 311+++++++++++++++++++++++++++++++------------------------------------------------
Msrc/mail.rs | 34+++++++++++++++++-----------------
Msrc/util.rs | 41++++++++++++++++++++++-------------------
8 files changed, 214 insertions(+), 268 deletions(-)

diff --git a/src/api/admin.rs b/src/api/admin.rs @@ -24,6 +24,8 @@ pub fn routes() -> Vec<Route> { invite_user, delete_user, deauth_user, + get_config, + post_config, ] } @@ -136,11 +138,11 @@ fn invite_user(data: JsonUpcase<InviteData>, _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 = "<data>")] +fn post_config(data: JsonUpcase<Value>, _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 @@ -419,8 +419,8 @@ fn password_hint(data: JsonUpcase<PasswordHintData>, 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 @@ -486,9 +486,9 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, 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<InviteData>, 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<InviteData>, 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<InviteData>, 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<AcceptD None => 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<AcceptD }; if let Some(invited_by_email) = &claims.invited_by_email { // User was invited to an organization, so they must be confirmed manually after acceptance - mail::send_invite_accepted(&claims.email, invited_by_email, &org_name, mail_config)?; + mail::send_invite_accepted(&claims.email, invited_by_email, &org_name)?; } else { // User was invited from /admin, so they are automatically confirmed - mail::send_invite_confirmed(&claims.email, &org_name, mail_config)?; + mail::send_invite_confirmed(&claims.email, &org_name)?; } } @@ -686,7 +684,7 @@ fn confirm_invite( None => 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 @@ -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<String>) -> 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 = "<data>")] fn generate_yubikey(data: JsonUpcase<PasswordData>, 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<EnableYubikeyData>, 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<String> = yubikeys.into_iter().map(|x| (&x[..12]).to_owned()).collect(); diff --git 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 @@ -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, - }) - } -} diff --git 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<String>, config: &MailConfig) -> EmptyResult { +pub fn send_password_hint(address: &str, hint: Option<String>) -> 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<String>, 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<String>, org_name: &str, invited_by_email: Option<String>, - 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 @@ -140,18 +140,6 @@ where } } -pub fn try_parse_string_or<S, T, U>(string: impl Try<Ok = S, Error = U>, default: T) -> T -where - S: AsRef<str>, - T: FromStr, -{ - if let Ok(Ok(value)) = string.into_result().map(|s| s.as_ref().parse::<T>()) { - value - } else { - default - } -} - // // Env methods // @@ -165,13 +153,6 @@ where try_parse_string(env::var(key)) } -pub fn get_env_or<V>(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<T> { + fn into_result(self) -> Result<T, Error>; +} + +impl<T> IntoResult<T> for Result<T, Error> { + fn into_result(self) -> Result<T, Error> { + self + } +} + +impl<T> IntoResult<T> for T { + fn into_result(self) -> Result<T, Error> { + Ok(self) + } +}