diff --git a/.env.template b/.env.template index 9fc29989..c0575a36 100644 --- a/.env.template +++ b/.env.template @@ -609,6 +609,10 @@ ## Enabling this would force the users to use a second factor to login every time. ## Note that the checkbox would still be present, but ignored. # DISABLE_2FA_REMEMBER=false + +## Set WebAuthn 2FA user verification to preferred +## When enabled, compatible authenticators can request PIN or biometric verification. +# WEBAUTHN_2FA_USER_VERIFICATION=false ## ## Authenticator Settings ## Disable authenticator time drifted codes to be valid. diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs index 07b964e5..98542311 100644 --- a/src/api/core/two_factor/webauthn.rs +++ b/src/api/core/two_factor/webauthn.rs @@ -44,6 +44,18 @@ static WEBAUTHN: LazyLock = LazyLock::new(|| { webauthn.build().expect("Building Webauthn failed") }); +fn user_verification_policy() -> UserVerificationPolicy { + user_verification_policy_from_config(CONFIG.webauthn_2fa_user_verification()) +} + +fn user_verification_policy_from_config(preferred: bool) -> UserVerificationPolicy { + if preferred { + UserVerificationPolicy::Preferred + } else { + UserVerificationPolicy::Discouraged_DO_NOT_USE + } +} + pub fn routes() -> Vec { routes![get_webauthn, generate_webauthn_challenge, activate_webauthn, activate_webauthn_put, delete_webauthn,] } @@ -150,7 +162,8 @@ async fn generate_webauthn_challenge(data: Json, headers: Hea )?; let mut state = serde_json::to_value(&state)?; - state["rs"]["policy"] = Value::String("discouraged".to_owned()); + let user_verification_policy = user_verification_policy(); + state["rs"]["policy"] = serde_json::to_value(&user_verification_policy)?; state["rs"]["extensions"].as_object_mut().unwrap().clear(); let type_ = TwoFactorType::WebauthnRegisterChallenge; @@ -160,7 +173,7 @@ async fn generate_webauthn_challenge(data: Json, headers: Hea // we need to modify some of the default settings defined by `start_passkey_registration()`. challenge.public_key.extensions = None; if let Some(asc) = challenge.public_key.authenticator_selection.as_mut() { - asc.user_verification = UserVerificationPolicy::Discouraged_DO_NOT_USE; + asc.user_verification = user_verification_policy; } let mut challenge_value = serde_json::to_value(challenge.public_key)?; @@ -387,15 +400,16 @@ pub async fn generate_webauthn_login(user_id: &UserId, conn: &DbConn) -> JsonRes // Generate a challenge based on the credentials let (mut response, state) = WEBAUTHN.start_passkey_authentication(&creds)?; - // Modify to discourage user verification + // Apply the configured user verification policy let mut state = serde_json::to_value(&state)?; - state["ast"]["policy"] = Value::String("discouraged".to_owned()); + let user_verification_policy = user_verification_policy(); + state["ast"]["policy"] = serde_json::to_value(&user_verification_policy)?; // Add appid, this is only needed for U2F compatibility, so maybe it can be removed as well let app_id = format!("{}/app-id.json", CONFIG.domain()); state["ast"]["appid"] = Value::String(app_id.clone()); - response.public_key.user_verification = UserVerificationPolicy::Discouraged_DO_NOT_USE; + response.public_key.user_verification = user_verification_policy; response .public_key .extensions @@ -516,3 +530,20 @@ fn check_and_update_backup_eligible( } Ok(false) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn configured_user_verification_policy() { + assert_eq!( + serde_json::to_value(user_verification_policy_from_config(false)).unwrap(), + "discouraged" + ); + assert_eq!( + serde_json::to_value(user_verification_policy_from_config(true)).unwrap(), + "preferred" + ); + } +} diff --git a/src/config.rs b/src/config.rs index d5b50146..4e05ad4a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -717,6 +717,9 @@ make_config! { /// Note that the checkbox would still be present, but ignored. disable_2fa_remember: bool, true, def, false; + /// Set WebAuthn 2FA user verification to preferred |> Discouraged avoids requesting a PIN or biometric check for standard WebAuthn 2FA. Preferred asks compatible authenticators for user verification and can improve compatibility with some security keys. + webauthn_2fa_user_verification: bool, true, def, false; + /// Disable authenticator time drifted codes to be valid |> Enabling this only allows the current TOTP code to be valid /// TOTP codes of the previous and next 30 seconds will be invalid. authenticator_disable_time_drift: bool, true, def, false;