1use std::collections::HashMap;
16
17use serde::{Deserialize, Serialize};
18
19use crate::client::{Client, PostBody};
20use crate::error::Result;
21use crate::serde_util::null_to_default;
22
23pub const MFA_TYPE_EMAIL: &str = "email";
25pub const MFA_TYPE_SMS: &str = "sms";
27pub const MFA_TYPE_APP: &str = "app";
29
30#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase", default)]
32pub struct MfaRequest {
33 pub owner: String,
34 pub mfa_type: String,
35 pub name: String,
36 #[serde(skip_serializing_if = "String::is_empty")]
37 pub secret: String,
38 #[serde(rename = "recoveryCodes", skip_serializing_if = "String::is_empty")]
39 pub recovery_code: String,
40}
41
42#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "camelCase", default)]
45pub struct MfaInitiateData {
46 pub enabled: bool,
47 pub is_preferred: bool,
48 pub mfa_remember_in_hours: i32,
49 pub mfa_type: String,
50 #[serde(deserialize_with = "null_to_default")]
51 pub recovery_codes: Vec<String>,
52 pub secret: String,
53 pub url: String,
54}
55
56impl Client {
57 pub async fn initiate_mfa(
60 &self,
61 owner: &str,
62 mfa_type: &str,
63 name: &str,
64 ) -> Result<MfaInitiateData> {
65 let request = MfaRequest {
66 owner: owner.to_string(),
67 mfa_type: mfa_type.to_string(),
68 name: name.to_string(),
69 ..Default::default()
70 };
71
72 let response = self
73 .do_post("mfa/setup/initiate", &[], to_form(&request)?)
74 .await?;
75
76 if response.data.is_null() {
77 return Ok(MfaInitiateData::default());
78 }
79
80 Ok(serde_json::from_value(response.data)?)
81 }
82
83 pub async fn verify_mfa(
85 &self,
86 owner: &str,
87 mfa_type: &str,
88 name: &str,
89 secret: &str,
90 passcode: &str,
91 ) -> Result<bool> {
92 let fields = HashMap::from([
93 ("owner".to_string(), owner.to_string()),
94 ("mfaType".to_string(), mfa_type.to_string()),
95 ("name".to_string(), name.to_string()),
96 ("secret".to_string(), secret.to_string()),
97 ("passcode".to_string(), passcode.to_string()),
98 ]);
99
100 let response = self
101 .do_post("mfa/setup/verify", &[], PostBody::Form(fields))
102 .await?;
103
104 Ok(response.status == "ok")
105 }
106
107 pub async fn enable_mfa(
109 &self,
110 owner: &str,
111 mfa_type: &str,
112 name: &str,
113 secret: &str,
114 recovery_code: &str,
115 ) -> Result<bool> {
116 let request = MfaRequest {
117 owner: owner.to_string(),
118 mfa_type: mfa_type.to_string(),
119 name: name.to_string(),
120 secret: secret.to_string(),
121 recovery_code: recovery_code.to_string(),
122 };
123
124 let response = self
125 .do_post("mfa/setup/enable", &[], to_form(&request)?)
126 .await?;
127
128 Ok(response.status == "ok")
129 }
130
131 pub async fn set_preferred_mfa(
133 &self,
134 owner: &str,
135 mfa_type: &str,
136 name: &str,
137 secret: &str,
138 ) -> Result<()> {
139 let request = MfaRequest {
140 owner: owner.to_string(),
141 mfa_type: mfa_type.to_string(),
142 name: name.to_string(),
143 secret: secret.to_string(),
144 ..Default::default()
145 };
146
147 self.do_post("set-preferred-mfa", &[], to_form(&request)?)
148 .await?;
149
150 Ok(())
151 }
152
153 pub async fn delete_mfa(&self, owner: &str, name: &str) -> Result<()> {
155 self.do_post(
156 "delete-mfa",
157 &[("owner", owner), ("name", name)],
158 PostBody::Raw(Vec::new()),
159 )
160 .await?;
161
162 Ok(())
163 }
164}
165
166fn to_form(request: &MfaRequest) -> Result<PostBody<'static>> {
168 let fields = serde_json::from_value::<HashMap<String, String>>(serde_json::to_value(request)?)?;
169 Ok(PostBody::Form(fields))
170}