From 4c1ff0ab6812aaf4dbc5398103149a56abf695c4 Mon Sep 17 00:00:00 2001 From: BlackDex Date: Wed, 11 Sep 2024 14:27:40 +0200 Subject: [PATCH] Add dynamic CSS support Together with https://github.com/dani-garcia/bw_web_builds/pull/180 this PR will add support for dynamic CSS changes. For example, we could hide the register link if signups are not allowed. In the future show or hide the SSO button depending on if it is enabled or not. There also is a special `user.vaultwarden.scss` file so that users can add custom CSS without the need to modify the default (static) changes. This will prevent future changes from not being applied and still have the custom user changes to be added. Also added a special redirect when someone goes directly to `/index.html` as that might cause issues with loading other scripts and files. Signed-off-by: BlackDex --- Cargo.lock | 43 +++++++++ Cargo.toml | 3 + src/api/web.rs | 33 ++++++- src/config.rs | 3 + .../scss/examples/user.vaultwarden.scss.hbs | 64 +++++++++++++ .../templates/scss/user.vaultwarden.scss.hbs | 30 ++++++ .../templates/scss/vaultwarden.scss.hbs | 95 +++++++++++++++++++ 7 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 src/static/templates/scss/examples/user.vaultwarden.scss.hbs create mode 100644 src/static/templates/scss/user.vaultwarden.scss.hbs create mode 100644 src/static/templates/scss/vaultwarden.scss.hbs diff --git a/Cargo.lock b/Cargo.lock index 645b7d3b35..d725a32a74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,6 +560,12 @@ dependencies = [ "stacker", ] +[[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" @@ -1241,6 +1247,19 @@ dependencies = [ "spinning_top", ] +[[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" @@ -1897,6 +1916,15 @@ dependencies = [ "log", ] +[[package]] +name = "lasso" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" +dependencies = [ + "hashbrown", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2483,6 +2511,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ + "phf_macros", "phf_shared", ] @@ -2506,6 +2535,19 @@ dependencies = [ "rand", ] +[[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" @@ -4097,6 +4139,7 @@ dependencies = [ "fern", "futures", "governor", + "grass_compiler", "handlebars", "hickory-resolver", "html5gum", diff --git a/Cargo.toml b/Cargo.toml index bca1aaebbd..71427909a6 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 6983719b95..949c86e7ce 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -1,6 +1,12 @@ 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::{ @@ -16,7 +22,7 @@ pub fn routes() -> Vec { // 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 +51,34 @@ fn not_found() -> ApiResult> { Ok(Html(text)) } +#[get("/css/vaultwarden.css")] +fn vaultwarden_css() -> Cached> { + let css_options = json!({ + "signup_disabled": !CONFIG.signups_allowed() && CONFIG.signups_domains_whitelist().is_empty(), + "mail_enabled": CONFIG.mail_enabled(), + }); + dbg!(&css_options); + let scss = CONFIG.render_template("scss/vaultwarden.scss", &css_options).expect("Rendered scss/vaultwarden.scss"); + let css = grass_compiler::from_string( + scss, + &grass_compiler::Options::default().style(grass_compiler::OutputStyle::Expanded), + ) + .expect("scss/vaultwarden.scss to compile"); + Cached::short(Css(css), false) +} + #[get("/")] async fn web_index() -> Cached> { 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 f5466e8606..ff876265af 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1352,6 +1352,9 @@ where reg!("404"); + reg!("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/examples/user.vaultwarden.scss.hbs b/src/static/templates/scss/examples/user.vaultwarden.scss.hbs new file mode 100644 index 0000000000..aeaec70797 --- /dev/null +++ b/src/static/templates/scss/examples/user.vaultwarden.scss.hbs @@ -0,0 +1,64 @@ +/**** START Custom User Changes ****/ + +/* Hide `Authenticator app` 2FA (First item of the list) */ +app-two-factor-setup ul.list-group.list-group-2fa li.list-group-item:nth-child(1) { + @extend %vw-hide; +} + +/* Hide `YubiKey OTP security key` 2FA (Second item of the list) */ +app-two-factor-setup ul.list-group.list-group-2fa li.list-group-item:nth-child(2) { + @extend %vw-hide; +} + +/* Hide `Duo` 2FA (Third item of the list) */ +app-two-factor-setup ul.list-group.list-group-2fa li.list-group-item:nth-child(3) { + @extend %vw-hide; +} + +/* Hide `FIDO2 WebAuthn` 2FA (Fourth item of the list) */ +app-two-factor-setup ul.list-group.list-group-2fa li.list-group-item:nth-child(4) { + @extend %vw-hide; +} + +/* Hide `Email` 2FA (Fifth item of the list) */ +/* Note: This will also be hidden automatically if email is not enabled */ +app-two-factor-setup ul.list-group.list-group-2fa li.list-group-item:nth-child(5) { + @extend %vw-hide; +} + + +/* Use a custom top left logo global */ +bit-icon > svg { + display: none !important; +} +bit-icon::before { + display: block; + content: "" !important; + width: 175px !important; + height: 30px !important; + background-image: url(../images/my-custom-global-logo.png) !important; + background-repeat: no-repeat !important; + background-size: contain; +} + + +/* Use a custom top left logo different per vault/admin */ +bit-icon > svg { + display: none !important; +} +bit-icon::before { + display: block; + content: "" !important; + width: 175px !important; + height: 30px !important; + background-repeat: no-repeat !important; + background-size: contain; +} +app-user-layout bit-icon::before { + background-image: url(../images/my-custom-password-manager-logo.png) !important; +} +app-organization-layout bit-icon::before { + background-image: url(../images/my-custom-admin-console-logo.png) !important; +} + +/**** END Custom User Changes ****/ 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 0000000000..114b6ab449 --- /dev/null +++ b/src/static/templates/scss/user.vaultwarden.scss.hbs @@ -0,0 +1,30 @@ +/**** START Custom User Changes ****/ +bit-icon > svg { + display: none !important; +} +bit-icon::before { + display: block; + content: "" !important; + width: 175px !important; + height: 30px !important; + background-repeat: no-repeat !important; + background-size: contain; +} + +app-user-layout bit-icon::before { + background-image: url(../images/logo-white@2x.png) !important; +} + +app-organization-layout bit-icon::before { + background-image: url(../images/logo-primary@2x.png) !important; +} + +bit-layout .tw-bg-background-alt4, bit-layout .tw-bg-background-alt3 { + background-color: rgb(var(--color-background-alt) / var(--tw-bg-opacity)) !important; +} + + +body, html { + border: 2px solid red !important; +} +/**** END Custom User Changes ****/ diff --git a/src/static/templates/scss/vaultwarden.scss.hbs b/src/static/templates/scss/vaultwarden.scss.hbs new file mode 100644 index 0000000000..6209a97f7c --- /dev/null +++ b/src/static/templates/scss/vaultwarden.scss.hbs @@ -0,0 +1,95 @@ +/**** 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; +} + +/* Replace the Bitwarden Shield at the top left with a Vaultwarden icon */ +.bwi-shield:before { + content: "" !important; + width: 32px !important; + height: 40px !important; + display: block !important; + background-image: url(../images/icon-white.png) !important; + background-repeat: no-repeat; + background-position-y: bottom; +} +/**** 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}} + +{{#unless mail_enabled}} +/* Hide Email 2FA if mail is not enabled */ +app-two-factor-setup ul.list-group.list-group-2fa li.list-group-item:nth-child(5) { + @extend %vw-hide; +} +{{/unless}} +/**** End Dynamic Vaultwarden Changes ****/ + +/**** Include a special user stylesheet for custom changes ****/ +{{> scss/user.vaultwarden.scss }}