bitwarden_auth/registration/
post_keys_for_user_password_registration.rs1use bitwarden_api_identity::models::RegisterFinishRequestModel;
4use bitwarden_core::{
5 OrganizationId, UserId,
6 key_management::{
7 MasterPasswordUnlockData, account_cryptographic_state::WrappedAccountCryptographicState,
8 },
9};
10use bitwarden_encoding::B64;
11use tracing::error;
12#[cfg(feature = "wasm")]
13use wasm_bindgen::prelude::*;
14
15use crate::registration::{RegistrationClient, RegistrationError};
16
17#[cfg_attr(
19 feature = "wasm",
20 derive(tsify::Tsify),
21 tsify(into_wasm_abi, from_wasm_abi)
22)]
23#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
24#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
25pub struct UserMasterPasswordRegistrationRequest {
26 pub email: String,
28 pub salt: String,
30 pub master_password: String,
32 pub master_password_hint: Option<String>,
34 pub email_verification_token: Option<String>,
36 pub sales_assisted_token: Option<String>,
38 pub organization_user_id: Option<OrganizationId>,
40 pub org_invite_token: Option<String>,
42 pub org_sponsored_free_family_plan_token: Option<String>,
44 pub accept_emergency_access_invite_token: Option<String>,
46 pub accept_emergency_access_id: Option<UserId>,
48 pub provider_invite_token: Option<String>,
50 pub provider_user_id: Option<UserId>,
52}
53
54#[cfg_attr(
56 feature = "wasm",
57 derive(tsify::Tsify),
58 tsify(into_wasm_abi, from_wasm_abi)
59)]
60#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
61#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
62pub struct UserMasterPasswordRegistrationResponse {
63 pub account_cryptographic_state: WrappedAccountCryptographicState,
65 pub master_password_unlock: MasterPasswordUnlockData,
67 pub user_key: B64,
69}
70
71#[cfg_attr(feature = "wasm", wasm_bindgen)]
72impl RegistrationClient {
73 pub async fn post_keys_for_user_password_registration(
76 &self,
77 request: UserMasterPasswordRegistrationRequest,
78 ) -> Result<UserMasterPasswordRegistrationResponse, RegistrationError> {
79 let client = &self.client.internal;
80 let identity_client = &client.get_api_configurations().identity_client;
81 internal_post_keys_for_user_password_registration(self, identity_client, request).await
82 }
83}
84
85async fn internal_post_keys_for_user_password_registration(
86 registration_client: &RegistrationClient,
87 identity_client: &bitwarden_api_identity::apis::ApiClient,
88 request: UserMasterPasswordRegistrationRequest,
89) -> Result<UserMasterPasswordRegistrationResponse, RegistrationError> {
90 let make_crypto_response = registration_client
91 .client
92 .crypto()
93 .make_user_password_registration(request.master_password, request.salt)
94 .map_err(|_| RegistrationError::Crypto)?;
95 let account_keys = Some(Box::new(
96 internal_account_keys_from_api_model(&make_crypto_response.account_keys_request)
97 .map_err(|_| RegistrationError::Crypto)?,
98 ));
99
100 let api_request = RegisterFinishRequestModel {
101 email: Some(request.email),
102 master_password_hint: request.master_password_hint,
103 master_password_unlock: Some(Box::new(
104 (&make_crypto_response.master_password_unlock_data).into(),
105 )),
106 master_password_authentication: Some(Box::new(
107 (&make_crypto_response.master_password_authentication_data).into(),
108 )),
109 account_keys,
110 email_verification_token: request.email_verification_token,
111 sales_assisted_token: request.sales_assisted_token,
112 organization_user_id: request.organization_user_id.map(Into::into),
113 org_invite_token: (request.org_invite_token),
114 org_sponsored_free_family_plan_token: (request.org_sponsored_free_family_plan_token),
115 accept_emergency_access_invite_token: (request.accept_emergency_access_invite_token),
116 accept_emergency_access_id: request.accept_emergency_access_id.map(Into::into),
117 provider_invite_token: (request.provider_invite_token),
118 provider_user_id: request.provider_user_id.map(Into::into),
119 kdf: None,
121 kdf_memory: None,
122 kdf_parallelism: None,
123 kdf_iterations: None,
124 master_password_hash: None,
125 user_symmetric_key: None,
126 user_asymmetric_keys: None,
127 };
128
129 identity_client
130 .accounts_api()
131 .post_register_finish(Some(api_request))
132 .await
133 .map_err(|e| {
134 error!("Failed to post account keys: {e:?}");
135 RegistrationError::Api
136 })?;
137
138 Ok(UserMasterPasswordRegistrationResponse {
139 account_cryptographic_state: make_crypto_response.account_cryptographic_state,
140 master_password_unlock: make_crypto_response.master_password_unlock_data,
141 user_key: make_crypto_response.user_key.to_encoded().to_vec().into(),
142 })
143}
144
145fn internal_account_keys_from_api_model(
146 input_model: &bitwarden_api_api::models::AccountKeysRequestModel,
147) -> Result<bitwarden_api_identity::models::AccountKeysRequestModel, RegistrationError> {
148 let public_key_encryption_key_pair =
149 input_model
150 .public_key_encryption_key_pair
151 .as_deref()
152 .map(|pair| {
153 Box::new(
154 bitwarden_api_identity::models::PublicKeyEncryptionKeyPairRequestModel {
155 wrapped_private_key: pair.wrapped_private_key.clone(),
156 public_key: pair.public_key.clone(),
157 signed_public_key: pair.signed_public_key.clone(),
158 },
159 )
160 });
161
162 let signature_key_pair = input_model.signature_key_pair.as_deref().map(|pair| {
163 Box::new(
164 bitwarden_api_identity::models::SignatureKeyPairRequestModel {
165 signature_algorithm: pair.signature_algorithm.clone(),
166 wrapped_signing_key: pair.wrapped_signing_key.clone(),
167 verifying_key: pair.verifying_key.clone(),
168 },
169 )
170 });
171
172 let security_state = input_model.security_state.as_deref().map(|state| {
173 Box::new(bitwarden_api_identity::models::SecurityStateModel {
174 security_state: state.security_state.clone(),
175 security_version: state.security_version,
176 })
177 });
178
179 let user_key_encrypted_account_private_key =
180 input_model.user_key_encrypted_account_private_key.clone();
181
182 let account_public_key = input_model.account_public_key.clone();
183
184 Ok(bitwarden_api_identity::models::AccountKeysRequestModel {
185 public_key_encryption_key_pair,
186 signature_key_pair,
187 security_state,
188 user_key_encrypted_account_private_key,
189 account_public_key,
190 })
191}
192
193#[cfg(test)]
194mod tests {
195 use bitwarden_api_identity::{
196 apis::ApiClient as IdentityApiClient, models::RegisterFinishResponseModel,
197 };
198 use bitwarden_core::Client;
199
200 use super::*;
201
202 #[tokio::test]
203 async fn test_post_user_password_registration_success() {
204 let client = Client::new(None);
205 let registration_client = RegistrationClient::new(client);
206
207 let test_email = "[email protected]";
208 let test_hint = "test hint";
209 let test_password = "test-password-123";
210
211 let identity_client = IdentityApiClient::new_mocked(|mock| {
212 mock.accounts_api
213 .expect_post_register_finish()
214 .once()
215 .withf(|body| {
216 if let Some(req) = body {
217 assert_eq!(req.email, Some(test_email.to_string()));
219 assert_eq!(req.master_password_hint, Some(test_hint.to_string()));
220
221 assert!(req.account_keys.is_some());
223 let account_keys = req.account_keys.as_ref().unwrap();
224 assert!(
225 account_keys
226 .user_key_encrypted_account_private_key
227 .is_some()
228 );
229 assert!(account_keys.account_public_key.is_some());
230 assert!(account_keys.public_key_encryption_key_pair.is_some());
231 let public_key_encryption_key_pair = account_keys
232 .public_key_encryption_key_pair
233 .as_ref()
234 .unwrap();
235 assert!(public_key_encryption_key_pair.public_key.is_some());
236 assert!(public_key_encryption_key_pair.signed_public_key.is_some());
237 assert!(public_key_encryption_key_pair.wrapped_private_key.is_some());
238 assert!(account_keys.signature_key_pair.is_some());
239 let signature_key_pair = account_keys.signature_key_pair.as_ref().unwrap();
240 assert_eq!(
241 signature_key_pair.signature_algorithm,
242 Some("mldsa44".to_string())
243 );
244 assert!(signature_key_pair.verifying_key.is_some());
245 assert!(signature_key_pair.wrapped_signing_key.is_some());
246 assert!(account_keys.security_state.is_some());
247 let security_state = account_keys.security_state.as_ref().unwrap();
248 assert!(security_state.security_state.is_some());
249 assert_eq!(security_state.security_version, 2);
250 assert!(req.master_password_unlock.is_some());
251 let master_password_unlock = req.master_password_unlock.as_ref().unwrap();
252 assert_eq!(master_password_unlock.salt, test_email.to_string());
253 assert_eq!(
254 master_password_unlock.kdf,
255 Box::new(bitwarden_api_identity::models::KdfRequestModel {
256 kdf_type: bitwarden_api_identity::models::KdfType::Argon2id,
257 iterations: 6,
258 memory: Some(32),
259 parallelism: Some(4),
260 })
261 );
262 assert!(req.master_password_authentication.is_some());
263 let master_password_authentication =
264 req.master_password_authentication.as_ref().unwrap();
265 assert_eq!(master_password_authentication.salt, test_email.to_string());
266 assert_eq!(
267 master_password_authentication.kdf,
268 Box::new(bitwarden_api_identity::models::KdfRequestModel {
269 kdf_type: bitwarden_api_identity::models::KdfType::Argon2id,
270 iterations: 6,
271 memory: Some(32),
272 parallelism: Some(4),
273 })
274 );
275
276 assert!(req.user_asymmetric_keys.is_none());
278 assert!(req.kdf.is_none());
279 assert!(req.kdf_iterations.is_none());
280 assert!(req.kdf_memory.is_none());
281 assert!(req.kdf_parallelism.is_none());
282
283 assert!(req.email_verification_token.is_none());
285 assert!(req.sales_assisted_token.is_none());
286 assert!(req.organization_user_id.is_none());
287 assert!(req.org_invite_token.is_none());
288 assert!(req.org_sponsored_free_family_plan_token.is_none());
289 assert!(req.accept_emergency_access_invite_token.is_none());
290 assert!(req.accept_emergency_access_id.is_none());
291 assert!(req.provider_invite_token.is_none());
292 assert!(req.provider_user_id.is_none());
293 true
294 } else {
295 false
296 }
297 })
298 .returning(move |_body| Ok(RegisterFinishResponseModel { object: None }));
299 });
300
301 let request = UserMasterPasswordRegistrationRequest {
302 email: test_email.to_string(),
303 salt: test_email.to_string(),
304 master_password: test_password.to_string(),
305 master_password_hint: Some(test_hint.to_string()),
306 email_verification_token: None,
307 sales_assisted_token: None,
308 organization_user_id: None,
309 org_invite_token: None,
310 org_sponsored_free_family_plan_token: None,
311 accept_emergency_access_invite_token: None,
312 accept_emergency_access_id: None,
313 provider_invite_token: None,
314 provider_user_id: None,
315 };
316
317 let result = internal_post_keys_for_user_password_registration(
318 ®istration_client,
319 &identity_client,
320 request,
321 )
322 .await;
323
324 assert!(result.is_ok());
325
326 if let IdentityApiClient::Mock(mut mock) = identity_client {
328 mock.accounts_api.checkpoint();
329 }
330 }
331
332 #[tokio::test]
333 async fn test_post_user_password_registration_failure() {
334 let client = Client::new(None);
335 let registration_client = RegistrationClient::new(client);
336
337 let test_email = "[email protected]";
338 let test_hint = "test hint";
339 let test_password = "test-password-123";
340
341 let identity_client = IdentityApiClient::new_mocked(|mock| {
342 mock.accounts_api
343 .expect_post_register_finish()
344 .once()
345 .returning(move |_body| {
346 Err(serde_json::Error::io(std::io::Error::other("API error")).into())
347 });
348 });
349
350 let request = UserMasterPasswordRegistrationRequest {
351 email: test_email.to_string(),
352 salt: test_email.to_string(),
353 master_password: test_password.to_string(),
354 master_password_hint: Some(test_hint.to_string()),
355 email_verification_token: None,
356 sales_assisted_token: None,
357 organization_user_id: None,
358 org_invite_token: None,
359 org_sponsored_free_family_plan_token: None,
360 accept_emergency_access_invite_token: None,
361 accept_emergency_access_id: None,
362 provider_invite_token: None,
363 provider_user_id: None,
364 };
365
366 let result = internal_post_keys_for_user_password_registration(
367 ®istration_client,
368 &identity_client,
369 request,
370 )
371 .await;
372
373 assert!(result.is_err());
374 assert!(matches!(result.unwrap_err(), RegistrationError::Api));
375
376 if let IdentityApiClient::Mock(mut mock) = identity_client {
378 mock.accounts_api.checkpoint();
379 }
380 }
381}