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 }}