diff --git a/Cargo.toml b/Cargo.toml index 872ea1a0..0e11ad7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,7 @@ proxmox-lang = "1" proxmox-router = { version = "1.1", features = [ "cli" ] } proxmox-schema = { version = "1", features = [ "api-macro" ] } proxmox-section-config = "1" -proxmox-tfa = { version = "1.3", features = [ "api", "api-types" ] } +proxmox-tfa = { version = "2", features = [ "api", "api-types" ] } proxmox-time = "1" proxmox-uuid = "1" proxmox-serde = "0.1" diff --git a/src/api2/access/tfa.rs b/src/api2/access/tfa.rs index 76258320..79e68bfa 100644 --- a/src/api2/access/tfa.rs +++ b/src/api2/access/tfa.rs @@ -228,6 +228,7 @@ fn add_tfa_entry( value, challenge, r#type, + None, )?; crate::config::tfa::write(&data)?; Ok(out) diff --git a/src/api2/config/access/tfa.rs b/src/api2/config/access/tfa.rs index f4720f41..a961a2f0 100644 --- a/src/api2/config/access/tfa.rs +++ b/src/api2/config/access/tfa.rs @@ -1,12 +1,13 @@ //! For now this only has the TFA subdir, which is in this file. //! If we add more, it should be moved into a sub module. -use anyhow::Error; +use anyhow::{format_err, Error}; use hex::FromHex; +use serde::{Deserialize, Serialize}; -use proxmox_router::{Router, RpcEnvironment, Permission, SubdirMap}; -use proxmox_schema::api; use proxmox_router::list_subdirs_api_method; +use proxmox_router::{Permission, Router, RpcEnvironment, SubdirMap}; +use proxmox_schema::api; use pbs_api_types::PROXMOX_CONFIG_DIGEST_SCHEMA; @@ -47,6 +48,15 @@ pub fn get_webauthn_config( Ok(Some(config)) } +#[api()] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +/// Deletable property name +pub enum DeletableProperty { + /// Delete the origin property. + Origin, +} + #[api( protected: true, input: { @@ -55,6 +65,14 @@ pub fn get_webauthn_config( flatten: true, type: WebauthnConfigUpdater, }, + delete: { + description: "List of properties to delete.", + type: Array, + optional: true, + items: { + type: DeletableProperty, + } + }, digest: { optional: true, schema: PROXMOX_CONFIG_DIGEST_SCHEMA, @@ -65,6 +83,7 @@ pub fn get_webauthn_config( /// Update the TFA configuration. pub fn update_webauthn_config( webauthn: WebauthnConfigUpdater, + delete: Option>, digest: Option, ) -> Result<(), Error> { let _lock = tfa::write_lock(); @@ -79,13 +98,34 @@ pub fn update_webauthn_config( &crate::config::tfa::webauthn_config_digest(&wa)?, )?; } - if let Some(ref rp) = webauthn.rp { wa.rp = rp.clone(); } - if let Some(ref origin) = webauthn.origin { wa.origin = origin.clone(); } - if let Some(ref id) = webauthn.id { wa.id = id.clone(); } + + if let Some(delete) = delete { + for delete in delete { + match delete { + DeletableProperty::Origin => { + wa.origin = None; + } + } + } + } + + if let Some(rp) = webauthn.rp { + wa.rp = rp; + } + if webauthn.origin.is_some() { + wa.origin = webauthn.origin; + } + if let Some(id) = webauthn.id { + wa.id = id; + } } else { - let rp = webauthn.rp.unwrap(); - let origin = webauthn.origin.unwrap(); - let id = webauthn.id.unwrap(); + let rp = webauthn + .rp + .ok_or_else(|| format_err!("missing proeprty: 'rp'"))?; + let origin = webauthn.origin; + let id = webauthn + .id + .ok_or_else(|| format_err!("missing property: 'id'"))?; tfa.webauthn = Some(WebauthnConfig { rp, origin, id }); } diff --git a/src/config/tfa.rs b/src/config/tfa.rs index d8a1680e..790e0960 100644 --- a/src/config/tfa.rs +++ b/src/config/tfa.rs @@ -127,7 +127,7 @@ impl TfaUserChallengeData { /// Get an optional TFA challenge for a user. pub fn login_challenge(userid: &Userid) -> Result, Error> { let _lock = write_lock()?; - read()?.authentication_challenge(UserAccess, userid.as_str()) + read()?.authentication_challenge(UserAccess, userid.as_str(), None) } /// Add a TOTP entry for a user. Returns the ID. @@ -176,7 +176,7 @@ pub fn add_webauthn_registration(userid: &Userid, description: String) -> Result let _lock = crate::config::tfa::write_lock(); let mut data = read()?; let challenge = - data.webauthn_registration_challenge(UserAccess, userid.as_str(), description)?; + data.webauthn_registration_challenge(UserAccess, userid.as_str(), description, None)?; write(&data)?; Ok(challenge) } @@ -189,7 +189,8 @@ pub fn finish_webauthn_registration( ) -> Result { let _lock = crate::config::tfa::write_lock(); let mut data = read()?; - let id = data.webauthn_registration_finish(UserAccess, userid.as_str(), challenge, response)?; + let id = + data.webauthn_registration_finish(UserAccess, userid.as_str(), challenge, response, None)?; write(&data)?; Ok(id) } @@ -203,7 +204,7 @@ pub fn verify_challenge( let _lock = crate::config::tfa::write_lock(); let mut data = read()?; if data - .verify(UserAccess, userid.as_str(), challenge, response)? + .verify(UserAccess, userid.as_str(), challenge, response, None)? .needs_saving() { write(&data)?; @@ -261,11 +262,10 @@ impl proxmox_tfa::api::OpenUserChallengeData for UserAccess { Err(err) => { eprintln!( "failed to parse challenge data for user {}: {}", - userid, - err + userid, err ); Default::default() - }, + } } };