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