2023-02-02 22:05:44 +01:00
use std ::env ::consts ::EXE_SUFFIX ;
2019-01-25 18:23:51 +01:00
use std ::process ::exit ;
use std ::sync ::RwLock ;
2022-10-26 13:02:05 +02:00
use job_scheduler_ng ::Schedule ;
2020-07-14 18:00:09 +02:00
use once_cell ::sync ::Lazy ;
2020-02-19 06:27:00 +01:00
use reqwest ::Url ;
2020-07-14 18:00:09 +02:00
use crate ::{
2020-08-18 17:15:44 +02:00
db ::DbConnType ,
2020-07-14 18:00:09 +02:00
error ::Error ,
2024-01-01 15:44:02 +01:00
util ::{ get_env , get_env_bool , parse_experimental_client_feature_flags } ,
2020-07-14 18:00:09 +02:00
} ;
2019-02-02 01:09:21 +01:00
2020-03-09 22:04:03 +01:00
static CONFIG_FILE : Lazy < String > = Lazy ::new ( | | {
let data_folder = get_env ( " DATA_FOLDER " ) . unwrap_or_else ( | | String ::from ( " data " ) ) ;
2022-12-29 14:11:52 +01:00
get_env ( " CONFIG_FILE " ) . unwrap_or_else ( | | format! ( " {data_folder} /config.json " ) )
2020-03-09 22:04:03 +01:00
} ) ;
pub static CONFIG : Lazy < Config > = Lazy ::new ( | | {
Config ::load ( ) . unwrap_or_else ( | e | {
2023-02-28 23:09:51 +01:00
println! ( " Error loading config: \n {e:?} \n " ) ;
2019-02-02 01:09:21 +01:00
exit ( 12 )
2020-03-09 22:04:03 +01:00
} )
} ) ;
2019-01-25 18:23:51 +01:00
2019-02-08 20:49:04 +01:00
pub type Pass = String ;
2019-01-25 18:23:51 +01:00
macro_rules ! make_config {
2019-02-06 17:32:13 +01:00
( $(
$( #[ doc = $groupdoc:literal ] ) ?
$group :ident $( : $group_enabled :ident ) ? {
2019-02-05 22:17:02 +01:00
$(
2019-02-06 17:32:13 +01:00
$( #[ doc = $doc:literal ] ) +
2020-01-20 22:28:54 +01:00
$name :ident : $ty :ident , $editable :literal , $none_action :ident $(, $default :expr ) ? ;
2019-02-06 17:32:13 +01:00
) + } ,
) + ) = > {
2019-02-02 01:09:21 +01:00
pub struct Config { inner : RwLock < Inner > }
2019-01-25 18:23:51 +01:00
2019-02-02 01:09:21 +01:00
struct Inner {
2021-11-07 18:53:39 +01:00
rocket_shutdown_handle : Option < rocket ::Shutdown > ,
2020-01-26 15:29:14 +01:00
templates : Handlebars < 'static > ,
2019-02-02 16:47:27 +01:00
config : ConfigItems ,
2019-02-03 00:22:18 +01:00
_env : ConfigBuilder ,
_usr : ConfigBuilder ,
2021-06-19 19:22:19 +02:00
_overrides : Vec < String > ,
2019-01-25 18:23:51 +01:00
}
2021-11-05 19:18:54 +01:00
#[ derive(Clone, Default, Deserialize, Serialize) ]
2019-02-02 16:47:27 +01:00
pub struct ConfigBuilder {
2019-02-05 22:17:02 +01:00
$( $(
2019-02-03 00:22:18 +01:00
#[ serde(skip_serializing_if = " Option::is_none " ) ]
2019-02-05 22:17:02 +01:00
$name : Option < $ty > ,
) + ) +
2019-02-02 16:47:27 +01:00
}
impl ConfigBuilder {
2021-01-19 17:55:21 +01:00
#[ allow(clippy::field_reassign_with_default) ]
2019-02-02 16:47:27 +01:00
fn from_env ( ) -> Self {
2023-01-02 18:18:28 +01:00
let env_file = get_env ( " ENV_FILE " ) . unwrap_or_else ( | | String ::from ( " .env " ) ) ;
match dotenvy ::from_path ( & env_file ) {
Ok ( _ ) = > {
println! ( " [INFO] Using environment file ` {env_file} ` for configuration. \n " ) ;
} ,
2020-11-12 13:40:26 +01:00
Err ( e ) = > match e {
2022-03-20 18:51:24 +01:00
dotenvy ::Error ::LineParse ( msg , pos ) = > {
2023-01-02 18:18:28 +01:00
println! ( " [ERROR] Failed parsing environment file: ` {env_file} ` \n Near {msg:?} on position {pos} \n Please fix and restart! \n " ) ;
exit ( 255 ) ;
2020-11-12 13:40:26 +01:00
} ,
2022-03-20 18:51:24 +01:00
dotenvy ::Error ::Io ( ioerr ) = > match ioerr . kind ( ) {
2020-11-12 13:40:26 +01:00
std ::io ::ErrorKind ::NotFound = > {
2023-01-02 18:18:28 +01:00
// Only exit if this environment variable is set, but the file was not found.
// This prevents incorrectly configured environments.
if let Some ( env_file ) = get_env ::< String > ( " ENV_FILE " ) {
println! ( " [ERROR] The configured ENV_FILE ` {env_file} ` was not found! \n " ) ;
exit ( 255 ) ;
}
2020-11-12 13:40:26 +01:00
} ,
std ::io ::ErrorKind ::PermissionDenied = > {
2023-01-02 18:18:28 +01:00
println! ( " [ERROR] Permission denied while trying to read environment file ` {env_file} `! \n " ) ;
exit ( 255 ) ;
2020-11-12 13:40:26 +01:00
} ,
_ = > {
2023-01-02 18:18:28 +01:00
println! ( " [ERROR] Reading environment file ` {env_file} ` failed: \n {ioerr:?} \n " ) ;
exit ( 255 ) ;
2020-11-12 13:40:26 +01:00
}
} ,
_ = > {
2023-01-02 18:18:28 +01:00
println! ( " [ERROR] Reading environment file ` {env_file} ` failed: \n {e:?} \n " ) ;
exit ( 255 ) ;
2020-11-12 13:40:26 +01:00
}
}
} ;
2019-02-02 16:47:27 +01:00
let mut builder = ConfigBuilder ::default ( ) ;
2019-02-05 22:17:02 +01:00
$( $(
2022-12-15 17:15:48 +01:00
builder . $name = make_config! { @ getenv paste ::paste! ( stringify! ( [ < $name :upper > ] ) ) , $ty } ;
2019-02-05 22:17:02 +01:00
) + ) +
2020-03-09 22:04:03 +01:00
2019-02-02 16:47:27 +01:00
builder
}
fn from_file ( path : & str ) -> Result < Self , Error > {
2022-07-15 19:13:26 +02:00
let config_str = std ::fs ::read_to_string ( path ) ? ;
2023-01-02 18:18:28 +01:00
println! ( " [INFO] Using saved config from ` {path} ` for configuration. \n " ) ;
2019-02-02 16:47:27 +01:00
serde_json ::from_str ( & config_str ) . map_err ( Into ::into )
}
2019-02-03 00:22:18 +01:00
/// Merges the values of both builders into a new builder.
/// If both have the same element, `other` wins.
2021-06-19 19:22:19 +02:00
fn merge ( & self , other : & Self , show_overrides : bool , overrides : & mut Vec < String > ) -> Self {
2019-02-03 00:22:18 +01:00
let mut builder = self . clone ( ) ;
2019-02-05 22:17:02 +01:00
$( $(
2019-02-03 00:22:18 +01:00
if let v @ Some ( _ ) = & other . $name {
builder . $name = v . clone ( ) ;
2019-02-20 20:59:37 +01:00
if self . $name . is_some ( ) {
2022-12-15 17:15:48 +01:00
overrides . push ( paste ::paste! ( stringify! ( [ < $name :upper > ] ) ) . into ( ) ) ;
2019-02-20 20:59:37 +01:00
}
2019-02-02 16:47:27 +01:00
}
2019-02-05 22:17:02 +01:00
) + ) +
2019-02-20 20:59:37 +01:00
2019-04-11 16:08:26 +02:00
if show_overrides & & ! overrides . is_empty ( ) {
2019-02-20 20:59:37 +01:00
// We can't use warn! here because logging isn't setup yet.
2023-10-05 19:08:26 +02:00
println! ( " [WARNING] The following environment variables are being overridden by the config.json file. " ) ;
2023-01-02 18:18:28 +01:00
println! ( " [WARNING] Please use the admin panel to make changes to them: " ) ;
2019-02-20 20:59:37 +01:00
println! ( " [WARNING] {} \n " , overrides . join ( " , " ) ) ;
}
2019-02-03 00:22:18 +01:00
builder
2019-02-02 16:47:27 +01:00
}
2019-02-03 00:22:18 +01:00
fn build ( & self ) -> ConfigItems {
2019-02-02 16:47:27 +01:00
let mut config = ConfigItems ::default ( ) ;
let _domain_set = self . domain . is_some ( ) ;
2019-02-05 22:17:02 +01:00
$( $(
2019-02-03 00:22:18 +01:00
config . $name = make_config! { @ build self . $name . clone ( ) , & config , $none_action , $( $default ) ? } ;
2019-02-05 22:17:02 +01:00
) + ) +
2019-02-02 16:47:27 +01:00
config . domain_set = _domain_set ;
2023-02-07 18:49:26 +01:00
config . domain = config . domain . trim_end_matches ( '/' ) . to_string ( ) ;
2023-02-07 18:34:47 +01:00
2020-04-09 10:42:27 +02:00
config . signups_domains_whitelist = config . signups_domains_whitelist . trim ( ) . to_lowercase ( ) ;
2020-08-06 07:35:29 +02:00
config . org_creation_users = config . org_creation_users . trim ( ) . to_lowercase ( ) ;
2020-04-09 10:42:27 +02:00
2024-07-12 22:33:11 +02:00
// Copy the values from the deprecated flags to the new ones
if config . http_request_block_regex . is_none ( ) {
config . http_request_block_regex = config . icon_blacklist_regex . clone ( ) ;
}
2019-02-02 16:47:27 +01:00
config
}
}
2021-11-05 19:18:54 +01:00
#[ derive(Clone, Default) ]
2021-10-23 20:46:39 +02:00
struct ConfigItems { $( $( $name : make_config ! { @ type $ty , $none_action } , ) + ) + }
2019-02-02 01:09:21 +01:00
#[ allow(unused) ]
impl Config {
2019-02-05 22:17:02 +01:00
$( $(
2021-10-23 20:46:39 +02:00
$( #[ doc = $doc ] ) +
2019-02-03 00:22:18 +01:00
pub fn $name ( & self ) -> make_config ! { @ type $ty , $none_action } {
2019-02-02 01:09:21 +01:00
self . inner . read ( ) . unwrap ( ) . config . $name . clone ( )
}
2019-02-05 22:17:02 +01:00
) + ) +
2019-02-02 01:09:21 +01:00
2019-02-03 00:22:18 +01:00
pub fn prepare_json ( & self ) -> serde_json ::Value {
2023-10-05 19:08:26 +02:00
let ( def , cfg , overridden ) = {
2019-02-03 00:22:18 +01:00
let inner = & self . inner . read ( ) . unwrap ( ) ;
2021-06-19 19:22:19 +02:00
( inner . _env . build ( ) , inner . config . clone ( ) , inner . _overrides . clone ( ) )
2019-02-03 00:22:18 +01:00
} ;
fn _get_form_type ( rust_type : & str ) -> & 'static str {
match rust_type {
2019-02-08 20:49:04 +01:00
" Pass " = > " password " ,
2019-02-03 00:22:18 +01:00
" String " = > " text " ,
" bool " = > " checkbox " ,
_ = > " number "
}
}
2019-02-02 16:47:27 +01:00
2019-02-04 01:37:25 +01:00
fn _get_doc ( doc : & str ) -> serde_json ::Value {
let mut split = doc . split ( " |> " ) . map ( str ::trim ) ;
2021-11-05 19:18:54 +01:00
// We do not use the json!() macro here since that causes a lot of macro recursion.
// This slows down compile time and it also causes issues with rust-analyzer
serde_json ::Value ::Object ( {
let mut doc_json = serde_json ::Map ::new ( ) ;
doc_json . insert ( " name " . into ( ) , serde_json ::to_value ( split . next ( ) ) . unwrap ( ) ) ;
doc_json . insert ( " description " . into ( ) , serde_json ::to_value ( split . next ( ) ) . unwrap ( ) ) ;
doc_json
2019-02-04 01:37:25 +01:00
} )
}
2021-11-05 19:18:54 +01:00
// We do not use the json!() macro here since that causes a lot of macro recursion.
// This slows down compile time and it also causes issues with rust-analyzer
serde_json ::Value ::Array ( < [ _ ] > ::into_vec ( Box ::new ( [
$(
serde_json ::Value ::Object ( {
let mut group = serde_json ::Map ::new ( ) ;
group . insert ( " group " . into ( ) , ( stringify! ( $group ) ) . into ( ) ) ;
group . insert ( " grouptoggle " . into ( ) , ( stringify! ( $( $group_enabled ) ? ) ) . into ( ) ) ;
group . insert ( " groupdoc " . into ( ) , ( make_config! { @ show $( $groupdoc ) ? } ) . into ( ) ) ;
group . insert ( " elements " . into ( ) , serde_json ::Value ::Array ( < [ _ ] > ::into_vec ( Box ::new ( [
$(
serde_json ::Value ::Object ( {
let mut element = serde_json ::Map ::new ( ) ;
element . insert ( " editable " . into ( ) , ( $editable ) . into ( ) ) ;
element . insert ( " name " . into ( ) , ( stringify! ( $name ) ) . into ( ) ) ;
element . insert ( " value " . into ( ) , serde_json ::to_value ( cfg . $name ) . unwrap ( ) ) ;
element . insert ( " default " . into ( ) , serde_json ::to_value ( def . $name ) . unwrap ( ) ) ;
element . insert ( " type " . into ( ) , ( _get_form_type ( stringify! ( $ty ) ) ) . into ( ) ) ;
element . insert ( " doc " . into ( ) , ( _get_doc ( concat! ( $( $doc ) , + ) ) ) . into ( ) ) ;
2023-10-05 19:08:26 +02:00
element . insert ( " overridden " . into ( ) , ( overridden . contains ( & paste ::paste! ( stringify! ( [ < $name :upper > ] ) ) . into ( ) ) ) . into ( ) ) ;
2021-11-05 19:18:54 +01:00
element
} ) ,
) +
] ) ) ) ) ;
group
} ) ,
) +
] ) ) )
2019-02-03 00:22:18 +01:00
}
2021-01-19 17:55:21 +01:00
pub fn get_support_json ( & self ) -> serde_json ::Value {
2021-11-05 19:18:54 +01:00
// Define which config keys need to be masked.
// Pass types will always be masked and no need to put them in the list.
// Besides Pass, only String types will be masked via _privacy_mask.
const PRIVACY_CONFIG : & [ & str ] = & [
" allowed_iframe_ancestors " ,
" database_url " ,
" domain_origin " ,
" domain_path " ,
" domain " ,
" helo_name " ,
" org_creation_users " ,
" signups_domains_whitelist " ,
" smtp_from " ,
" smtp_host " ,
" smtp_username " ,
] ;
2021-01-19 17:55:21 +01:00
let cfg = {
let inner = & self . inner . read ( ) . unwrap ( ) ;
inner . config . clone ( )
} ;
2021-11-05 19:18:54 +01:00
/// We map over the string and remove all alphanumeric, _ and - characters.
/// This is the fastest way (within micro-seconds) instead of using a regex (which takes mili-seconds)
fn _privacy_mask ( value : & str ) -> String {
2022-12-08 13:35:53 +01:00
let mut n : u16 = 0 ;
let mut colon_match = false ;
value
. chars ( )
. map ( | c | {
n + = 1 ;
match c {
':' if n < = 11 = > {
colon_match = true ;
c
}
'/' if n < = 13 & & colon_match = > c ,
',' = > c ,
_ = > '*' ,
}
} )
. collect ::< String > ( )
2021-11-05 19:18:54 +01:00
}
serde_json ::Value ::Object ( {
let mut json = serde_json ::Map ::new ( ) ;
$( $(
json . insert ( stringify! ( $name ) . into ( ) , make_config! { @ supportstr $name , cfg . $name , $ty , $none_action } ) ;
) + ) + ;
json
} )
2021-01-19 17:55:21 +01:00
}
2021-06-19 19:22:19 +02:00
pub fn get_overrides ( & self ) -> Vec < String > {
let overrides = {
let inner = & self . inner . read ( ) . unwrap ( ) ;
inner . _overrides . clone ( )
} ;
overrides
}
2021-01-19 17:55:21 +01:00
}
} ;
// Support string print
2021-11-05 19:18:54 +01:00
( @ supportstr $name :ident , $value :expr , Pass , option ) = > { serde_json ::to_value ( $value . as_ref ( ) . map ( | _ | String ::from ( " *** " ) ) ) . unwrap ( ) } ; // Optional pass, we map to an Option<String> with "***"
( @ supportstr $name :ident , $value :expr , Pass , $none_action :ident ) = > { " *** " . into ( ) } ; // Required pass, we return "***"
( @ supportstr $name :ident , $value :expr , String , option ) = > { // Optional other value, we return as is or convert to string to apply the privacy config
2021-01-19 17:55:21 +01:00
if PRIVACY_CONFIG . contains ( & stringify! ( $name ) ) {
2021-11-05 19:18:54 +01:00
serde_json ::to_value ( $value . as_ref ( ) . map ( | x | _privacy_mask ( x ) ) ) . unwrap ( )
2021-01-19 17:55:21 +01:00
} else {
2021-11-05 19:18:54 +01:00
serde_json ::to_value ( $value ) . unwrap ( )
2019-02-02 16:47:27 +01:00
}
} ;
2021-11-05 19:18:54 +01:00
( @ supportstr $name :ident , $value :expr , String , $none_action :ident ) = > { // Required other value, we return as is or convert to string to apply the privacy config
2021-01-19 17:55:21 +01:00
if PRIVACY_CONFIG . contains ( & stringify! ( $name ) ) {
2021-11-05 19:18:54 +01:00
_privacy_mask ( & $value ) . into ( )
} else {
( $value ) . into ( )
}
2021-01-19 17:55:21 +01:00
} ;
2021-11-05 19:18:54 +01:00
( @ supportstr $name :ident , $value :expr , $ty :ty , option ) = > { serde_json ::to_value ( $value ) . unwrap ( ) } ; // Optional other value, we return as is or convert to string to apply the privacy config
( @ supportstr $name :ident , $value :expr , $ty :ty , $none_action :ident ) = > { ( $value ) . into ( ) } ; // Required other value, we return as is or convert to string to apply the privacy config
2019-02-02 16:47:27 +01:00
2019-02-05 22:17:02 +01:00
// Group or empty string
( @ show ) = > { " " } ;
2019-02-06 00:38:57 +01:00
( @ show $lit :literal ) = > { $lit } ;
2019-02-05 22:17:02 +01:00
2019-02-03 00:22:18 +01:00
// Wrap the optionals in an Option type
( @ type $ty :ty , option ) = > { Option < $ty > } ;
( @ type $ty :ty , $id :ident ) = > { $ty } ;
// Generate the values depending on none_action
( @ build $value :expr , $config :expr , option , ) = > { $value } ;
( @ build $value :expr , $config :expr , def , $default :expr ) = > { $value . unwrap_or ( $default ) } ;
( @ build $value :expr , $config :expr , auto , $default_fn :expr ) = > { {
2019-02-02 16:47:27 +01:00
match $value {
2019-02-02 01:09:21 +01:00
Some ( v ) = > v ,
None = > {
2019-06-02 00:28:20 +02:00
let f : & dyn Fn ( & ConfigItems ) -> _ = & $default_fn ;
2019-02-02 16:47:27 +01:00
f ( $config )
2019-02-02 01:09:21 +01:00
}
}
2019-02-03 00:22:18 +01:00
} } ;
2019-12-27 18:42:39 +01:00
( @ build $value :expr , $config :expr , gen , $default_fn :expr ) = > { {
let f : & dyn Fn ( & ConfigItems ) -> _ = & $default_fn ;
f ( $config )
} } ;
2020-01-20 22:28:54 +01:00
( @ getenv $name :expr , bool ) = > { get_env_bool ( $name ) } ;
( @ getenv $name :expr , $ty :ident ) = > { get_env ( $name ) } ;
2019-02-02 01:09:21 +01:00
}
2019-01-25 18:23:51 +01:00
2019-02-04 01:37:25 +01:00
//STRUCTURE:
2019-02-05 22:17:02 +01:00
// /// Short description (without this they won't appear on the list)
// group {
// /// Friendly Name |> Description (Optional)
2019-12-27 18:42:39 +01:00
// name: type, is_editable, action, <default_value (Optional)>
2019-02-05 22:17:02 +01:00
// }
2019-02-04 01:37:25 +01:00
//
2019-12-27 18:42:39 +01:00
// Where action applied when the value wasn't provided and can be:
2019-02-03 00:22:18 +01:00
// def: Use a default value
// auto: Value is auto generated based on other values
// option: Value is optional
2019-12-27 18:42:39 +01:00
// gen: Value is always autogenerated and it's original value ignored
2019-02-02 01:09:21 +01:00
make_config! {
2019-02-05 22:17:02 +01:00
folders {
/// Data folder |> Main data folder
data_folder : String , false , def , " data " . to_string ( ) ;
2019-05-26 23:02:41 +02:00
/// Database URL
database_url : String , false , auto , | c | format! ( " {} / {} " , c . data_folder , " db.sqlite3 " ) ;
2019-06-18 16:45:19 +02:00
/// Icon cache folder
2019-02-05 22:17:02 +01:00
icon_cache_folder : String , false , auto , | c | format! ( " {} / {} " , c . data_folder , " icon_cache " ) ;
/// Attachments folder
attachments_folder : String , false , auto , | c | format! ( " {} / {} " , c . data_folder , " attachments " ) ;
2021-03-14 23:35:55 +01:00
/// Sends folder
sends_folder : String , false , auto , | c | format! ( " {} / {} " , c . data_folder , " sends " ) ;
2021-11-07 18:53:39 +01:00
/// Temp folder |> Used for storing temporary file uploads
2024-03-17 19:52:55 +01:00
tmp_folder : String , false , auto , | c | format! ( " {} / {} " , c . data_folder , " tmp " ) ;
2019-02-05 22:17:02 +01:00
/// Templates folder
templates_folder : String , false , auto , | c | format! ( " {} / {} " , c . data_folder , " templates " ) ;
/// Session JWT key
rsa_key_filename : String , false , auto , | c | format! ( " {} / {} " , c . data_folder , " rsa_key " ) ;
/// Web vault folder
web_vault_folder : String , false , def , " web-vault/ " . to_string ( ) ;
2019-02-06 17:32:13 +01:00
} ,
2019-02-05 22:17:02 +01:00
ws {
/// Enable websocket notifications
2024-03-17 19:52:55 +01:00
enable_websocket : bool , false , def , true ;
2019-02-05 22:17:02 +01:00
} ,
2023-06-11 13:28:18 +02:00
push {
/// Enable push notifications
push_enabled : bool , false , def , false ;
2024-01-01 16:01:57 +01:00
/// Push relay uri
2023-06-11 13:28:18 +02:00
push_relay_uri : String , false , def , " https://push.bitwarden.com " . to_string ( ) ;
2024-01-01 16:01:57 +01:00
/// Push identity uri
push_identity_uri : String , false , def , " https://identity.bitwarden.com " . to_string ( ) ;
2023-06-11 13:28:18 +02:00
/// Installation id |> The installation id from https://bitwarden.com/host
push_installation_id : Pass , false , def , String ::new ( ) ;
/// Installation key |> The installation key from https://bitwarden.com/host
push_installation_key : Pass , false , def , String ::new ( ) ;
} ,
2021-04-03 05:16:49 +02:00
jobs {
/// Job scheduler poll interval |> How often the job scheduler thread checks for jobs to run.
/// Set to 0 to globally disable scheduled jobs.
job_poll_interval_ms : u64 , false , def , 30_000 ;
/// Send purge schedule |> Cron schedule of the job that checks for Sends past their deletion date.
/// Defaults to hourly. Set blank to disable this job.
2021-04-06 08:12:36 +02:00
send_purge_schedule : String , false , def , " 0 5 * * * * " . to_string ( ) ;
2021-04-03 05:52:15 +02:00
/// Trash purge schedule |> Cron schedule of the job that checks for trashed items to delete permanently.
/// Defaults to daily. Set blank to disable this job.
2021-04-06 08:12:36 +02:00
trash_purge_schedule : String , false , def , " 0 5 0 * * * " . to_string ( ) ;
2021-10-25 10:36:05 +02:00
/// Incomplete 2FA login schedule |> Cron schedule of the job that checks for incomplete 2FA logins.
/// Defaults to once every minute. Set blank to disable this job.
incomplete_2fa_schedule : String , false , def , " 30 * * * * * " . to_string ( ) ;
2021-10-19 10:27:50 +02:00
/// Emergency notification reminder schedule |> Cron schedule of the job that sends expiration reminders to emergency access grantors.
2022-11-26 19:07:28 +01:00
/// Defaults to hourly. (3 minutes after the hour) Set blank to disable this job.
emergency_notification_reminder_schedule : String , false , def , " 0 3 * * * * " . to_string ( ) ;
2021-10-19 10:27:50 +02:00
/// Emergency request timeout schedule |> Cron schedule of the job that grants emergency access requests that have met the required wait time.
2022-11-26 19:07:28 +01:00
/// Defaults to hourly. (7 minutes after the hour) Set blank to disable this job.
emergency_request_timeout_schedule : String , false , def , " 0 7 * * * * " . to_string ( ) ;
2022-11-20 19:15:45 +01:00
/// Event cleanup schedule |> Cron schedule of the job that cleans old events from the event table.
/// Defaults to daily. Set blank to disable this job.
event_cleanup_schedule : String , false , def , " 0 10 0 * * * " . to_string ( ) ;
2023-08-04 21:12:23 +02:00
/// Auth Request cleanup schedule |> Cron schedule of the job that cleans old auth requests from the auth request.
/// Defaults to every minute. Set blank to disable this job.
auth_request_purge_schedule : String , false , def , " 30 * * * * * " . to_string ( ) ;
2021-04-03 05:16:49 +02:00
} ,
2019-02-06 17:32:13 +01:00
2019-02-05 22:17:02 +01:00
/// General settings
settings {
2019-03-03 16:09:15 +01:00
/// Domain URL |> This needs to be set to the URL used to access the server, including 'http[s]://'
2023-02-07 12:48:48 +01:00
/// and port, if it's different than the default. Some server functions don't work correctly without this value
2019-02-05 22:17:02 +01:00
domain : String , true , def , " http://localhost " . to_string ( ) ;
2019-02-18 19:25:33 +01:00
/// Domain Set |> Indicates if the domain is set by the admin. Otherwise the default will be used.
2019-02-05 22:17:02 +01:00
domain_set : bool , false , def , false ;
2020-02-19 06:27:00 +01:00
/// Domain origin |> Domain URL origin (in https://example.com:8443/path, https://example.com:8443 is the origin)
domain_origin : String , false , auto , | c | extract_url_origin ( & c . domain ) ;
/// Domain path |> Domain URL path (in https://example.com:8443/path, /path is the path)
domain_path : String , false , auto , | c | extract_url_path ( & c . domain ) ;
2019-02-05 22:17:02 +01:00
/// Enable web vault
web_vault_enabled : bool , false , def , true ;
2021-05-12 05:07:32 +02:00
/// Allow Sends |> Controls whether users are allowed to create Bitwarden Sends.
/// This setting applies globally to all users. To control this on a per-org basis instead, use the "Disable Send" org policy.
sends_allowed : bool , true , def , true ;
2019-08-20 20:07:12 +02:00
/// HIBP Api Key |> HaveIBeenPwned API Key, request it here: https://haveibeenpwned.com/API/Key
2019-08-20 23:53:00 +02:00
hibp_api_key : Pass , true , option ;
2019-08-20 20:07:12 +02:00
2021-07-13 15:17:03 +02:00
/// Per-user attachment storage limit (KB) |> Max kilobytes of attachment storage allowed per user. When this limit is reached, the user will not be allowed to upload further attachments.
2020-02-17 22:56:26 +01:00
user_attachment_limit : i64 , true , option ;
2021-07-13 15:17:03 +02:00
/// Per-organization attachment storage limit (KB) |> Max kilobytes of attachment storage allowed per org. When this limit is reached, org members will not be allowed to upload further attachments for ciphers owned by that org.
2020-02-17 22:56:26 +01:00
org_attachment_limit : i64 , true , option ;
2024-01-27 02:43:26 +01:00
/// Per-user send storage limit (KB) |> Max kilobytes of sends storage allowed per user. When this limit is reached, the user will not be allowed to upload further sends.
user_send_limit : i64 , true , option ;
2020-02-17 22:56:26 +01:00
2021-04-03 05:52:15 +02:00
/// Trash auto-delete days |> Number of days to wait before auto-deleting a trashed item.
/// If unset, trashed items are not auto-deleted. This setting applies globally, so make
/// sure to inform all users of any changes to this setting.
trash_auto_delete_days : i64 , true , option ;
2021-10-25 10:36:05 +02:00
/// Incomplete 2FA time limit |> Number of minutes to wait before a 2FA-enabled login is
/// considered incomplete, resulting in an email notification. An incomplete 2FA login is one
/// where the correct master password was provided but the required 2FA step was not completed,
/// which potentially indicates a master password compromise. Set to 0 to disable this check.
/// This setting applies globally to all users.
incomplete_2fa_time_limit : i64 , true , def , 3 ;
2021-12-20 10:34:31 +01:00
/// Disable icon downloads |> Set to true to disable icon downloading in the internal icon service.
/// This still serves existing icons from $ICON_CACHE_FOLDER, without generating any external
/// network requests. $ICON_CACHE_TTL must also be set to 0; otherwise, the existing icons
/// will be deleted eventually, but won't be downloaded again.
2019-02-05 22:17:02 +01:00
disable_icon_download : bool , true , def , false ;
2021-04-27 23:18:32 +02:00
/// Allow new signups |> Controls whether new users can register. Users can be invited by the vaultwarden admin even if this is disabled
2019-02-05 22:17:02 +01:00
signups_allowed : bool , true , def , true ;
2019-11-25 06:28:49 +01:00
/// Require email verification on signups. This will prevent logins from succeeding until the address has been verified
signups_verify : bool , true , def , false ;
/// If signups require email verification, automatically re-send verification email if it hasn't been sent for a while (in seconds)
signups_verify_resend_time : u64 , true , def , 3_600 ;
/// If signups require email verification, limit how many emails are automatically sent when login is attempted (0 means no limit)
signups_verify_resend_limit : u32 , true , def , 6 ;
2020-04-09 10:42:27 +02:00
/// Email domain whitelist |> Allow signups only from this list of comma-separated domains, even when signups are otherwise disabled
2022-11-04 12:56:02 +01:00
signups_domains_whitelist : String , true , def , String ::new ( ) ;
2022-11-20 19:15:45 +01:00
/// Enable event logging |> Enables event logging for organizations.
org_events_enabled : bool , false , def , false ;
2020-08-06 07:35:29 +02:00
/// Org creation users |> Allow org creation only by this list of comma-separated user emails.
/// Blank or 'all' means all users can create orgs; 'none' means no users can create orgs.
2022-11-04 12:56:02 +01:00
org_creation_users : String , true , def , String ::new ( ) ;
2020-04-09 10:42:27 +02:00
/// Allow invitations |> Controls whether users can be invited by organization admins, even when signups are otherwise disabled
2019-02-05 22:17:02 +01:00
invitations_allowed : bool , true , def , true ;
2022-10-08 18:31:34 +02:00
/// Invitation token expiration time (in hours) |> The number of hours after which an organization invite token, emergency access invite token,
/// email verification token and deletion request token will expire (must be at least 1)
invitation_expiration_hours : u32 , false , def , 120 ;
2024-01-10 19:02:36 +01:00
/// Enable emergency access |> Controls whether users can enable emergency access to their accounts. This setting applies globally to all users.
2021-03-24 20:15:55 +01:00
emergency_access_allowed : bool , true , def , true ;
2023-10-20 11:57:57 +02:00
/// Allow email change |> Controls whether users can change their email. This setting applies globally to all users.
email_change_allowed : bool , true , def , true ;
2023-01-24 13:06:31 +01:00
/// Password iterations |> Number of server-side passwords hashing iterations for the password hash.
/// The default for new users. If changed, it will be updated during login for existing users.
password_iterations : i32 , true , def , 600_000 ;
2022-07-01 05:46:17 +02:00
/// Allow password hints |> Controls whether users can set password hints. This setting applies globally to all users.
password_hints_allowed : bool , true , def , true ;
2021-07-10 10:20:37 +02:00
/// Show password hint |> Controls whether a password hint should be shown directly in the web page
/// if SMTP service is not configured. Not recommended for publicly-accessible instances as this
/// provides unauthenticated access to potentially sensitive data.
show_password_hint : bool , true , def , false ;
2019-02-05 22:17:02 +01:00
2023-04-04 17:02:24 +02:00
/// Admin token/Argon2 PHC |> The plain text token or Argon2 PHC string used to authenticate in this very same page. Changing it here will not deauthorize the current session!
2019-02-08 20:49:04 +01:00
admin_token : Pass , true , option ;
2020-02-04 22:14:50 +01:00
/// Invitation organization name |> Name shown in the invitation emails that don't come from a specific organization
2021-04-27 23:18:32 +02:00
invitation_org_name : String , true , def , " Vaultwarden " . to_string ( ) ;
2022-11-20 19:15:45 +01:00
2023-07-29 13:10:46 +02:00
/// Events days retain |> Number of days to retain events stored in the database. If unset, events are kept indefinitely.
2022-11-20 19:15:45 +01:00
events_days_retain : i64 , false , option ;
2019-02-05 22:17:02 +01:00
} ,
/// Advanced settings
2019-02-06 17:32:13 +01:00
advanced {
2019-12-27 18:42:39 +01:00
/// Client IP header |> If not present, the remote IP is used.
/// Set to the string "none" (without quotes), to disable any headers and just use the remote IP
ip_header : String , true , def , " X-Real-IP " . to_string ( ) ;
/// Internal IP header property, used to avoid recomputing each time
_ip_header_enabled : bool , false , gen , | c | & c . ip_header . trim ( ) . to_lowercase ( ) ! = " none " ;
2021-12-20 10:34:31 +01:00
/// Icon service |> The predefined icon services are: internal, bitwarden, duckduckgo, google.
/// To specify a custom icon service, set a URL template with exactly one instance of `{}`,
/// which is replaced with the domain. For example: `https://icon.example.com/domain/{}`.
/// `internal` refers to Vaultwarden's built-in icon fetching implementation. If an external
2021-12-30 03:01:32 +01:00
/// service is set, an icon request to Vaultwarden will return an HTTP redirect to the
2021-12-20 10:34:31 +01:00
/// corresponding icon at the external service.
icon_service : String , false , def , " internal " . to_string ( ) ;
2022-12-08 13:35:53 +01:00
/// _icon_service_url
2022-07-17 16:21:03 +02:00
_icon_service_url : String , false , gen , | c | generate_icon_service_url ( & c . icon_service ) ;
2022-12-08 13:35:53 +01:00
/// _icon_service_csp
2022-07-17 16:21:03 +02:00
_icon_service_csp : String , false , gen , | c | generate_icon_service_csp ( & c . icon_service , & c . _icon_service_url ) ;
2021-12-30 03:01:32 +01:00
/// Icon redirect code |> The HTTP status code to use for redirects to an external icon service.
2022-01-09 08:40:35 +01:00
/// The supported codes are 301 (legacy permanent), 302 (legacy temporary), 307 (temporary), and 308 (permanent).
2021-12-30 03:01:32 +01:00
/// Temporary redirects are useful while testing different icon services, but once a service
2022-01-09 08:40:35 +01:00
/// has been decided on, consider using permanent redirects for cacheability. The legacy codes
/// are currently better supported by the Bitwarden clients.
icon_redirect_code : u32 , true , def , 302 ;
2023-07-29 13:10:46 +02:00
/// Positive icon cache expiry |> Number of seconds to consider that an already cached icon is fresh. After this period, the icon will be refreshed
2019-02-05 22:17:02 +01:00
icon_cache_ttl : u64 , true , def , 2_592_000 ;
/// Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again.
icon_cache_negttl : u64 , true , def , 259_200 ;
2019-02-12 21:56:28 +01:00
/// Icon download timeout |> Number of seconds when to stop attempting to download an icon.
2019-03-03 16:09:15 +01:00
icon_download_timeout : u64 , true , def , 10 ;
2024-07-12 22:33:11 +02:00
/// [Deprecated] Icon blacklist Regex |> Use `http_request_block_regex` instead
icon_blacklist_regex : String , false , option ;
/// [Deprecated] Icon blacklist non global IPs |> Use `http_request_block_non_global_ips` instead
icon_blacklist_non_global_ips : bool , false , def , true ;
/// Block HTTP domains/IPs by Regex |> Any domains or IPs that match this regex won't be fetched by the internal HTTP client.
2019-03-18 22:12:39 +01:00
/// Useful to hide other servers in the local network. Check the WIKI for more details
2024-07-12 22:33:11 +02:00
http_request_block_regex : String , true , option ;
/// Block non global IPs |> Enabling this will cause the internal HTTP client to refuse to connect to any non global IP address.
2023-07-29 13:10:46 +02:00
/// Useful to secure your internal environment: See https://en.wikipedia.org/wiki/Reserved_IP_addresses for a list of IPs which it will block
2024-07-12 22:33:11 +02:00
http_request_block_non_global_ips : bool , true , auto , | c | c . icon_blacklist_non_global_ips ;
2019-02-05 22:17:02 +01:00
2019-03-03 16:09:15 +01:00
/// Disable Two-Factor remember |> Enabling this would force the users to use a second factor to login every time.
/// Note that the checkbox would still be present, but ignored.
disable_2fa_remember : bool , true , def , false ;
2019-02-05 22:17:02 +01:00
2019-11-07 17:11:29 +01:00
/// Disable authenticator time drifted codes to be valid |> Enabling this only allows the current TOTP code to be valid
/// TOTP codes of the previous and next 30 seconds will be invalid.
2020-02-04 22:14:50 +01:00
authenticator_disable_time_drift : bool , true , def , false ;
2019-11-07 17:11:29 +01:00
2024-01-01 15:44:02 +01:00
/// Customize the enabled feature flags on the clients |> This is a comma separated list of feature flags to enable.
experimental_client_feature_flags : String , false , def , " fido2-vault-credentials " . to_string ( ) ;
2019-08-19 22:14:00 +02:00
/// Require new device emails |> When a user logs in an email is required to be sent.
/// If sending the email fails the login attempt will fail.
require_device_email : bool , true , def , false ;
2019-03-03 16:09:15 +01:00
/// Reload templates (Dev) |> When this is set to true, the templates get reloaded with every request.
/// ONLY use this during development, as it can slow down the server
2019-02-05 22:17:02 +01:00
reload_templates : bool , true , def , false ;
/// Enable extended logging
extended_logging : bool , false , def , true ;
2020-07-23 06:50:49 +02:00
/// Log timestamp format
2020-07-23 23:19:51 +02:00
log_timestamp_format : String , true , def , " %Y-%m-%d %H:%M:%S.%3f " . to_string ( ) ;
2019-03-29 20:27:20 +01:00
/// Enable the log to output to Syslog
use_syslog : bool , false , def , false ;
2019-02-05 22:17:02 +01:00
/// Log file path
log_file : String , false , option ;
2019-03-25 14:12:41 +01:00
/// Log level
log_level : String , false , def , " Info " . to_string ( ) ;
2019-02-18 11:48:48 +01:00
2021-04-27 23:18:32 +02:00
/// Enable DB WAL |> Turning this off might lead to worse performance, but might help if using vaultwarden on some exotic filesystems,
2019-03-03 16:09:15 +01:00
/// that do not support WAL. Please make sure you read project wiki on the topic before changing this setting.
2019-02-18 11:48:48 +01:00
enable_db_wal : bool , false , def , true ;
2019-02-20 21:44:35 +01:00
2020-10-03 22:31:52 +02:00
/// Max database connection retries |> Number of times to retry the database connection during startup, with 1 second between each retry, set to 0 to retry indefinitely
db_connection_retries : u32 , false , def , 15 ;
2023-07-29 13:10:46 +02:00
/// Timeout when acquiring database connection
2022-04-27 02:50:20 +02:00
database_timeout : u64 , false , def , 30 ;
2021-11-07 18:53:39 +01:00
2021-10-24 22:22:28 +02:00
/// Database connection pool size
database_max_conns : u32 , false , def , 10 ;
2022-04-29 09:26:49 +02:00
/// Database connection init |> SQL statements to run when creating a new database connection, mainly useful for connection-scoped pragmas. If empty, a database-specific default is used.
2022-11-04 12:56:02 +01:00
database_conn_init : String , false , def , String ::new ( ) ;
2022-04-27 02:50:20 +02:00
2019-10-08 19:33:27 +02:00
/// Bypass admin page security (Know the risks!) |> Disables the Admin Token for the admin page so you may use your own auth in-front
2022-11-14 17:14:50 +01:00
disable_admin_token : bool , false , def , false ;
2020-02-04 22:14:50 +01:00
/// Allowed iframe ancestors (Know the risks!) |> Allows other domains to embed the web vault into an iframe, useful for embedding into secure intranets
allowed_iframe_ancestors : String , true , def , String ::new ( ) ;
2021-12-22 21:48:49 +01:00
2021-12-25 01:10:21 +01:00
/// Seconds between login requests |> Number of seconds, on average, between login and 2FA requests from the same IP address before rate limiting kicks in
2021-12-22 21:48:49 +01:00
login_ratelimit_seconds : u64 , false , def , 60 ;
2021-12-25 01:10:21 +01:00
/// Max burst size for login requests |> Allow a burst of requests of up to this size, while maintaining the average indicated by `login_ratelimit_seconds`. Note that this applies to both the login and the 2FA, so it's recommended to allow a burst size of at least 2
2021-12-22 21:48:49 +01:00
login_ratelimit_max_burst : u32 , false , def , 10 ;
2022-11-22 15:12:06 +01:00
/// Seconds between admin login requests |> Number of seconds, on average, between admin requests from the same IP address before rate limiting kicks in
2021-12-22 21:48:49 +01:00
admin_ratelimit_seconds : u64 , false , def , 300 ;
2022-11-22 15:12:06 +01:00
/// Max burst size for admin login requests |> Allow a burst of requests of up to this size, while maintaining the average indicated by `admin_ratelimit_seconds`
2021-12-22 21:48:49 +01:00
admin_ratelimit_max_burst : u32 , false , def , 3 ;
2022-12-15 17:15:48 +01:00
2023-02-20 17:02:14 +01:00
/// Admin session lifetime |> Set the lifetime of admin sessions to this value (in minutes).
admin_session_lifetime : i64 , true , def , 20 ;
2023-02-20 16:10:30 +01:00
2022-12-15 17:15:48 +01:00
/// Enable groups (BETA!) (Know the risks!) |> Enables groups support for organizations (Currently contains known issues!).
org_groups_enabled : bool , false , def , false ;
2019-02-05 22:17:02 +01:00
} ,
/// Yubikey settings
2019-02-06 00:38:57 +01:00
yubico : _enable_yubico {
/// Enabled
_enable_yubico : bool , true , def , true ;
2019-02-05 22:17:02 +01:00
/// Client ID
yubico_client_id : String , true , option ;
/// Secret Key
2019-02-08 20:49:04 +01:00
yubico_secret_key : Pass , true , option ;
2019-02-05 22:17:02 +01:00
/// Server
yubico_server : String , true , option ;
} ,
2019-04-11 18:40:03 +02:00
/// Global Duo settings (Note that users can override them)
2019-04-07 18:58:15 +02:00
duo : _enable_duo {
/// Enabled
2023-04-04 17:02:24 +02:00
_enable_duo : bool , true , def , true ;
2019-04-07 18:58:15 +02:00
/// Integration Key
duo_ikey : String , true , option ;
/// Secret Key
duo_skey : Pass , true , option ;
/// Host
duo_host : String , true , option ;
2019-04-11 16:08:26 +02:00
/// Application Key (generated automatically)
_duo_akey : Pass , false , option ;
2019-04-07 18:58:15 +02:00
} ,
2019-02-05 22:17:02 +01:00
/// SMTP Email Settings
2019-02-06 00:38:57 +01:00
smtp : _enable_smtp {
/// Enabled
2020-11-18 12:07:08 +01:00
_enable_smtp : bool , true , def , true ;
2023-02-12 18:53:55 +01:00
/// Use Sendmail |> Whether to send mail via the `sendmail` command
use_sendmail : bool , true , def , false ;
/// Sendmail Command |> Which sendmail command to use. The one found in the $PATH is used if not specified.
sendmail_command : String , true , option ;
2019-02-05 22:17:02 +01:00
/// Host
2020-11-18 12:07:08 +01:00
smtp_host : String , true , option ;
2022-02-22 20:48:00 +01:00
/// DEPRECATED smtp_ssl |> DEPRECATED - Please use SMTP_SECURITY
smtp_ssl : bool , false , option ;
/// DEPRECATED smtp_explicit_tls |> DEPRECATED - Please use SMTP_SECURITY
smtp_explicit_tls : bool , false , option ;
/// Secure SMTP |> ("starttls", "force_tls", "off") Enable a secure connection. Default is "starttls" (Explicit - ports 587 or 25), "force_tls" (Implicit - port 465) or "off", no encryption
smtp_security : String , true , auto , | c | smtp_convert_deprecated_ssl_options ( c . smtp_ssl , c . smtp_explicit_tls ) ; // TODO: After deprecation make it `def, "starttls".to_string()`
2019-02-05 22:17:02 +01:00
/// Port
2022-02-22 20:48:00 +01:00
smtp_port : u16 , true , auto , | c | if c . smtp_security = = * " force_tls " { 465 } else if c . smtp_security = = * " starttls " { 587 } else { 25 } ;
2019-02-05 22:17:02 +01:00
/// From Address
2020-11-18 12:07:08 +01:00
smtp_from : String , true , def , String ::new ( ) ;
2019-02-05 22:17:02 +01:00
/// From Name
2021-04-27 23:18:32 +02:00
smtp_from_name : String , true , def , " Vaultwarden " . to_string ( ) ;
2019-02-05 22:17:02 +01:00
/// Username
2020-11-18 12:07:08 +01:00
smtp_username : String , true , option ;
2019-02-05 22:17:02 +01:00
/// Password
2020-11-18 12:07:08 +01:00
smtp_password : Pass , true , option ;
2020-09-19 17:09:58 +02:00
/// SMTP Auth mechanism |> Defaults for SSL is "Plain" and "Login" and nothing for Non-SSL connections. Possible values: ["Plain", "Login", "Xoauth2"]. Multiple options need to be separated by a comma ','.
2020-11-18 12:07:08 +01:00
smtp_auth_mechanism : String , true , option ;
2019-11-06 21:39:33 +01:00
/// SMTP connection timeout |> Number of seconds when to stop trying to connect to the SMTP server
2020-11-18 12:07:08 +01:00
smtp_timeout : u64 , true , def , 15 ;
2020-07-05 01:59:15 +02:00
/// Server name sent during HELO |> By default this value should be is on the machine's hostname, but might need to be changed in case it trips some anti-spam filters
2020-11-18 12:07:08 +01:00
helo_name : String , true , option ;
2022-09-30 19:14:26 +02:00
/// Embed images as email attachments.
smtp_embed_images : bool , true , def , true ;
2022-12-08 13:35:53 +01:00
/// _smtp_img_src
2022-09-30 19:14:26 +02:00
_smtp_img_src : String , false , gen , | c | generate_smtp_img_src ( c . smtp_embed_images , & c . domain ) ;
2020-11-18 12:07:08 +01:00
/// Enable SMTP debugging (Know the risks!) |> DANGEROUS: Enabling this will output very detailed SMTP messages. This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting!
2021-06-19 19:22:19 +02:00
smtp_debug : bool , false , def , false ;
2020-11-18 12:07:08 +01:00
/// Accept Invalid Certs (Know the risks!) |> DANGEROUS: Allow invalid certificates. This option introduces significant vulnerabilities to man-in-the-middle attacks!
smtp_accept_invalid_certs : bool , true , def , false ;
/// Accept Invalid Hostnames (Know the risks!) |> DANGEROUS: Allow invalid hostnames. This option introduces significant vulnerabilities to man-in-the-middle attacks!
smtp_accept_invalid_hostnames : bool , true , def , false ;
2019-02-05 22:17:02 +01:00
} ,
2019-10-16 07:10:27 +02:00
/// Email 2FA Settings
email_2fa : _enable_email_2fa {
/// Enabled |> Disabling will prevent users from setting up new email 2FA and using existing email 2FA configured
2023-02-12 18:53:55 +01:00
_enable_email_2fa : bool , true , auto , | c | c . _enable_smtp & & ( c . smtp_host . is_some ( ) | | c . use_sendmail ) ;
2022-01-24 10:17:00 +01:00
/// Email token size |> Number of digits in an email 2FA token (min: 6, max: 255). Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting.
email_token_size : u8 , true , def , 6 ;
2019-10-16 07:10:27 +02:00
/// Token expiration time |> Maximum time in seconds a token is valid. The time the user has to open email client and copy token.
email_expiration_time : u64 , true , def , 600 ;
/// Maximum attempts |> Maximum attempts before an email token is reset and a new email will need to be sent
email_attempts_limit : u64 , true , def , 3 ;
2024-03-17 22:35:02 +01:00
/// Automatically enforce at login |> Setup email 2FA provider regardless of any organization policy
email_2fa_enforce_on_verified_invite : bool , true , def , false ;
/// Auto-enable 2FA (Know the risks!) |> Automatically setup email 2FA as fallback provider when needed
email_2fa_auto_fallback : bool , true , def , false ;
2019-10-16 07:10:27 +02:00
} ,
2019-02-02 01:09:21 +01:00
}
2019-01-25 18:23:51 +01:00
2019-02-02 16:47:27 +01:00
fn validate_config ( cfg : & ConfigItems ) -> Result < ( ) , Error > {
2020-08-18 17:15:44 +02:00
// Validate connection URL is valid and DB feature is enabled
2022-11-22 04:40:20 +01:00
let url = & cfg . database_url ;
2022-11-22 05:46:51 +01:00
if DbConnType ::from_url ( url ) ? = = DbConnType ::sqlite & & url . contains ( '/' ) {
2022-11-22 04:40:20 +01:00
let path = std ::path ::Path ::new ( & url ) ;
if let Some ( parent ) = path . parent ( ) {
2022-11-22 05:46:51 +01:00
if ! parent . is_dir ( ) {
err! ( format! ( " SQLite database directory ` {} ` does not exist or is not a directory " , parent . display ( ) ) ) ;
2022-11-22 04:40:20 +01:00
}
}
}
2020-03-09 22:04:03 +01:00
2023-01-24 13:06:31 +01:00
if cfg . password_iterations < 100_000 {
err! ( " PASSWORD_ITERATIONS should be at least 100000 or higher. The default is 600000! " ) ;
}
2020-10-06 15:23:55 +02:00
let limit = 256 ;
if cfg . database_max_conns < 1 | | cfg . database_max_conns > limit {
2022-12-29 14:11:52 +01:00
err! ( format! ( " `DATABASE_MAX_CONNS` contains an invalid value. Ensure it is between 1 and {limit} . " , ) ) ;
2020-10-06 15:23:55 +02:00
}
2022-12-29 01:30:25 +01:00
if let Some ( log_file ) = & cfg . log_file {
if std ::fs ::OpenOptions ::new ( ) . append ( true ) . create ( true ) . open ( log_file ) . is_err ( ) {
err! ( " Unable to write to log file " , log_file ) ;
}
}
2020-03-09 22:04:03 +01:00
let dom = cfg . domain . to_lowercase ( ) ;
2020-02-23 14:55:27 +01:00
if ! dom . starts_with ( " http:// " ) & & ! dom . starts_with ( " https:// " ) {
2021-01-19 17:55:21 +01:00
err! (
" DOMAIN variable needs to contain the protocol (http, https). Use 'http[s]://bw.example.com' instead of 'bw.example.com' "
) ;
2020-02-23 14:55:27 +01:00
}
2019-10-08 19:34:47 +02:00
2020-04-09 10:42:27 +02:00
let whitelist = & cfg . signups_domains_whitelist ;
if ! whitelist . is_empty ( ) & & whitelist . split ( ',' ) . any ( | d | d . trim ( ) . is_empty ( ) ) {
err! ( " `SIGNUPS_DOMAINS_WHITELIST` contains empty tokens " ) ;
}
2020-08-06 07:35:29 +02:00
let org_creation_users = cfg . org_creation_users . trim ( ) . to_lowercase ( ) ;
2021-01-19 17:55:21 +01:00
if ! ( org_creation_users . is_empty ( ) | | org_creation_users = = " all " | | org_creation_users = = " none " )
& & org_creation_users . split ( ',' ) . any ( | u | ! u . contains ( '@' ) )
{
err! ( " `ORG_CREATION_USERS` contains invalid email addresses " ) ;
2020-08-06 07:35:29 +02:00
}
2019-03-07 20:21:50 +01:00
if let Some ( ref token ) = cfg . admin_token {
2020-01-30 22:10:50 +01:00
if token . trim ( ) . is_empty ( ) & & ! cfg . disable_admin_token {
2020-04-10 05:55:08 +02:00
println! ( " [WARNING] `ADMIN_TOKEN` is enabled but has an empty value, so the admin page will be disabled. " ) ;
println! ( " [WARNING] To enable the admin page without a token, use `DISABLE_ADMIN_TOKEN`. " ) ;
2019-03-07 20:21:50 +01:00
}
}
2023-06-11 13:28:18 +02:00
if cfg . push_enabled & & ( cfg . push_installation_id = = String ::new ( ) | | cfg . push_installation_key = = String ::new ( ) ) {
err! (
" Misconfigured Push Notification service \n \
########################################################################################\ n \
# It looks like you enabled Push Notification feature , but didn ' t configure it #\ n \
# properly . Make sure the installation id and key from https ://bitwarden.com/host are #\n\
# added to your configuration . #\ n \
########################################################################################\ n "
)
}
2024-01-01 16:01:57 +01:00
if cfg . push_enabled {
let push_relay_uri = cfg . push_relay_uri . to_lowercase ( ) ;
if ! push_relay_uri . starts_with ( " https:// " ) {
err! ( " `PUSH_RELAY_URI` must start with 'https://'. " )
}
if Url ::parse ( & push_relay_uri ) . is_err ( ) {
err! ( " Invalid URL format for `PUSH_RELAY_URI`. " ) ;
}
let push_identity_uri = cfg . push_identity_uri . to_lowercase ( ) ;
if ! push_identity_uri . starts_with ( " https:// " ) {
err! ( " `PUSH_IDENTITY_URI` must start with 'https://'. " )
}
if Url ::parse ( & push_identity_uri ) . is_err ( ) {
err! ( " Invalid URL format for `PUSH_IDENTITY_URI`. " ) ;
}
}
2024-01-28 23:36:27 +01:00
// TODO: deal with deprecated flags so they can be removed from this list, cf. #4263
2024-01-01 15:44:02 +01:00
const KNOWN_FLAGS : & [ & str ] =
& [ " autofill-overlay " , " autofill-v2 " , " browser-fileless-import " , " fido2-vault-credentials " ] ;
2024-01-28 23:36:27 +01:00
let configured_flags = parse_experimental_client_feature_flags ( & cfg . experimental_client_feature_flags ) ;
let invalid_flags : Vec < _ > = configured_flags . keys ( ) . filter ( | flag | ! KNOWN_FLAGS . contains ( & flag . as_str ( ) ) ) . collect ( ) ;
if ! invalid_flags . is_empty ( ) {
err! ( format! ( " Unrecognized experimental client feature flags: {invalid_flags:?} . \n \n \
Please ensure all feature flags are spelled correctly and that they are supported in this version . \ n \
Supported flags : { KNOWN_FLAGS :? } " ));
2024-01-01 15:44:02 +01:00
}
2024-01-27 02:43:26 +01:00
const MAX_FILESIZE_KB : i64 = i64 ::MAX > > 10 ;
if let Some ( limit ) = cfg . user_attachment_limit {
if ! ( 0 i64 ..= MAX_FILESIZE_KB ) . contains ( & limit ) {
err! ( " `USER_ATTACHMENT_LIMIT` is out of bounds " ) ;
}
}
if let Some ( limit ) = cfg . org_attachment_limit {
if ! ( 0 i64 ..= MAX_FILESIZE_KB ) . contains ( & limit ) {
err! ( " `ORG_ATTACHMENT_LIMIT` is out of bounds " ) ;
}
}
if let Some ( limit ) = cfg . user_send_limit {
if ! ( 0 i64 ..= MAX_FILESIZE_KB ) . contains ( & limit ) {
err! ( " `USER_SEND_LIMIT` is out of bounds " ) ;
}
}
2019-08-31 17:47:52 +02:00
if cfg . _enable_duo
& & ( cfg . duo_host . is_some ( ) | | cfg . duo_ikey . is_some ( ) | | cfg . duo_skey . is_some ( ) )
2019-04-11 16:08:26 +02:00
& & ! ( cfg . duo_host . is_some ( ) & & cfg . duo_ikey . is_some ( ) & & cfg . duo_skey . is_some ( ) )
2019-04-07 18:58:15 +02:00
{
2019-04-11 16:08:26 +02:00
err! ( " All Duo options need to be set for global Duo support " )
2019-04-07 18:58:15 +02:00
}
2022-12-28 21:05:21 +01:00
if cfg . _enable_yubico {
if cfg . yubico_client_id . is_some ( ) ! = cfg . yubico_secret_key . is_some ( ) {
err! ( " Both `YUBICO_CLIENT_ID` and `YUBICO_SECRET_KEY` must be set for Yubikey OTP support " )
}
if let Some ( yubico_server ) = & cfg . yubico_server {
let yubico_server = yubico_server . to_lowercase ( ) ;
if ! yubico_server . starts_with ( " https:// " ) {
err! ( " `YUBICO_SERVER` must be a valid URL and start with 'https://'. Either unset this variable or provide a valid URL. " )
}
}
2019-02-02 16:47:27 +01:00
}
2019-08-31 17:47:52 +02:00
if cfg . _enable_smtp {
2022-02-22 20:48:00 +01:00
match cfg . smtp_security . as_str ( ) {
" off " | " starttls " | " force_tls " = > ( ) ,
_ = > err! (
" `SMTP_SECURITY` is invalid. It needs to be one of the following options: starttls, force_tls or off "
) ,
}
2023-02-12 18:53:55 +01:00
if cfg . use_sendmail {
2023-02-02 22:05:44 +01:00
let command = cfg . sendmail_command . clone ( ) . unwrap_or_else ( | | format! ( " sendmail {EXE_SUFFIX} " ) ) ;
2019-02-02 16:47:27 +01:00
2023-02-02 22:05:44 +01:00
let mut path = std ::path ::PathBuf ::from ( & command ) ;
2023-01-25 22:54:50 +01:00
if ! path . is_absolute ( ) {
2023-02-02 22:05:44 +01:00
match which ::which ( & command ) {
2023-01-25 22:54:50 +01:00
Ok ( result ) = > path = result ,
Err ( _ ) = > err! ( format! ( " sendmail command {command:?} not found in $PATH " ) ) ,
2023-02-12 18:53:55 +01:00
}
2023-01-25 22:54:50 +01:00
}
2023-02-12 18:53:55 +01:00
2023-01-25 22:54:50 +01:00
match path . metadata ( ) {
Err ( err ) if err . kind ( ) = = std ::io ::ErrorKind ::NotFound = > {
err! ( format! ( " sendmail command not found at ` {path:?} ` " ) )
}
Err ( err ) = > {
err! ( format! ( " failed to access sendmail command at ` {path:?} `: {err} " ) )
}
Ok ( metadata ) = > {
if metadata . is_dir ( ) {
err! ( format! ( " sendmail command at ` {path:?} ` isn't a directory " ) ) ;
2023-02-12 18:53:55 +01:00
}
2023-01-25 22:54:50 +01:00
#[ cfg(unix) ]
{
use std ::os ::unix ::fs ::PermissionsExt ;
if ! metadata . permissions ( ) . mode ( ) & 0o111 ! = 0 {
err! ( format! ( " sendmail command at ` {path:?} ` isn't executable " ) ) ;
2023-02-12 18:53:55 +01:00
}
}
}
}
} else {
if cfg . smtp_host . is_some ( ) = = cfg . smtp_from . is_empty ( ) {
err! ( " Both `SMTP_HOST` and `SMTP_FROM` need to be set for email support without `USE_SENDMAIL` " )
}
2021-01-31 20:07:42 +01:00
2023-02-12 18:53:55 +01:00
if cfg . smtp_username . is_some ( ) ! = cfg . smtp_password . is_some ( ) {
err! ( " Both `SMTP_USERNAME` and `SMTP_PASSWORD` need to be set to enable email authentication without `USE_SENDMAIL` " )
}
2019-08-31 17:47:52 +02:00
}
2019-02-02 16:47:27 +01:00
2023-02-12 18:53:55 +01:00
if ( cfg . smtp_host . is_some ( ) | | cfg . use_sendmail ) & & ! cfg . smtp_from . contains ( '@' ) {
err! ( " SMTP_FROM does not contain a mandatory @ sign " )
2019-08-31 17:47:52 +02:00
}
if cfg . _enable_email_2fa & & cfg . email_token_size < 6 {
err! ( " `EMAIL_TOKEN_SIZE` has a minimum size of 6 " )
}
2019-08-26 20:26:54 +02:00
}
2023-02-12 18:53:55 +01:00
if cfg . _enable_email_2fa & & ! ( cfg . smtp_host . is_some ( ) | | cfg . use_sendmail ) {
err! ( " To enable email 2FA, a mail transport must be configured " )
}
2024-03-17 22:35:02 +01:00
if ! cfg . _enable_email_2fa & & cfg . email_2fa_enforce_on_verified_invite {
err! ( " To enforce email 2FA on verified invitations, email 2fa has to be enabled! " ) ;
}
if ! cfg . _enable_email_2fa & & cfg . email_2fa_auto_fallback {
err! ( " To use email 2FA as automatic fallback, email 2fa has to be enabled! " ) ;
}
2024-07-12 22:33:11 +02:00
// Check if the HTTP request block regex is valid
if let Some ( ref r ) = cfg . http_request_block_regex {
2021-11-05 19:18:54 +01:00
let validate_regex = regex ::Regex ::new ( r ) ;
2020-12-08 17:33:15 +01:00
match validate_regex {
Ok ( _ ) = > ( ) ,
2024-07-12 22:33:11 +02:00
Err ( e ) = > err! ( format! ( " `HTTP_REQUEST_BLOCK_REGEX` is invalid: {e:#?} " ) ) ,
2020-12-08 17:33:15 +01:00
}
}
2021-12-20 10:34:31 +01:00
// Check if the icon service is valid
let icon_service = cfg . icon_service . as_str ( ) ;
match icon_service {
" internal " | " bitwarden " | " duckduckgo " | " google " = > ( ) ,
_ = > {
if ! icon_service . starts_with ( " http " ) {
2022-12-29 14:11:52 +01:00
err! ( format! ( " Icon service URL ` {icon_service} ` must start with \" http \" " ) )
2021-12-20 10:34:31 +01:00
}
match icon_service . matches ( " {} " ) . count ( ) {
1 = > ( ) , // nominal
2022-12-29 14:11:52 +01:00
0 = > err! ( format! ( " Icon service URL ` {icon_service} ` has no placeholder \" {{ }} \" " ) ) ,
_ = > err! ( format! ( " Icon service URL ` {icon_service} ` has more than one placeholder \" {{ }} \" " ) ) ,
2021-12-20 10:34:31 +01:00
}
}
}
2021-12-30 03:01:32 +01:00
// Check if the icon redirect code is valid
match cfg . icon_redirect_code {
2022-01-09 08:40:35 +01:00
301 | 302 | 307 | 308 = > ( ) ,
_ = > err! ( " Only HTTP 301/302 and 307/308 redirects are supported " ) ,
2021-12-30 03:01:32 +01:00
}
2022-10-08 18:31:34 +02:00
if cfg . invitation_expiration_hours < 1 {
2022-10-09 05:50:43 +02:00
err! ( " `INVITATION_EXPIRATION_HOURS` has a minimum duration of 1 hour " )
2022-10-08 18:31:34 +02:00
}
2022-11-20 19:15:45 +01:00
// Validate schedule crontab format
2022-10-26 13:02:05 +02:00
if ! cfg . send_purge_schedule . is_empty ( ) & & cfg . send_purge_schedule . parse ::< Schedule > ( ) . is_err ( ) {
err! ( " `SEND_PURGE_SCHEDULE` is not a valid cron expression " )
}
2022-11-20 19:15:45 +01:00
2022-10-26 13:02:05 +02:00
if ! cfg . trash_purge_schedule . is_empty ( ) & & cfg . trash_purge_schedule . parse ::< Schedule > ( ) . is_err ( ) {
err! ( " `TRASH_PURGE_SCHEDULE` is not a valid cron expression " )
}
2022-11-20 19:15:45 +01:00
2022-10-26 13:02:05 +02:00
if ! cfg . incomplete_2fa_schedule . is_empty ( ) & & cfg . incomplete_2fa_schedule . parse ::< Schedule > ( ) . is_err ( ) {
err! ( " `INCOMPLETE_2FA_SCHEDULE` is not a valid cron expression " )
}
2022-11-20 19:15:45 +01:00
2022-10-26 13:02:05 +02:00
if ! cfg . emergency_notification_reminder_schedule . is_empty ( )
& & cfg . emergency_notification_reminder_schedule . parse ::< Schedule > ( ) . is_err ( )
{
err! ( " `EMERGENCY_NOTIFICATION_REMINDER_SCHEDULE` is not a valid cron expression " )
}
2022-11-20 19:15:45 +01:00
2022-10-26 13:02:05 +02:00
if ! cfg . emergency_request_timeout_schedule . is_empty ( )
& & cfg . emergency_request_timeout_schedule . parse ::< Schedule > ( ) . is_err ( )
{
err! ( " `EMERGENCY_REQUEST_TIMEOUT_SCHEDULE` is not a valid cron expression " )
}
2022-11-20 19:15:45 +01:00
if ! cfg . event_cleanup_schedule . is_empty ( ) & & cfg . event_cleanup_schedule . parse ::< Schedule > ( ) . is_err ( ) {
err! ( " `EVENT_CLEANUP_SCHEDULE` is not a valid cron expression " )
}
2023-08-04 21:12:23 +02:00
if ! cfg . auth_request_purge_schedule . is_empty ( ) & & cfg . auth_request_purge_schedule . parse ::< Schedule > ( ) . is_err ( ) {
err! ( " `AUTH_REQUEST_PURGE_SCHEDULE` is not a valid cron expression " )
}
2023-02-28 23:09:51 +01:00
if ! cfg . disable_admin_token {
match cfg . admin_token . as_ref ( ) {
Some ( t ) if t . starts_with ( " $argon2 " ) = > {
if let Err ( e ) = argon2 ::password_hash ::PasswordHash ::new ( t ) {
err! ( format! ( " The configured Argon2 PHC in `ADMIN_TOKEN` is invalid: ' {e} ' " ) )
}
}
Some ( _ ) = > {
println! (
" [NOTICE] You are using a plain text `ADMIN_TOKEN` which is insecure. \n \
Please generate a secure Argon2 PHC string by using ` vaultwarden hash ` or ` argon2 ` . \ n \
See : https ://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token\n"
) ;
}
_ = > { }
}
}
2019-02-02 16:47:27 +01:00
Ok ( ( ) )
}
2020-02-19 06:27:00 +01:00
/// Extracts an RFC 6454 web origin from a URL.
fn extract_url_origin ( url : & str ) -> String {
2020-02-23 14:55:27 +01:00
match Url ::parse ( url ) {
Ok ( u ) = > u . origin ( ) . ascii_serialization ( ) ,
Err ( e ) = > {
2022-12-29 14:11:52 +01:00
println! ( " Error validating domain: {e} " ) ;
2020-02-23 14:55:27 +01:00
String ::new ( )
}
}
2020-02-19 06:27:00 +01:00
}
/// Extracts the path from a URL.
/// All trailing '/' chars are trimmed, even if the path is a lone '/'.
fn extract_url_path ( url : & str ) -> String {
2020-02-23 14:55:27 +01:00
match Url ::parse ( url ) {
Ok ( u ) = > u . path ( ) . trim_end_matches ( '/' ) . to_string ( ) ,
Err ( _ ) = > {
// We already print it in the method above, no need to do it again
String ::new ( )
}
}
2020-02-19 06:27:00 +01:00
}
2022-09-30 19:14:26 +02:00
fn generate_smtp_img_src ( embed_images : bool , domain : & str ) -> String {
if embed_images {
" cid: " . to_string ( )
} else {
2022-10-06 11:59:47 +02:00
format! ( " {domain} /vw_static/ " )
2022-09-30 19:14:26 +02:00
}
}
2022-07-17 16:21:03 +02:00
/// Generate the correct URL for the icon service.
/// This will be used within icons.rs to call the external icon service.
fn generate_icon_service_url ( icon_service : & str ) -> String {
match icon_service {
2022-11-04 12:56:02 +01:00
" internal " = > String ::new ( ) ,
2022-07-17 16:21:03 +02:00
" bitwarden " = > " https://icons.bitwarden.net/{}/icon.png " . to_string ( ) ,
" duckduckgo " = > " https://icons.duckduckgo.com/ip3/{}.ico " . to_string ( ) ,
" google " = > " https://www.google.com/s2/favicons?domain={}&sz=32 " . to_string ( ) ,
_ = > icon_service . to_string ( ) ,
}
}
/// Generate the CSP string needed to allow redirected icon fetching
fn generate_icon_service_csp ( icon_service : & str , icon_service_url : & str ) -> String {
// We split on the first '{', since that is the variable delimiter for an icon service URL.
// Everything up until the first '{' should be fixed and can be used as an CSP string.
let csp_string = match icon_service_url . split_once ( '{' ) {
Some ( ( c , _ ) ) = > c . to_string ( ) ,
2022-11-04 12:56:02 +01:00
None = > String ::new ( ) ,
2022-07-17 16:21:03 +02:00
} ;
// Because Google does a second redirect to there gstatic.com domain, we need to add an extra csp string.
match icon_service {
" google " = > csp_string + " https://*.gstatic.com/favicon " ,
_ = > csp_string ,
}
}
2022-02-22 20:48:00 +01:00
/// Convert the old SMTP_SSL and SMTP_EXPLICIT_TLS options
fn smtp_convert_deprecated_ssl_options ( smtp_ssl : Option < bool > , smtp_explicit_tls : Option < bool > ) -> String {
if smtp_explicit_tls . is_some ( ) | | smtp_ssl . is_some ( ) {
println! ( " [DEPRECATED]: `SMTP_SSL` or `SMTP_EXPLICIT_TLS` is set. Please use `SMTP_SECURITY` instead. " ) ;
}
if smtp_explicit_tls . is_some ( ) & & smtp_explicit_tls . unwrap ( ) {
return " force_tls " . to_string ( ) ;
} else if smtp_ssl . is_some ( ) & & ! smtp_ssl . unwrap ( ) {
return " off " . to_string ( ) ;
}
// Return the default `starttls` in all other cases
" starttls " . to_string ( )
}
2019-02-02 01:09:21 +01:00
impl Config {
2019-02-06 00:38:57 +01:00
pub fn load ( ) -> Result < Self , Error > {
// Loading from env and file
let _env = ConfigBuilder ::from_env ( ) ;
let _usr = ConfigBuilder ::from_file ( & CONFIG_FILE ) . unwrap_or_default ( ) ;
// Create merged config, config file overwrites env
2021-06-19 19:22:19 +02:00
let mut _overrides = Vec ::new ( ) ;
let builder = _env . merge ( & _usr , true , & mut _overrides ) ;
2019-02-06 00:38:57 +01:00
// Fill any missing with defaults
let config = builder . build ( ) ;
validate_config ( & config ) ? ;
Ok ( Config {
2021-01-19 17:55:21 +01:00
inner : RwLock ::new ( Inner {
2021-11-07 18:53:39 +01:00
rocket_shutdown_handle : None ,
2021-01-19 17:55:21 +01:00
templates : load_templates ( & config . templates_folder ) ,
config ,
_env ,
_usr ,
2021-06-19 19:22:19 +02:00
_overrides ,
2021-01-19 17:55:21 +01:00
} ) ,
2019-02-06 00:38:57 +01:00
} )
}
2019-02-02 16:47:27 +01:00
pub fn update_config ( & self , other : ConfigBuilder ) -> Result < ( ) , Error > {
2019-02-03 00:22:18 +01:00
// Remove default values
2019-02-06 00:38:57 +01:00
//let builder = other.remove(&self.inner.read().unwrap()._env);
// TODO: Remove values that are defaults, above only checks those set by env and not the defaults
let builder = other ;
2019-02-02 16:47:27 +01:00
2019-02-03 00:22:18 +01:00
// Serialize now before we consume the builder
let config_str = serde_json ::to_string_pretty ( & builder ) ? ;
2019-02-02 17:45:25 +01:00
2019-02-03 00:22:18 +01:00
// Prepare the combined config
2021-06-19 19:22:19 +02:00
let mut overrides = Vec ::new ( ) ;
2019-02-03 00:22:18 +01:00
let config = {
let env = & self . inner . read ( ) . unwrap ( ) . _env ;
2021-06-19 19:22:19 +02:00
env . merge ( & builder , false , & mut overrides ) . build ( )
2019-02-03 00:22:18 +01:00
} ;
validate_config ( & config ) ? ;
// Save both the user and the combined config
{
let mut writer = self . inner . write ( ) . unwrap ( ) ;
writer . config = config ;
writer . _usr = builder ;
2021-06-19 19:22:19 +02:00
writer . _overrides = overrides ;
2019-02-03 00:22:18 +01:00
}
2019-02-02 16:47:27 +01:00
2019-02-02 17:45:25 +01:00
//Save to file
use std ::{ fs ::File , io ::Write } ;
2019-02-04 01:37:25 +01:00
let mut file = File ::create ( & * CONFIG_FILE ) ? ;
2019-02-02 17:45:25 +01:00
file . write_all ( config_str . as_bytes ( ) ) ? ;
2019-02-02 16:47:27 +01:00
Ok ( ( ) )
}
2021-10-23 20:46:39 +02:00
fn update_config_partial ( & self , other : ConfigBuilder ) -> Result < ( ) , Error > {
2019-04-11 16:08:26 +02:00
let builder = {
let usr = & self . inner . read ( ) . unwrap ( ) . _usr ;
2021-06-19 19:22:19 +02:00
let mut _overrides = Vec ::new ( ) ;
usr . merge ( & other , false , & mut _overrides )
2019-04-11 16:08:26 +02:00
} ;
self . update_config ( builder )
}
2020-05-24 23:00:26 +02:00
/// Tests whether an email's domain is allowed. A domain is allowed if it
/// is in signups_domains_whitelist, or if no whitelist is set (so there
/// are no domain restrictions in effect).
pub fn is_email_domain_allowed ( & self , email : & str ) -> bool {
2019-11-28 21:59:05 +01:00
let e : Vec < & str > = email . rsplitn ( 2 , '@' ) . collect ( ) ;
2019-11-16 23:01:45 +01:00
if e . len ( ) ! = 2 | | e [ 0 ] . is_empty ( ) | | e [ 1 ] . is_empty ( ) {
warn! ( " Failed to parse email address '{}' " , email ) ;
2019-12-27 18:37:14 +01:00
return false ;
2019-11-16 23:01:45 +01:00
}
2020-04-11 23:51:36 +02:00
let email_domain = e [ 0 ] . to_lowercase ( ) ;
2020-04-09 10:42:27 +02:00
let whitelist = self . signups_domains_whitelist ( ) ;
2020-03-09 22:04:03 +01:00
2020-05-24 23:00:26 +02:00
whitelist . is_empty ( ) | | whitelist . split ( ',' ) . any ( | d | d . trim ( ) = = email_domain )
2020-04-09 10:42:27 +02:00
}
/// Tests whether signup is allowed for an email address, taking into
/// account the signups_allowed and signups_domains_whitelist settings.
pub fn is_signup_allowed ( & self , email : & str ) -> bool {
if ! self . signups_domains_whitelist ( ) . is_empty ( ) {
// The whitelist setting overrides the signups_allowed setting.
2020-05-24 23:00:26 +02:00
self . is_email_domain_allowed ( email )
2020-04-09 10:42:27 +02:00
} else {
self . signups_allowed ( )
}
2019-11-16 23:01:45 +01:00
}
2020-08-06 07:35:29 +02:00
/// Tests whether the specified user is allowed to create an organization.
pub fn is_org_creation_allowed ( & self , email : & str ) -> bool {
let users = self . org_creation_users ( ) ;
2021-01-19 17:55:21 +01:00
if users . is_empty ( ) | | users = = " all " {
2020-08-06 07:35:29 +02:00
true
} else if users = = " none " {
false
} else {
let email = email . to_lowercase ( ) ;
users . split ( ',' ) . any ( | u | u . trim ( ) = = email )
}
}
2019-02-06 17:32:13 +01:00
pub fn delete_user_config ( & self ) -> Result < ( ) , Error > {
2024-03-17 15:11:20 +01:00
std ::fs ::remove_file ( & * CONFIG_FILE ) ? ;
2019-02-06 17:32:13 +01:00
// Empty user config
let usr = ConfigBuilder ::default ( ) ;
// Config now is env + defaults
let config = {
let env = & self . inner . read ( ) . unwrap ( ) . _env ;
env . build ( )
} ;
// Save configs
{
let mut writer = self . inner . write ( ) . unwrap ( ) ;
writer . config = config ;
writer . _usr = usr ;
2021-06-19 19:22:19 +02:00
writer . _overrides = Vec ::new ( ) ;
2019-02-06 17:32:13 +01:00
}
Ok ( ( ) )
}
2019-02-03 00:22:18 +01:00
pub fn private_rsa_key ( & self ) -> String {
format! ( " {} .pem " , CONFIG . rsa_key_filename ( ) )
}
2019-02-02 01:09:21 +01:00
pub fn mail_enabled ( & self ) -> bool {
2019-02-06 00:38:57 +01:00
let inner = & self . inner . read ( ) . unwrap ( ) . config ;
2023-02-12 18:53:55 +01:00
inner . _enable_smtp & & ( inner . smtp_host . is_some ( ) | | inner . use_sendmail )
2019-02-06 00:38:57 +01:00
}
2019-04-11 16:08:26 +02:00
pub fn get_duo_akey ( & self ) -> String {
if let Some ( akey ) = self . _duo_akey ( ) {
akey
} else {
2022-11-13 10:03:04 +01:00
let akey_s = crate ::crypto ::encode_random_bytes ::< 64 > ( data_encoding ::BASE64 ) ;
2019-04-11 16:08:26 +02:00
// Save the new value
2021-01-19 17:55:21 +01:00
let builder = ConfigBuilder {
_duo_akey : Some ( akey_s . clone ( ) ) ,
.. Default ::default ( )
} ;
2019-04-11 16:08:26 +02:00
self . update_config_partial ( builder ) . ok ( ) ;
akey_s
}
2019-02-02 01:09:21 +01:00
}
2019-01-25 18:23:51 +01:00
2020-04-10 05:55:08 +02:00
/// Tests whether the admin token is set to a non-empty value.
pub fn is_admin_token_set ( & self ) -> bool {
let token = self . admin_token ( ) ;
2020-05-03 17:24:51 +02:00
token . is_some ( ) & & ! token . unwrap ( ) . trim ( ) . is_empty ( )
2020-04-10 05:55:08 +02:00
}
2019-02-02 01:09:21 +01:00
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 " ) ;
2020-01-26 15:29:14 +01:00
let hb = load_templates ( CONFIG . templates_folder ( ) ) ;
2019-02-02 01:09:21 +01:00
hb . render ( name , data ) . map_err ( Into ::into )
} else {
let hb = & CONFIG . inner . read ( ) . unwrap ( ) . templates ;
hb . render ( name , data ) . map_err ( Into ::into )
}
}
2021-11-07 18:53:39 +01:00
pub fn set_rocket_shutdown_handle ( & self , handle : rocket ::Shutdown ) {
self . inner . write ( ) . unwrap ( ) . rocket_shutdown_handle = Some ( handle ) ;
}
pub fn shutdown ( & self ) {
2022-05-20 20:37:32 +02:00
if let Ok ( mut c ) = self . inner . write ( ) {
if let Some ( handle ) = c . rocket_shutdown_handle . take ( ) {
2021-11-07 18:53:39 +01:00
handle . notify ( ) ;
}
}
}
2019-01-25 18:23:51 +01:00
}
2024-01-12 20:44:37 +01:00
use handlebars ::{
Context , DirectorySourceOptions , Handlebars , Helper , HelperResult , Output , RenderContext , RenderErrorReason ,
Renderable ,
} ;
2019-02-03 00:22:18 +01:00
2020-01-26 15:29:14 +01:00
fn load_templates < P > ( path : P ) -> Handlebars < 'static >
where
P : AsRef < std ::path ::Path > ,
{
2019-01-25 18:23:51 +01:00
let mut hb = Handlebars ::new ( ) ;
// Error on missing params
hb . set_strict_mode ( true ) ;
2019-02-17 15:22:27 +01:00
// Register helpers
2020-01-26 15:29:14 +01:00
hb . register_helper ( " case " , Box ::new ( case_helper ) ) ;
2022-12-28 20:05:10 +01:00
hb . register_helper ( " to_json " , Box ::new ( to_json ) ) ;
2019-01-25 18:23:51 +01:00
macro_rules ! reg {
( $name :expr ) = > { {
let template = include_str! ( concat! ( " static/templates/ " , $name , " .hbs " ) ) ;
hb . register_template_string ( $name , template ) . unwrap ( ) ;
} } ;
2019-02-10 19:12:34 +01:00
( $name :expr , $ext :expr ) = > { {
reg! ( $name ) ;
reg! ( concat! ( $name , $ext ) ) ;
} } ;
2019-01-25 18:23:51 +01:00
}
// First register default templates here
2021-05-08 17:46:31 +02:00
reg! ( " email/email_header " ) ;
reg! ( " email/email_footer " ) ;
reg! ( " email/email_footer_text " ) ;
2023-01-25 08:06:21 +01:00
reg! ( " email/admin_reset_password " , " .html " ) ;
2019-11-25 06:28:49 +01:00
reg! ( " email/change_email " , " .html " ) ;
reg! ( " email/delete_account " , " .html " ) ;
2021-03-24 20:15:55 +01:00
reg! ( " email/emergency_access_invite_accepted " , " .html " ) ;
reg! ( " email/emergency_access_invite_confirmed " , " .html " ) ;
reg! ( " email/emergency_access_recovery_approved " , " .html " ) ;
reg! ( " email/emergency_access_recovery_initiated " , " .html " ) ;
reg! ( " email/emergency_access_recovery_rejected " , " .html " ) ;
reg! ( " email/emergency_access_recovery_reminder " , " .html " ) ;
reg! ( " email/emergency_access_recovery_timed_out " , " .html " ) ;
2021-10-25 10:36:05 +02:00
reg! ( " email/incomplete_2fa_login " , " .html " ) ;
reg! ( " email/invite_accepted " , " .html " ) ;
reg! ( " email/invite_confirmed " , " .html " ) ;
2019-07-22 08:26:24 +02:00
reg! ( " email/new_device_logged_in " , " .html " ) ;
2023-11-12 22:15:44 +01:00
reg! ( " email/protected_action " , " .html " ) ;
2019-02-10 19:12:34 +01:00
reg! ( " email/pw_hint_none " , " .html " ) ;
reg! ( " email/pw_hint_some " , " .html " ) ;
2021-04-12 04:57:17 +02:00
reg! ( " email/send_2fa_removed_from_org " , " .html " ) ;
2021-03-24 20:15:55 +01:00
reg! ( " email/send_emergency_access_invite " , " .html " ) ;
2023-11-12 22:15:44 +01:00
reg! ( " email/send_org_invite " , " .html " ) ;
reg! ( " email/send_single_org_removed_from_org " , " .html " ) ;
reg! ( " email/smtp_test " , " .html " ) ;
2019-08-03 08:07:14 +02:00
reg! ( " email/twofactor_email " , " .html " ) ;
2019-11-25 06:28:49 +01:00
reg! ( " email/verify_email " , " .html " ) ;
reg! ( " email/welcome_must_verify " , " .html " ) ;
2023-11-12 22:15:44 +01:00
reg! ( " email/welcome " , " .html " ) ;
2019-01-25 18:23:51 +01:00
reg! ( " admin/base " ) ;
reg! ( " admin/login " ) ;
2020-05-28 10:42:36 +02:00
reg! ( " admin/settings " ) ;
reg! ( " admin/users " ) ;
reg! ( " admin/organizations " ) ;
reg! ( " admin/diagnostics " ) ;
2019-01-25 18:23:51 +01:00
2022-12-02 17:58:27 +01:00
reg! ( " 404 " ) ;
2019-01-25 18:23:51 +01:00
// And then load user templates to overwrite the defaults
// Use .hbs extension for the files
// Templates get registered with their relative name
2024-01-12 20:44:37 +01:00
hb . register_templates_directory (
path ,
DirectorySourceOptions {
tpl_extension : " .hbs " . to_owned ( ) ,
.. Default ::default ( )
} ,
)
. unwrap ( ) ;
2019-01-25 18:23:51 +01:00
hb
}
2019-02-03 00:22:18 +01:00
2020-01-26 15:29:14 +01:00
fn case_helper < ' reg , ' rc > (
2024-01-12 20:44:37 +01:00
h : & Helper < ' rc > ,
2021-11-19 17:50:16 +01:00
r : & ' reg Handlebars < '_ > ,
2020-01-26 15:29:14 +01:00
ctx : & ' rc Context ,
rc : & mut RenderContext < ' reg , ' rc > ,
out : & mut dyn Output ,
) -> HelperResult {
2024-01-12 20:44:37 +01:00
let param =
h . param ( 0 ) . ok_or_else ( | | RenderErrorReason ::Other ( String ::from ( " Param not found for helper \" case \" " ) ) ) ? ;
2020-01-26 15:29:14 +01:00
let value = param . value ( ) . clone ( ) ;
if h . params ( ) . iter ( ) . skip ( 1 ) . any ( | x | x . value ( ) = = & value ) {
2022-11-20 19:15:45 +01:00
h . template ( ) . map ( | t | t . render ( r , ctx , rc , out ) ) . unwrap_or_else ( | | Ok ( ( ) ) )
2020-01-26 15:29:14 +01:00
} else {
Ok ( ( ) )
2019-02-03 00:22:18 +01:00
}
}
2019-02-17 15:22:27 +01:00
2022-12-28 20:05:10 +01:00
fn to_json < ' reg , ' rc > (
2024-01-12 20:44:37 +01:00
h : & Helper < ' rc > ,
2022-12-28 20:05:10 +01:00
_r : & ' reg Handlebars < '_ > ,
_ctx : & ' rc Context ,
_rc : & mut RenderContext < ' reg , ' rc > ,
out : & mut dyn Output ,
) -> HelperResult {
2024-01-12 20:44:37 +01:00
let param = h
. param ( 0 )
. ok_or_else ( | | RenderErrorReason ::Other ( String ::from ( " Expected 1 parameter for \" to_json \" " ) ) ) ?
. value ( ) ;
2022-12-28 20:05:10 +01:00
let json = serde_json ::to_string ( param )
2024-01-12 20:44:37 +01:00
. map_err ( | e | RenderErrorReason ::Other ( format! ( " Can't serialize parameter to JSON: {e} " ) ) ) ? ;
2022-12-28 20:05:10 +01:00
out . write ( & json ) ? ;
Ok ( ( ) )
}