mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-01-16 12:43:02 +00:00
Cached config operations
This commit is contained in:
@@ -12,6 +12,7 @@ use serde_json::Value;
|
|||||||
use crate::{
|
use crate::{
|
||||||
api::{core::now, ApiResult, EmptyResult},
|
api::{core::now, ApiResult, EmptyResult},
|
||||||
auth::decode_file_download,
|
auth::decode_file_download,
|
||||||
|
config::CachedConfigOperation,
|
||||||
db::models::{AttachmentId, CipherId},
|
db::models::{AttachmentId, CipherId},
|
||||||
error::Error,
|
error::Error,
|
||||||
util::Cached,
|
util::Cached,
|
||||||
@@ -52,19 +53,18 @@ fn not_found() -> ApiResult<Html<String>> {
|
|||||||
Ok(Html(text))
|
Ok(Html(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/css/vaultwarden.css")]
|
static VAULTWARDEN_CSS_CACHE: CachedConfigOperation<String> = CachedConfigOperation::new(|config| {
|
||||||
fn vaultwarden_css() -> Cached<Css<String>> {
|
|
||||||
let css_options = json!({
|
let css_options = json!({
|
||||||
"emergency_access_allowed": CONFIG.emergency_access_allowed(),
|
"emergency_access_allowed": config.emergency_access_allowed(),
|
||||||
"load_user_scss": true,
|
"load_user_scss": true,
|
||||||
"mail_2fa_enabled": CONFIG._enable_email_2fa(),
|
"mail_2fa_enabled": config._enable_email_2fa(),
|
||||||
"mail_enabled": CONFIG.mail_enabled(),
|
"mail_enabled": config.mail_enabled(),
|
||||||
"sends_allowed": CONFIG.sends_allowed(),
|
"sends_allowed": config.sends_allowed(),
|
||||||
"signup_disabled": CONFIG.is_signup_disabled(),
|
"signup_disabled": config.is_signup_disabled(),
|
||||||
"sso_enabled": CONFIG.sso_enabled(),
|
"sso_enabled": config.sso_enabled(),
|
||||||
"sso_only": CONFIG.sso_enabled() && CONFIG.sso_only(),
|
"sso_only": config.sso_enabled() && config.sso_only(),
|
||||||
"yubico_enabled": CONFIG._enable_yubico() && CONFIG.yubico_client_id().is_some() && CONFIG.yubico_secret_key().is_some(),
|
"yubico_enabled": config._enable_yubico() && config.yubico_client_id().is_some() && config.yubico_secret_key().is_some(),
|
||||||
"webauthn_2fa_supported": CONFIG.is_webauthn_2fa_supported(),
|
"webauthn_2fa_supported": config.is_webauthn_2fa_supported(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let scss = match CONFIG.render_template("scss/vaultwarden.scss", &css_options) {
|
let scss = match CONFIG.render_template("scss/vaultwarden.scss", &css_options) {
|
||||||
@@ -78,7 +78,7 @@ fn vaultwarden_css() -> Cached<Css<String>> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let css = match grass_compiler::from_string(
|
match grass_compiler::from_string(
|
||||||
scss,
|
scss,
|
||||||
&grass_compiler::Options::default().style(grass_compiler::OutputStyle::Compressed),
|
&grass_compiler::Options::default().style(grass_compiler::OutputStyle::Compressed),
|
||||||
) {
|
) {
|
||||||
@@ -97,10 +97,12 @@ fn vaultwarden_css() -> Cached<Css<String>> {
|
|||||||
)
|
)
|
||||||
.expect("SCSS to compile")
|
.expect("SCSS to compile")
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Cache for one day should be enough and not too much
|
#[get("/css/vaultwarden.css")]
|
||||||
Cached::ttl(Css(css), 86_400, false)
|
fn vaultwarden_css() -> Css<String> {
|
||||||
|
Css(CONFIG.cached_operation(&VAULTWARDEN_CSS_CACHE))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{
|
|||||||
fmt,
|
fmt,
|
||||||
process::exit,
|
process::exit,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||||
LazyLock, RwLock,
|
LazyLock, RwLock,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -103,6 +103,7 @@ macro_rules! make_config {
|
|||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
rocket_shutdown_handle: Option<rocket::Shutdown>,
|
rocket_shutdown_handle: Option<rocket::Shutdown>,
|
||||||
|
revision: usize,
|
||||||
|
|
||||||
templates: Handlebars<'static>,
|
templates: Handlebars<'static>,
|
||||||
config: ConfigItems,
|
config: ConfigItems,
|
||||||
@@ -322,7 +323,7 @@ macro_rules! make_config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
struct ConfigItems { $($( $name: make_config! {@type $ty, $none_action}, )+)+ }
|
struct ConfigItems { $($( pub $name: make_config! {@type $ty, $none_action}, )+)+ }
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ElementDoc {
|
struct ElementDoc {
|
||||||
@@ -1467,6 +1468,23 @@ pub enum PathType {
|
|||||||
RsaKey,
|
RsaKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct CachedConfigOperation<T: Clone> {
|
||||||
|
generator: fn(&Config) -> T,
|
||||||
|
value_cache: RwLock<Option<T>>,
|
||||||
|
revision: AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> CachedConfigOperation<T> {
|
||||||
|
#[allow(private_interfaces)]
|
||||||
|
pub const fn new(generator: fn(&Config) -> T) -> Self {
|
||||||
|
CachedConfigOperation {
|
||||||
|
generator,
|
||||||
|
value_cache: RwLock::new(None),
|
||||||
|
revision: AtomicUsize::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub async fn load() -> Result<Self, Error> {
|
pub async fn load() -> Result<Self, Error> {
|
||||||
// Loading from env and file
|
// Loading from env and file
|
||||||
@@ -1486,6 +1504,7 @@ impl Config {
|
|||||||
Ok(Config {
|
Ok(Config {
|
||||||
inner: RwLock::new(Inner {
|
inner: RwLock::new(Inner {
|
||||||
rocket_shutdown_handle: None,
|
rocket_shutdown_handle: None,
|
||||||
|
revision: 1,
|
||||||
templates: load_templates(&config.templates_folder),
|
templates: load_templates(&config.templates_folder),
|
||||||
config,
|
config,
|
||||||
_env,
|
_env,
|
||||||
@@ -1524,6 +1543,7 @@ impl Config {
|
|||||||
writer.config = config;
|
writer.config = config;
|
||||||
writer._usr = builder;
|
writer._usr = builder;
|
||||||
writer._overrides = overrides;
|
writer._overrides = overrides;
|
||||||
|
writer.revision += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Save to file
|
//Save to file
|
||||||
@@ -1542,6 +1562,51 @@ impl Config {
|
|||||||
self.update_config(builder, false).await
|
self.update_config(builder, false).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn delete_user_config(&self) -> Result<(), Error> {
|
||||||
|
let operator = opendal_operator_for_path(&CONFIG_FILE_PARENT_DIR)?;
|
||||||
|
operator.delete(&CONFIG_FILENAME).await?;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
writer._overrides = Vec::new();
|
||||||
|
writer.revision += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cached_operation<T: Clone>(&self, operation: &CachedConfigOperation<T>) -> T {
|
||||||
|
let config_revision = self.inner.read().unwrap().revision;
|
||||||
|
let cache_revision = operation.revision.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
// If the current revision matches the cached revision, return the cached value
|
||||||
|
if cache_revision == config_revision {
|
||||||
|
let reader = operation.value_cache.read().unwrap();
|
||||||
|
return reader.as_ref().unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, compute the value, update the cache and revision, and return the new value
|
||||||
|
let value = (operation.generator)(&CONFIG);
|
||||||
|
{
|
||||||
|
let mut writer = operation.value_cache.write().unwrap();
|
||||||
|
*writer = Some(value.clone());
|
||||||
|
operation.revision.store(config_revision, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
/// Tests whether an email's domain is allowed. A domain is allowed if it
|
/// 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
|
/// is in signups_domains_whitelist, or if no whitelist is set (so there
|
||||||
/// are no domain restrictions in effect).
|
/// are no domain restrictions in effect).
|
||||||
@@ -1591,33 +1656,10 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_user_config(&self) -> Result<(), Error> {
|
|
||||||
let operator = opendal_operator_for_path(&CONFIG_FILE_PARENT_DIR)?;
|
|
||||||
operator.delete(&CONFIG_FILENAME).await?;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
writer._overrides = Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn private_rsa_key(&self) -> String {
|
pub fn private_rsa_key(&self) -> String {
|
||||||
format!("{}.pem", self.rsa_key_filename())
|
format!("{}.pem", self.rsa_key_filename())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mail_enabled(&self) -> bool {
|
pub fn mail_enabled(&self) -> bool {
|
||||||
let inner = &self.inner.read().unwrap().config;
|
let inner = &self.inner.read().unwrap().config;
|
||||||
inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail)
|
inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail)
|
||||||
|
|||||||
Reference in New Issue
Block a user