forked from Proxmox/proxmox
tfa: add a 'types' feature to get TfaInfo and TfaType
without adding the entire API as well, so API clients can actually use the types used by the api methods without requiring the backend implementation being built in as well... Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
b6840e95ad
commit
0d942e81a3
@ -30,6 +30,7 @@ proxmox-uuid = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
types = [ "serde/derive"]
|
||||
u2f = [ "dep:libc", "dep:serde_json", "serde/derive" ]
|
||||
api = [ "u2f", "dep:webauthn-rs", "dep:proxmox-uuid", "dep:proxmox-time" ]
|
||||
api-types = [ "dep:proxmox-schema" ]
|
||||
api = [ "types", "u2f", "dep:webauthn-rs", "dep:proxmox-uuid", "dep:proxmox-time" ]
|
||||
api-types = [ "types", "dep:proxmox-schema" ]
|
||||
|
@ -12,39 +12,7 @@ use proxmox_schema::api;
|
||||
use super::{OpenUserChallengeData, TfaConfig, TfaInfo, TfaUserData};
|
||||
use crate::totp::Totp;
|
||||
|
||||
#[cfg_attr(feature = "api-types", api)]
|
||||
/// A TFA entry type.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum TfaType {
|
||||
/// A TOTP entry type.
|
||||
Totp,
|
||||
/// A U2F token entry.
|
||||
U2f,
|
||||
/// A Webauthn token entry.
|
||||
Webauthn,
|
||||
/// Recovery tokens.
|
||||
Recovery,
|
||||
/// Yubico authentication entry.
|
||||
Yubico,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "api-types", api(
|
||||
properties: {
|
||||
type: { type: TfaType },
|
||||
info: { type: TfaInfo },
|
||||
},
|
||||
))]
|
||||
/// A TFA entry for a user.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TypedTfaInfo {
|
||||
#[serde(rename = "type")]
|
||||
pub ty: TfaType,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub info: TfaInfo,
|
||||
}
|
||||
pub use crate::types::{TfaType, TfaUpdateInfo, TypedTfaInfo};
|
||||
|
||||
fn to_data(data: &TfaUserData) -> Vec<TypedTfaInfo> {
|
||||
let mut out = Vec::with_capacity(
|
||||
@ -258,44 +226,6 @@ pub fn list_tfa(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "api-types", api(
|
||||
properties: {
|
||||
recovery: {
|
||||
description: "A list of recovery codes as integers.",
|
||||
type: Array,
|
||||
items: {
|
||||
type: Integer,
|
||||
description: "A one-time usable recovery code entry.",
|
||||
},
|
||||
},
|
||||
},
|
||||
))]
|
||||
/// The result returned when adding TFA entries to a user.
|
||||
#[derive(Default, Serialize)]
|
||||
pub struct TfaUpdateInfo {
|
||||
/// The id if a newly added TFA entry.
|
||||
id: Option<String>,
|
||||
|
||||
/// When adding u2f entries, this contains a challenge the user must respond to in order to
|
||||
/// finish the registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
challenge: Option<String>,
|
||||
|
||||
/// When adding recovery codes, this contains the list of codes to be displayed to the user
|
||||
/// this one time.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
recovery: Vec<String>,
|
||||
}
|
||||
|
||||
impl TfaUpdateInfo {
|
||||
fn id(id: String) -> Self {
|
||||
Self {
|
||||
id: Some(id),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn need_description(description: Option<String>) -> Result<String, Error> {
|
||||
description.ok_or_else(|| format_err!("'description' is required for new entries"))
|
||||
}
|
||||
@ -389,7 +319,7 @@ fn add_totp(
|
||||
{
|
||||
bail!("failed to verify TOTP challenge");
|
||||
}
|
||||
Ok(TfaUpdateInfo::id(config.add_totp(
|
||||
Ok(TfaUpdateInfo::with_id(config.add_totp(
|
||||
userid,
|
||||
description,
|
||||
totp,
|
||||
@ -403,7 +333,7 @@ fn add_yubico(
|
||||
value: Option<String>,
|
||||
) -> Result<TfaUpdateInfo, Error> {
|
||||
let key = value.ok_or_else(|| format_err!("missing 'value' parameter for 'yubico' entry"))?;
|
||||
Ok(TfaUpdateInfo::id(config.add_yubico(
|
||||
Ok(TfaUpdateInfo::with_id(config.add_yubico(
|
||||
userid,
|
||||
description,
|
||||
key,
|
||||
@ -431,7 +361,7 @@ fn add_u2f<A: ?Sized + OpenUserChallengeData>(
|
||||
})?;
|
||||
config
|
||||
.u2f_registration_finish(access, userid, &challenge, &value)
|
||||
.map(TfaUpdateInfo::id)
|
||||
.map(TfaUpdateInfo::with_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -458,7 +388,7 @@ fn add_webauthn<A: ?Sized + OpenUserChallengeData>(
|
||||
})?;
|
||||
config
|
||||
.webauthn_registration_finish(access, userid, &challenge, &value, origin)
|
||||
.map(TfaUpdateInfo::id)
|
||||
.map(TfaUpdateInfo::with_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,6 @@ use webauthn_rs::{proto::UserVerificationPolicy, Webauthn};
|
||||
use crate::totp::Totp;
|
||||
use proxmox_uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "api-types")]
|
||||
use proxmox_schema::api;
|
||||
|
||||
mod serde_tools;
|
||||
|
||||
mod recovery;
|
||||
@ -35,6 +32,8 @@ pub use webauthn::{WebauthnConfig, WebauthnCredential};
|
||||
#[cfg(feature = "api-types")]
|
||||
pub use webauthn::WebauthnConfigUpdater;
|
||||
|
||||
pub use crate::types::TfaInfo;
|
||||
|
||||
use recovery::Recovery;
|
||||
use u2f::{U2fChallenge, U2fChallengeEntry, U2fRegistrationChallenge};
|
||||
use webauthn::{WebauthnAuthChallenge, WebauthnRegistrationChallenge};
|
||||
@ -825,47 +824,6 @@ impl<T> TfaEntry<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "api-types", api)]
|
||||
/// Over the API we only provide this part when querying a user's second factor list.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TfaInfo {
|
||||
/// The id used to reference this entry.
|
||||
pub id: String,
|
||||
|
||||
/// User chosen description for this entry.
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
pub description: String,
|
||||
|
||||
/// Creation time of this entry as unix epoch.
|
||||
pub created: i64,
|
||||
|
||||
/// Whether this TFA entry is currently enabled.
|
||||
#[serde(skip_serializing_if = "is_default_tfa_enable")]
|
||||
#[serde(default = "default_tfa_enable")]
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
impl TfaInfo {
|
||||
/// For recovery keys we have a fixed entry.
|
||||
pub fn recovery(created: i64) -> Self {
|
||||
Self {
|
||||
id: "recovery".to_string(),
|
||||
description: String::new(),
|
||||
enable: true,
|
||||
created,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn default_tfa_enable() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
const fn is_default_tfa_enable(v: &bool) -> bool {
|
||||
*v
|
||||
}
|
||||
|
||||
/// When sending a TFA challenge to the user, we include information about what kind of challenge
|
||||
/// the user may perform. If webauthn credentials are available, a webauthn challenge will be
|
||||
/// included.
|
||||
|
@ -5,3 +5,8 @@ pub mod totp;
|
||||
|
||||
#[cfg(feature = "api")]
|
||||
pub mod api;
|
||||
|
||||
#[cfg(feature = "types")]
|
||||
mod types;
|
||||
#[cfg(feature = "types")]
|
||||
pub use types::{TfaInfo, TfaType, TfaUpdateInfo, TypedTfaInfo};
|
||||
|
122
proxmox-tfa/src/types.rs
Normal file
122
proxmox-tfa/src/types.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "api-types")]
|
||||
use proxmox_schema::api;
|
||||
|
||||
#[cfg_attr(feature = "api-types", api)]
|
||||
/// A TFA entry type.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum TfaType {
|
||||
/// A TOTP entry type.
|
||||
Totp,
|
||||
/// A U2F token entry.
|
||||
U2f,
|
||||
/// A Webauthn token entry.
|
||||
Webauthn,
|
||||
/// Recovery tokens.
|
||||
Recovery,
|
||||
/// Yubico authentication entry.
|
||||
Yubico,
|
||||
}
|
||||
serde_plain::derive_display_from_serialize!(TfaType);
|
||||
serde_plain::derive_fromstr_from_deserialize!(TfaType);
|
||||
|
||||
#[cfg_attr(feature = "api-types", api)]
|
||||
/// Over the API we only provide this part when querying a user's second factor list.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TfaInfo {
|
||||
/// The id used to reference this entry.
|
||||
pub id: String,
|
||||
|
||||
/// User chosen description for this entry.
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub description: String,
|
||||
|
||||
/// Creation time of this entry as unix epoch.
|
||||
pub created: i64,
|
||||
|
||||
/// Whether this TFA entry is currently enabled.
|
||||
#[serde(skip_serializing_if = "is_default_tfa_enable")]
|
||||
#[serde(default = "default_tfa_enable")]
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
const fn default_tfa_enable() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
const fn is_default_tfa_enable(v: &bool) -> bool {
|
||||
*v
|
||||
}
|
||||
|
||||
impl TfaInfo {
|
||||
/// For recovery keys we have a fixed entry.
|
||||
pub fn recovery(created: i64) -> Self {
|
||||
Self {
|
||||
id: "recovery".to_string(),
|
||||
description: String::new(),
|
||||
enable: true,
|
||||
created,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "api-types",
|
||||
api(
|
||||
properties: {
|
||||
type: { type: TfaType },
|
||||
info: { type: TfaInfo },
|
||||
},
|
||||
)
|
||||
)]
|
||||
/// A TFA entry for a user.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TypedTfaInfo {
|
||||
#[serde(rename = "type")]
|
||||
pub ty: TfaType,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub info: TfaInfo,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "api-types", api(
|
||||
properties: {
|
||||
recovery: {
|
||||
description: "A list of recovery codes as integers.",
|
||||
type: Array,
|
||||
items: {
|
||||
type: Integer,
|
||||
description: "A one-time usable recovery code entry.",
|
||||
},
|
||||
},
|
||||
},
|
||||
))]
|
||||
/// The result returned when adding TFA entries to a user.
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct TfaUpdateInfo {
|
||||
/// The id if a newly added TFA entry.
|
||||
pub id: Option<String>,
|
||||
|
||||
/// When adding u2f entries, this contains a challenge the user must respond to in order to
|
||||
/// finish the registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub challenge: Option<String>,
|
||||
|
||||
/// When adding recovery codes, this contains the list of codes to be displayed to the user
|
||||
/// this one time.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub recovery: Vec<String>,
|
||||
}
|
||||
|
||||
impl TfaUpdateInfo {
|
||||
pub(crate) fn with_id(id: String) -> Self {
|
||||
Self {
|
||||
id: Some(id),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user