diff --git a/.env.template b/.env.template index 3c177a2608..ee78161973 100644 --- a/.env.template +++ b/.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 diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index 3919d2a645..7712ea824a 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -46,15 +46,14 @@ pub fn events_routes() -> Vec { // // 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 { 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 { "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", })) } diff --git a/src/config.rs b/src/config.rs index 041e89a731..c182179888 100644 --- a/src/config.rs +++ b/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 = 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()) diff --git a/src/util.rs b/src/util.rs index 9b43116f7e..b15353010d 100644 --- a/src/util.rs +++ b/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 { + let feature_states = + experimental_client_feature_flags.to_lowercase().split(',').map(|f| (f.trim().to_owned(), true)).collect(); + + feature_states +}