summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock43
-rw-r--r--Cargo.toml3
-rw-r--r--src/api/web.rs103
-rw-r--r--src/config.rs15
-rw-r--r--src/static/templates/scss/user.vaultwarden.scss.hbs1
-rw-r--r--src/static/templates/scss/vaultwarden.scss.hbs105
6 files changed, 266 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ae469d23..9edd20bf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -553,6 +553,12 @@ dependencies = [
]
[[package]]
+name = "codemap"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
+
+[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1232,6 +1238,19 @@ dependencies = [
]
[[package]]
+name = "grass_compiler"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d9e3df7f0222ce5184154973d247c591d9aadc28ce7a73c6cd31100c9facff6"
+dependencies = [
+ "codemap",
+ "indexmap",
+ "lasso",
+ "once_cell",
+ "phf",
+]
+
+[[package]]
name = "h2"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1887,6 +1906,15 @@ dependencies = [
]
[[package]]
+name = "lasso"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb"
+dependencies = [
+ "hashbrown 0.14.5",
+]
+
+[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2484,6 +2512,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
+ "phf_macros",
"phf_shared",
]
@@ -2508,6 +2537,19 @@ dependencies = [
]
[[package]]
+name = "phf_macros"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4056,6 +4098,7 @@ dependencies = [
"fern",
"futures",
"governor",
+ "grass_compiler",
"handlebars",
"hickory-resolver",
"html5gum",
diff --git a/Cargo.toml b/Cargo.toml
index 78a1a4b5..150b3b9d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -163,6 +163,9 @@ argon2 = "0.5.3"
# Reading a password from the cli for generating the Argon2id ADMIN_TOKEN
rpassword = "7.3.1"
+# Loading a dynamic CSS Stylesheet
+grass_compiler = { version = "0.13.4", default-features = false }
+
# Strip debuginfo from the release builds
# The symbols are the provide better panic traces
# Also enable fat LTO and use 1 codegen unit for optimizations
diff --git a/src/api/web.rs b/src/api/web.rs
index 6983719b..a96d7e2a 100644
--- a/src/api/web.rs
+++ b/src/api/web.rs
@@ -1,13 +1,20 @@
+use once_cell::sync::Lazy;
use std::path::{Path, PathBuf};
-use rocket::{fs::NamedFile, http::ContentType, response::content::RawHtml as Html, serde::json::Json, Catcher, Route};
+use rocket::{
+ fs::NamedFile,
+ http::ContentType,
+ response::{content::RawCss as Css, content::RawHtml as Html, Redirect},
+ serde::json::Json,
+ Catcher, Route,
+};
use serde_json::Value;
use crate::{
api::{core::now, ApiResult, EmptyResult},
auth::decode_file_download,
error::Error,
- util::{Cached, SafeString},
+ util::{get_web_vault_version, Cached, SafeString},
CONFIG,
};
@@ -16,7 +23,7 @@ pub fn routes() -> Vec<Route> {
// crate::utils::LOGGED_ROUTES to make sure they appear in the log
let mut routes = routes![attachments, alive, alive_head, static_files];
if CONFIG.web_vault_enabled() {
- routes.append(&mut routes![web_index, web_index_head, app_id, web_files]);
+ routes.append(&mut routes![web_index, web_index_direct, web_index_head, app_id, web_files, vaultwarden_css]);
}
#[cfg(debug_assertions)]
@@ -45,11 +52,101 @@ fn not_found() -> ApiResult<Html<String>> {
Ok(Html(text))
}
+#[get("/css/vaultwarden.css")]
+fn vaultwarden_css() -> Cached<Css<String>> {
+ // Configure the web-vault version as an integer so it can be used as a comparison smaller or greater then.
+ // The default is based upon the version since this feature is added.
+ static WEB_VAULT_VERSION: Lazy<u32> = Lazy::new(|| {
+ let re = regex::Regex::new(r"(\d{4})\.(\d{1,2})\.(\d{1,2})").unwrap();
+ let vault_version = get_web_vault_version();
+
+ let (major, minor, patch) = match re.captures(&vault_version) {
+ Some(c) if c.len() == 4 => (
+ c.get(1).unwrap().as_str().parse().unwrap(),
+ c.get(2).unwrap().as_str().parse().unwrap(),
+ c.get(3).unwrap().as_str().parse().unwrap(),
+ ),
+ _ => (2024, 6, 2),
+ };
+ format!("{major}{minor:02}{patch:02}").parse::<u32>().unwrap()
+ });
+
+ // Configure the Vaultwarden version as an integer so it can be used as a comparison smaller or greater then.
+ // The default is based upon the version since this feature is added.
+ static VW_VERSION: Lazy<u32> = Lazy::new(|| {
+ let re = regex::Regex::new(r"(\d{1})\.(\d{1,2})\.(\d{1,2})").unwrap();
+ let vw_version = crate::VERSION.unwrap_or("1.32.1");
+
+ let (major, minor, patch) = match re.captures(vw_version) {
+ Some(c) if c.len() == 4 => (
+ c.get(1).unwrap().as_str().parse().unwrap(),
+ c.get(2).unwrap().as_str().parse().unwrap(),
+ c.get(3).unwrap().as_str().parse().unwrap(),
+ ),
+ _ => (1, 32, 1),
+ };
+ format!("{major}{minor:02}{patch:02}").parse::<u32>().unwrap()
+ });
+
+ let css_options = json!({
+ "web_vault_version": *WEB_VAULT_VERSION,
+ "vw_version": *VW_VERSION,
+ "signup_disabled": !CONFIG.signups_allowed() && CONFIG.signups_domains_whitelist().is_empty(),
+ "mail_enabled": CONFIG.mail_enabled(),
+ "yubico_enabled": CONFIG._enable_yubico() && (CONFIG.yubico_client_id().is_some() == CONFIG.yubico_secret_key().is_some()),
+ "emergency_access_allowed": CONFIG.emergency_access_allowed(),
+ "sends_allowed": CONFIG.sends_allowed(),
+ "load_user_scss": true,
+ });
+
+ let scss = match CONFIG.render_template("scss/vaultwarden.scss", &css_options) {
+ Ok(t) => t,
+ Err(e) => {
+ // Something went wrong loading the template. Use the fallback
+ warn!("Loading scss/vaultwarden.scss.hbs or scss/user.vaultwarden.scss.hbs failed. {e}");
+ CONFIG
+ .render_fallback_template("scss/vaultwarden.scss", &css_options)
+ .expect("Fallback scss/vaultwarden.scss.hbs to render")
+ }
+ };
+
+ let css = match grass_compiler::from_string(
+ scss,
+ &grass_compiler::Options::default().style(grass_compiler::OutputStyle::Compressed),
+ ) {
+ Ok(css) => css,
+ Err(e) => {
+ // Something went wrong compiling the scss. Use the fallback
+ warn!("Compiling the Vaultwarden SCSS styles failed. {e}");
+ let mut css_options = css_options;
+ css_options["load_user_scss"] = json!(false);
+ let scss = CONFIG
+ .render_fallback_template("scss/vaultwarden.scss", &css_options)
+ .expect("Fallback scss/vaultwarden.scss.hbs to render");
+ grass_compiler::from_string(
+ scss,
+ &grass_compiler::Options::default().style(grass_compiler::OutputStyle::Compressed),
+ )
+ .expect("SCSS to compile")
+ }
+ };
+
+ // Cache for one day should be enough and not too much
+ Cached::ttl(Css(css), 86_400, false)
+}
+
#[get("/")]
async fn web_index() -> Cached<Option<NamedFile>> {
Cached::short(NamedFile::open(Path::new(&CONFIG.web_vault_folder()).join("index.html")).await.ok(), false)
}
+// Make sure that `/index.html` redirect to actual domain path.
+// If not, this might cause issues with the web-vault
+#[get("/index.html")]
+fn web_index_direct() -> Redirect {
+ Redirect::to(format!("{}/", CONFIG.domain_path()))
+}
+
#[head("/")]
fn web_index_head() -> EmptyResult {
// Add an explicit HEAD route to prevent uptime monitoring services from
diff --git a/src/config.rs b/src/config.rs
index aa6b1145..61a47b76 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1269,11 +1269,16 @@ impl Config {
let hb = load_templates(CONFIG.templates_folder());
hb.render(name, data).map_err(Into::into)
} else {
- let hb = &CONFIG.inner.read().unwrap().templates;
+ let hb = &self.inner.read().unwrap().templates;
hb.render(name, data).map_err(Into::into)
}
}
+ pub fn render_fallback_template<T: serde::ser::Serialize>(&self, name: &str, data: &T) -> Result<String, Error> {
+ let hb = &self.inner.read().unwrap().templates;
+ hb.render(&format!("fallback_{name}"), data).map_err(Into::into)
+ }
+
pub fn set_rocket_shutdown_handle(&self, handle: rocket::Shutdown) {
self.inner.write().unwrap().rocket_shutdown_handle = Some(handle);
}
@@ -1312,6 +1317,11 @@ where
reg!($name);
reg!(concat!($name, $ext));
}};
+ (@withfallback $name:expr) => {{
+ let template = include_str!(concat!("static/templates/", $name, ".hbs"));
+ hb.register_template_string($name, template).unwrap();
+ hb.register_template_string(concat!("fallback_", $name), template).unwrap();
+ }};
}
// First register default templates here
@@ -1355,6 +1365,9 @@ where
reg!("404");
+ reg!(@withfallback "scss/vaultwarden.scss");
+ reg!("scss/user.vaultwarden.scss");
+
// And then load user templates to overwrite the defaults
// Use .hbs extension for the files
// Templates get registered with their relative name
diff --git a/src/static/templates/scss/user.vaultwarden.scss.hbs b/src/static/templates/scss/user.vaultwarden.scss.hbs
new file mode 100644
index 00000000..c0b8ed2a
--- /dev/null
+++ b/src/static/templates/scss/user.vaultwarden.scss.hbs
@@ -0,0 +1 @@
+/* See the wiki for examples and details: https://github.com/dani-garcia/vaultwarden/wiki/Customize-Vaultwarden-CSS */
diff --git a/src/static/templates/scss/vaultwarden.scss.hbs b/src/static/templates/scss/vaultwarden.scss.hbs
new file mode 100644
index 00000000..3fc3e70e
--- /dev/null
+++ b/src/static/templates/scss/vaultwarden.scss.hbs
@@ -0,0 +1,105 @@
+/**** START Static Vaultwarden changes ****/
+/* This combines all selectors extending it into one */
+%vw-hide {
+ display: none !important;
+}
+
+/* This allows searching for the combined style in the browsers dev-tools (look into the head tag) */
+.vw-hide,
+head {
+ @extend %vw-hide;
+}
+
+/* Hide the Subscription Page tab */
+bit-nav-item[route="settings/subscription"] {
+ @extend %vw-hide;
+}
+
+/* Hide any link pointing to Free Bitwarden Families */
+a[href$="/settings/sponsored-families"] {
+ @extend %vw-hide;
+}
+
+/* Hide the `Enterprise Single Sign-On` button on the login page */
+a[routerlink="/sso"] {
+ @extend %vw-hide;
+}
+
+/* Hide Two-Factor menu in Organization settings */
+bit-nav-item[route="settings/two-factor"],
+a[href$="/settings/two-factor"] {
+ @extend %vw-hide;
+}
+
+/* Hide Business Owned checkbox */
+app-org-info > form:nth-child(1) > div:nth-child(3) {
+ @extend %vw-hide;
+}
+
+/* Hide the `This account is owned by a business` checkbox and label */
+#ownedBusiness,
+label[for^="ownedBusiness"] {
+ @extend %vw-hide;
+}
+
+/* Hide the radio button and label for the `Custom` org user type */
+#userTypeCustom,
+label[for^="userTypeCustom"] {
+ @extend %vw-hide;
+}
+
+/* Hide Business Name */
+app-org-account form div bit-form-field.tw-block:nth-child(3) {
+ @extend %vw-hide;
+}
+
+/* Hide organization plans */
+app-organization-plans > form > bit-section:nth-child(2) {
+ @extend %vw-hide;
+}
+
+/* Hide Device Verification form at the Two Step Login screen */
+app-security > app-two-factor-setup > form {
+ @extend %vw-hide;
+}
+/**** END Static Vaultwarden Changes ****/
+/**** START Dynamic Vaultwarden Changes ****/
+{{#if signup_disabled}}
+/* Hide the register link on the login screen */
+app-frontend-layout > app-login > form > div > div > div > p {
+ @extend %vw-hide;
+}
+{{/if}}
+
+/* Hide `Email` 2FA if mail is not enabled */
+{{#unless mail_enabled}}
+app-two-factor-setup ul.list-group.list-group-2fa li.list-group-item:nth-child(5) {
+ @extend %vw-hide;
+}
+{{/unless}}
+
+/* Hide `YubiKey OTP security key` 2FA if it is not enabled */
+{{#unless yubico_enabled}}
+app-two-factor-setup ul.list-group.list-group-2fa li.list-group-item:nth-child(2) {
+ @extend %vw-hide;
+}
+{{/unless}}
+
+/* Hide Emergency Access if not allowed */
+{{#unless emergency_access_allowed}}
+bit-nav-item[route="settings/emergency-access"] {
+ @extend %vw-hide;
+}
+{{/unless}}
+
+/* Hide Sends if not allowed */
+{{#unless sends_allowed}}
+bit-nav-item[route="sends"] {
+ @extend %vw-hide;
+}
+{{/unless}}
+/**** End Dynamic Vaultwarden Changes ****/
+/**** Include a special user stylesheet for custom changes ****/
+{{#if load_user_scss}}
+{{> scss/user.vaultwarden.scss }}
+{{/if}}