Browse Source

Allow customizing the featureStates (#4168)

* Allow customizing the featureStates

Use a comma separated list of features to enable using the FEATURE_FLAGS env variable

* Move feature flag parsing to util

* Fix formatting

* Update supported feature flags

* Rename feature_flags to experimental_client_feature_flags

Additionally, use a caret (^) instead of an exclamation mark (!) to disable features

* Fix formatting issue.

* Add documentation to env template

* Remove functionality to disable feature flags

* Fix JSON key for feature states

* Convert error to warning when feature flag is unrecognized

* Simplify parsing of feature flags

* Fix default value of feature flags in env template

* Fix formatting
Philipp Kolberg 1 year ago
parent
commit
98b2178c7d
4 changed files with 36 additions and 11 deletions
  1. 11 0
      .env.template
  2. 4 10
      src/api/core/mod.rs
  3. 12 1
      src/config.rs
  4. 9 0
      src/util.rs

+ 11 - 0
.env.template

@@ -390,6 +390,17 @@
 ## In any case, if a code has been used it can not be used again, also codes which predates it will be invalid.
 # AUTHENTICATOR_DISABLE_TIME_DRIFT=false
 
+## Client Settings
+## Enable experimental feature flags for clients.
+## This is a comma-separated list of flags, e.g. "flag1,flag2,flag3".
+##
+## The following flags are available:
+## - "autofill-overlay": Add an overlay menu to form fields for quick access to credentials.
+## - "autofill-v2": Use the new autofill implementation.
+## - "browser-fileless-import": Directly import credentials from other providers without a file.
+## - "fido2-vault-credentials": Enable the use of FIDO2 security keys as second factor.
+## EXPERIMENTAL_CLIENT_FEATURE_FLAGS=fido2-vault-credentials
+
 ## Rocket specific settings
 ## See https://rocket.rs/v0.4/guide/configuration/ for more details.
 # ROCKET_ADDRESS=0.0.0.0

+ 4 - 10
src/api/core/mod.rs

@@ -46,15 +46,14 @@ pub fn events_routes() -> Vec<Route> {
 //
 // Move this somewhere else
 //
-use rocket::{serde::json::Json, Catcher, Route};
-use serde_json::Value;
+use rocket::{serde::json::Json, serde::json::Value, Catcher, Route};
 
 use crate::{
     api::{JsonResult, JsonUpcase, Notify, UpdateType},
     auth::Headers,
     db::DbConn,
     error::Error,
-    util::get_reqwest_client,
+    util::{get_reqwest_client, parse_experimental_client_feature_flags},
 };
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -192,6 +191,7 @@ fn version() -> Json<&'static str> {
 #[get("/config")]
 fn config() -> Json<Value> {
     let domain = crate::CONFIG.domain();
+    let feature_states = parse_experimental_client_feature_flags(&crate::CONFIG.experimental_client_feature_flags());
     Json(json!({
         // Note: The clients use this version to handle backwards compatibility concerns
         // This means they expect a version that closely matches the Bitwarden server version
@@ -212,13 +212,7 @@ fn config() -> Json<Value> {
           "notifications": format!("{domain}/notifications"),
           "sso": "",
         },
-        "featureStates": {
-          // Any feature flags that we want the clients to use
-          // Can check the enabled ones at:
-          // https://vault.bitwarden.com/api/config
-          "fido2-vault-credentials": true,  // Passkey support
-          "autofill-v2": false,             // Disabled because it is causing issues https://github.com/dani-garcia/vaultwarden/discussions/4052
-        },
+        "featureStates": feature_states,
         "object": "config",
     }))
 }

+ 12 - 1
src/config.rs

@@ -9,7 +9,7 @@ use reqwest::Url;
 use crate::{
     db::DbConnType,
     error::Error,
-    util::{get_env, get_env_bool},
+    util::{get_env, get_env_bool, parse_experimental_client_feature_flags},
 };
 
 static CONFIG_FILE: Lazy<String> = Lazy::new(|| {
@@ -547,6 +547,9 @@ make_config! {
         /// TOTP codes of the previous and next 30 seconds will be invalid.
         authenticator_disable_time_drift: bool, true, def, false;
 
+        /// 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();
+
         /// 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;
@@ -751,6 +754,14 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
         )
     }
 
+    const KNOWN_FLAGS: &[&str] =
+        &["autofill-overlay", "autofill-v2", "browser-fileless-import", "fido2-vault-credentials"];
+    for flag in parse_experimental_client_feature_flags(&cfg.experimental_client_feature_flags).keys() {
+        if !KNOWN_FLAGS.contains(&flag.as_str()) {
+            warn!("The experimental client feature flag {flag:?} is unrecognized. Please ensure the feature flag is spelled correctly and that it is supported in this version.");
+        }
+    }
+
     if cfg._enable_duo
         && (cfg.duo_host.is_some() || cfg.duo_ikey.is_some() || cfg.duo_skey.is_some())
         && !(cfg.duo_host.is_some() && cfg.duo_ikey.is_some() && cfg.duo_skey.is_some())

+ 9 - 0
src/util.rs

@@ -2,6 +2,7 @@
 // Web Headers and caching
 //
 use std::{
+    collections::HashMap,
     io::{Cursor, ErrorKind},
     ops::Deref,
 };
@@ -747,3 +748,11 @@ pub fn convert_json_key_lcase_first(src_json: Value) -> Value {
         value => value,
     }
 }
+
+/// Parses the experimental client feature flags string into a HashMap.
+pub fn parse_experimental_client_feature_flags(experimental_client_feature_flags: &str) -> HashMap<String, bool> {
+    let feature_states =
+        experimental_client_feature_flags.to_lowercase().split(',').map(|f| (f.trim().to_owned(), true)).collect();
+
+    feature_states
+}