Skip to main content

bitwarden_auth/
registration.rs

1//! Client for account registration and cryptography initialization related API methods.
2//! It is used both for the initial registration request in the case of password registrations,
3//! and for cryptography initialization for a jit provisioned user. After a method
4//! on this client is called, the user account should have initialized account keys, an
5//! authentication method such as SSO or master password, and a decryption method such as
6//! key-connector, TDE, or master password.
7
8use bitwarden_api_api::models::{
9    DeviceKeysRequestModel, KeysRequestModel, OrganizationUserResetPasswordEnrollmentRequestModel,
10    SetInitialPasswordRequestModel, SetKeyConnectorKeyRequestModel,
11};
12use bitwarden_core::{
13    Client, OrganizationId, UserId,
14    key_management::{
15        MasterPasswordUnlockData, account_cryptographic_state::WrappedAccountCryptographicState,
16    },
17};
18use bitwarden_crypto::EncString;
19use bitwarden_encoding::B64;
20use bitwarden_error::bitwarden_error;
21use thiserror::Error;
22use tracing::{error, info};
23#[cfg(feature = "wasm")]
24use wasm_bindgen::prelude::*;
25
26/// Request parameters for TDE (Trusted Device Encryption) registration.
27#[cfg_attr(
28    feature = "wasm",
29    derive(tsify::Tsify),
30    tsify(into_wasm_abi, from_wasm_abi)
31)]
32#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
33#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
34pub struct TdeRegistrationRequest {
35    /// Organization ID to enroll in
36    pub org_id: OrganizationId,
37    /// Organization's public key for encrypting the reset password key. This should be verified by
38    /// the client and not verifying may compromise the security of the user's account.
39    pub org_public_key: B64,
40    /// User ID for the account being initialized
41    pub user_id: UserId,
42    /// Device identifier for TDE enrollment
43    pub device_identifier: String,
44    /// Whether to trust this device for TDE
45    pub trust_device: bool,
46}
47
48/// Request parameters for SSO JIT master password registration.
49#[cfg_attr(
50    feature = "wasm",
51    derive(tsify::Tsify),
52    tsify(into_wasm_abi, from_wasm_abi)
53)]
54#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
55#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
56pub struct JitMasterPasswordRegistrationRequest {
57    /// Organization ID to enroll in
58    pub org_id: OrganizationId,
59    /// Organization's public key for encrypting the reset password key. This should be verified by
60    /// the client and not verifying may compromise the security of the user's account.
61    pub org_public_key: B64,
62    /// Organization SSO identifier
63    pub organization_sso_identifier: String,
64    /// User ID for the account being initialized
65    pub user_id: UserId,
66    /// Salt for master password hashing, usually email
67    pub salt: String,
68    /// Master password for the account
69    pub master_password: String,
70    /// Optional hint for the master password
71    pub master_password_hint: Option<String>,
72    /// Should enroll user into admin password reset
73    pub reset_password_enroll: bool,
74}
75
76/// Client for initializing a user account.
77#[derive(Clone)]
78#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
79#[cfg_attr(feature = "wasm", wasm_bindgen)]
80pub struct RegistrationClient {
81    #[allow(dead_code)]
82    pub(crate) client: Client,
83}
84
85impl RegistrationClient {
86    pub(crate) fn new(client: Client) -> Self {
87        Self { client }
88    }
89}
90
91#[cfg_attr(feature = "uniffi", uniffi::export)]
92#[cfg_attr(feature = "wasm", wasm_bindgen)]
93impl RegistrationClient {
94    /// Initializes a new cryptographic state for a user and posts it to the server; enrolls in
95    /// admin password reset and finally enrolls the user to TDE unlock.
96    pub async fn post_keys_for_tde_registration(
97        &self,
98        request: TdeRegistrationRequest,
99    ) -> Result<TdeRegistrationResponse, RegistrationError> {
100        let client = &self.client.internal;
101        let api_client = &client.get_api_configurations().api_client;
102        internal_post_keys_for_tde_registration(self, api_client, request).await
103    }
104
105    /// Initializes a new cryptographic state for a user and posts it to the server; enrolls the
106    /// user to key connector unlock.
107    pub async fn post_keys_for_key_connector_registration(
108        &self,
109        key_connector_url: String,
110        sso_org_identifier: String,
111    ) -> Result<KeyConnectorRegistrationResult, RegistrationError> {
112        let client = &self.client.internal;
113        let configuration = &client.get_api_configurations();
114        let key_connector_client = client.get_key_connector_client(key_connector_url);
115
116        internal_post_keys_for_key_connector_registration(
117            self,
118            &configuration.api_client,
119            &key_connector_client,
120            sso_org_identifier,
121        )
122        .await
123    }
124
125    /// Initializes a new cryptographic state for a user and posts it to the server;
126    /// enrolls the user to master password unlock.
127    pub async fn post_keys_for_jit_password_registration(
128        &self,
129        request: JitMasterPasswordRegistrationRequest,
130    ) -> Result<JitMasterPasswordRegistrationResponse, RegistrationError> {
131        let client = &self.client.internal;
132        let api_client = &client.get_api_configurations().api_client;
133        internal_post_keys_for_jit_password_registration(self, api_client, request).await
134    }
135}
136
137async fn internal_post_keys_for_tde_registration(
138    registration_client: &RegistrationClient,
139    api_client: &bitwarden_api_api::apis::ApiClient,
140    request: TdeRegistrationRequest,
141) -> Result<TdeRegistrationResponse, RegistrationError> {
142    // First call crypto API to get all keys
143    info!("Initializing account cryptography");
144    let tde_registration_crypto_result = registration_client
145        .client
146        .crypto()
147        .make_user_tde_registration(request.org_public_key.clone())
148        .map_err(|_| RegistrationError::Crypto)?;
149
150    // Post the generated keys to the API here. The user now has keys and is "registered", but
151    // has no unlock method.
152    let keys_request = KeysRequestModel {
153        account_keys: Some(Box::new(
154            tde_registration_crypto_result.account_keys_request.clone(),
155        )),
156        // Note: This property is deprecated and will be removed
157        public_key: tde_registration_crypto_result
158            .account_keys_request
159            .account_public_key
160            .ok_or(RegistrationError::Crypto)?,
161        // Note: This property is deprecated and will be removed
162        encrypted_private_key: tde_registration_crypto_result
163            .account_keys_request
164            .user_key_encrypted_account_private_key
165            .ok_or(RegistrationError::Crypto)?,
166    };
167    info!("Posting user account cryptographic state to server");
168    api_client
169        .accounts_api()
170        .post_keys(Some(keys_request))
171        .await
172        .map_err(|e| {
173            tracing::error!("Failed to post account keys: {e:?}");
174            RegistrationError::Api
175        })?;
176
177    // Next, enroll the user for reset password using the reset password key generated above.
178    info!("Enrolling into admin account recovery");
179    api_client
180        .organization_users_api()
181        .put_reset_password_enrollment(
182            request.org_id.into(),
183            request.user_id.into(),
184            Some(OrganizationUserResetPasswordEnrollmentRequestModel {
185                reset_password_key: Some(
186                    tde_registration_crypto_result
187                        .reset_password_key
188                        .to_string(),
189                ),
190                master_password_hash: None,
191            }),
192        )
193        .await
194        .map_err(|e| {
195            tracing::error!("Failed to enroll for reset password: {e:?}");
196            RegistrationError::Api
197        })?;
198
199    if request.trust_device {
200        // Next, enroll the user for TDE unlock
201        info!("Enrolling into trusted device decryption");
202        api_client
203            .devices_api()
204            .put_keys(
205                request.device_identifier.as_str(),
206                Some(DeviceKeysRequestModel::new(
207                    tde_registration_crypto_result
208                        .trusted_device_keys
209                        .protected_user_key
210                        .to_string(),
211                    tde_registration_crypto_result
212                        .trusted_device_keys
213                        .protected_device_private_key
214                        .to_string(),
215                    tde_registration_crypto_result
216                        .trusted_device_keys
217                        .protected_device_public_key
218                        .to_string(),
219                )),
220            )
221            .await
222            .map_err(|e| {
223                tracing::error!("Failed to enroll device for TDE: {e:?}");
224                RegistrationError::Api
225            })?;
226    }
227
228    info!("User initialized!");
229    // Note: This passing out of state and keys is temporary. Once SDK state management is more
230    // mature, the account cryptographic state and keys should be set directly here.
231    Ok(TdeRegistrationResponse {
232        account_cryptographic_state: tde_registration_crypto_result.account_cryptographic_state,
233        device_key: tde_registration_crypto_result
234            .trusted_device_keys
235            .device_key,
236        user_key: tde_registration_crypto_result
237            .user_key
238            .to_encoded()
239            .to_vec()
240            .into(),
241    })
242}
243
244/// Result of TDE registration process.
245#[cfg_attr(
246    feature = "wasm",
247    derive(tsify::Tsify),
248    tsify(into_wasm_abi, from_wasm_abi)
249)]
250#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
251#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
252pub struct TdeRegistrationResponse {
253    /// The account cryptographic state of the user
254    pub account_cryptographic_state: WrappedAccountCryptographicState,
255    /// The device key
256    pub device_key: B64,
257    /// The decrypted user key. This can be used to get the consuming client to an unlocked state.
258    pub user_key: B64,
259}
260
261async fn internal_post_keys_for_key_connector_registration(
262    registration_client: &RegistrationClient,
263    api_client: &bitwarden_api_api::apis::ApiClient,
264    key_connector_api_client: &bitwarden_api_key_connector::apis::ApiClient,
265    sso_org_identifier: String,
266) -> Result<KeyConnectorRegistrationResult, RegistrationError> {
267    // First call crypto API to get all keys
268    info!("Initializing account cryptography");
269    let registration_crypto_result = registration_client
270        .client
271        .crypto()
272        .make_user_key_connector_registration()
273        .map_err(|_| RegistrationError::Crypto)?;
274
275    info!("Posting key connector key to key connector server");
276    let key_connector_key: B64 = registration_crypto_result.key_connector_key.into();
277    post_key_to_key_connector(key_connector_api_client, &key_connector_key).await?;
278
279    info!("Posting user account cryptographic state to server");
280    let request = SetKeyConnectorKeyRequestModel {
281        key_connector_key_wrapped_user_key: Some(
282            registration_crypto_result
283                .key_connector_key_wrapped_user_key
284                .to_string(),
285        ),
286        account_keys: Some(Box::new(registration_crypto_result.account_keys_request)),
287        ..SetKeyConnectorKeyRequestModel::new(sso_org_identifier.to_string())
288    };
289    api_client
290        .accounts_key_management_api()
291        .post_set_key_connector_key(Some(request))
292        .await
293        .map_err(|e| {
294            error!("Failed to post account cryptographic state to server: {e:?}");
295            RegistrationError::Api
296        })?;
297
298    info!("User initialized!");
299    // Note: This passing out of state and keys is temporary. Once SDK state management is more
300    // mature, the account cryptographic state and keys should be set directly here.
301    Ok(KeyConnectorRegistrationResult {
302        account_cryptographic_state: registration_crypto_result.account_cryptographic_state,
303        key_connector_key,
304        key_connector_key_wrapped_user_key: registration_crypto_result
305            .key_connector_key_wrapped_user_key,
306        user_key: registration_crypto_result.user_key.to_encoded().into(),
307    })
308}
309
310async fn post_key_to_key_connector(
311    key_connector_api_client: &bitwarden_api_key_connector::apis::ApiClient,
312    key_connector_key: &B64,
313) -> Result<(), RegistrationError> {
314    let request =
315        bitwarden_api_key_connector::models::user_key_request_model::UserKeyKeyRequestModel {
316            key: key_connector_key.to_string(),
317        };
318
319    let result = if key_connector_api_client
320        .user_keys_api()
321        .get_user_key()
322        .await
323        .is_ok()
324    {
325        info!("User's key connector key exists, updating");
326        key_connector_api_client
327            .user_keys_api()
328            .put_user_key(request)
329            .await
330    } else {
331        info!("User's key connector key does not exist, creating");
332        key_connector_api_client
333            .user_keys_api()
334            .post_user_key(request)
335            .await
336    };
337
338    result.map_err(|e| {
339        error!("Failed to post key connector key to key connector server: {e:?}");
340        RegistrationError::KeyConnectorApi
341    })
342}
343
344/// Result of Key Connector registration process.
345#[cfg_attr(
346    feature = "wasm",
347    derive(tsify::Tsify),
348    tsify(into_wasm_abi, from_wasm_abi)
349)]
350#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
351#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
352pub struct KeyConnectorRegistrationResult {
353    /// The account cryptographic state of the user.
354    pub account_cryptographic_state: WrappedAccountCryptographicState,
355    /// The key connector key used for unlocking.
356    pub key_connector_key: B64,
357    /// The encrypted user key, wrapped with the key connector key.
358    pub key_connector_key_wrapped_user_key: EncString,
359    /// The decrypted user key. This can be used to get the consuming client to an unlocked state.
360    pub user_key: B64,
361}
362
363async fn internal_post_keys_for_jit_password_registration(
364    registration_client: &RegistrationClient,
365    api_client: &bitwarden_api_api::apis::ApiClient,
366    request: JitMasterPasswordRegistrationRequest,
367) -> Result<JitMasterPasswordRegistrationResponse, RegistrationError> {
368    // First call crypto API to get all keys
369    info!("Initializing account cryptography");
370    let registration_crypto_result = registration_client
371        .client
372        .crypto()
373        .make_user_jit_master_password_registration(
374            request.master_password,
375            request.salt,
376            request.org_public_key,
377        )
378        .map_err(|_| RegistrationError::Crypto)?;
379
380    // Post the generated keys to the API here. The user now has keys and is "registered", but
381    // has no unlock method.
382    let api_request = SetInitialPasswordRequestModel {
383        account_keys: Some(Box::new(
384            registration_crypto_result.account_keys_request.clone(),
385        )),
386        master_password_unlock: Some(Box::new(
387            (&registration_crypto_result.master_password_unlock_data).into(),
388        )),
389        master_password_authentication: Some(Box::new(
390            (&registration_crypto_result.master_password_authentication_data).into(),
391        )),
392        master_password_hint: request.master_password_hint,
393        org_identifier: request.organization_sso_identifier,
394        // TODO Deprecated fields below, to be removed with https://bitwarden.atlassian.net/browse/PM-27327
395        kdf_parallelism: None,
396        master_password_hash: None,
397        key: None,
398        keys: None,
399        kdf: None,
400        kdf_iterations: None,
401        kdf_memory: None,
402    };
403    info!("Posting user account cryptographic state to server");
404    api_client
405        .accounts_api()
406        .post_set_password(Some(api_request))
407        .await
408        .map_err(|e| {
409            error!("Failed to post account keys: {e:?}");
410            RegistrationError::Api
411        })?;
412
413    // Enroll the user for reset password using the reset password key generated above.
414    if request.reset_password_enroll {
415        info!("Enrolling into admin account recovery");
416        api_client
417            .organization_users_api()
418            .put_reset_password_enrollment(
419                request.org_id.into(),
420                request.user_id.into(),
421                Some(OrganizationUserResetPasswordEnrollmentRequestModel {
422                    reset_password_key: Some(
423                        registration_crypto_result.reset_password_key.to_string(),
424                    ),
425                    master_password_hash: Some(
426                        registration_crypto_result
427                            .master_password_authentication_data
428                            .master_password_authentication_hash
429                            .to_string(),
430                    ),
431                }),
432            )
433            .await
434            .map_err(|e| {
435                error!("Failed to enroll for reset password: {e:?}");
436                RegistrationError::Api
437            })?;
438    }
439
440    info!("User initialized!");
441    // Note: This passing out of state and keys is temporary. Once SDK state management is more
442    // mature, the account cryptographic state and keys should be set directly here.
443    Ok(JitMasterPasswordRegistrationResponse {
444        account_cryptographic_state: registration_crypto_result.account_cryptographic_state,
445        master_password_unlock: registration_crypto_result.master_password_unlock_data,
446        user_key: registration_crypto_result
447            .user_key
448            .to_encoded()
449            .to_vec()
450            .into(),
451    })
452}
453
454/// Result of JIT master password registration process.
455#[cfg_attr(
456    feature = "wasm",
457    derive(tsify::Tsify),
458    tsify(into_wasm_abi, from_wasm_abi)
459)]
460#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
461#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
462pub struct JitMasterPasswordRegistrationResponse {
463    /// The account cryptographic state of the user
464    pub account_cryptographic_state: WrappedAccountCryptographicState,
465    /// The master password unlock data
466    pub master_password_unlock: MasterPasswordUnlockData,
467    /// The decrypted user key.
468    pub user_key: B64,
469}
470
471/// Errors that can occur during user registration.
472#[derive(Debug, Error)]
473#[bitwarden_error(flat)]
474pub enum RegistrationError {
475    /// Key Connector API call failed.
476    #[error("Key Connector Api call failed")]
477    KeyConnectorApi,
478    /// API call failed.
479    #[error("Api call failed")]
480    Api,
481    /// Cryptography initialization failed.
482    #[error("Cryptography initialization failed")]
483    Crypto,
484}
485
486#[cfg(test)]
487mod tests {
488    use std::num::NonZeroU32;
489
490    use bitwarden_api_api::{
491        apis::ApiClient,
492        models::{DeviceResponseModel, KdfRequestModel, KdfType, KeysResponseModel},
493    };
494    use bitwarden_core::Client;
495    use bitwarden_crypto::Kdf;
496
497    use super::*;
498
499    const TEST_USER_ID: &str = "060000fb-0922-4dd3-b170-6e15cb5df8c8";
500    const TEST_ORG_ID: &str = "1bc9ac1e-f5aa-45f2-94bf-b181009709b8";
501    const TEST_DEVICE_ID: &str = "test-device-id";
502    const TEST_SSO_ORG_IDENTIFIER: &str = "test-org";
503
504    const TEST_ORG_PUBLIC_KEY: &[u8] = &[
505        48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0,
506        48, 130, 1, 10, 2, 130, 1, 1, 0, 173, 4, 54, 63, 125, 12, 254, 38, 115, 34, 95, 164, 148,
507        115, 86, 140, 129, 74, 19, 70, 212, 212, 130, 163, 105, 249, 101, 120, 154, 46, 194, 250,
508        229, 242, 156, 67, 109, 179, 187, 134, 59, 235, 60, 107, 144, 163, 35, 22, 109, 230, 134,
509        243, 44, 243, 79, 84, 76, 11, 64, 56, 236, 167, 98, 26, 30, 213, 143, 105, 52, 92, 129, 92,
510        88, 22, 115, 135, 63, 215, 79, 8, 11, 183, 124, 10, 73, 231, 170, 110, 210, 178, 22, 100,
511        76, 75, 118, 202, 252, 204, 67, 204, 152, 6, 244, 208, 161, 146, 103, 225, 233, 239, 88,
512        195, 88, 150, 230, 111, 62, 142, 12, 157, 184, 155, 34, 84, 237, 111, 11, 97, 56, 152, 130,
513        14, 72, 123, 140, 47, 137, 5, 97, 166, 4, 147, 111, 23, 65, 78, 63, 208, 198, 50, 161, 39,
514        80, 143, 100, 194, 37, 252, 194, 53, 207, 166, 168, 250, 165, 121, 9, 207, 90, 36, 213,
515        211, 84, 255, 14, 205, 114, 135, 217, 137, 105, 232, 58, 169, 222, 10, 13, 138, 203, 16,
516        12, 122, 72, 227, 95, 160, 111, 54, 200, 198, 143, 156, 15, 143, 196, 50, 150, 204, 144,
517        255, 162, 248, 50, 28, 47, 66, 9, 83, 158, 67, 9, 50, 147, 174, 147, 200, 199, 238, 190,
518        248, 60, 114, 218, 32, 209, 120, 218, 17, 234, 14, 128, 192, 166, 33, 60, 73, 227, 108,
519        201, 41, 160, 81, 133, 171, 205, 221, 2, 3, 1, 0, 1,
520    ];
521
522    #[tokio::test]
523    async fn test_post_keys_for_tde_registration_success() {
524        let client = Client::new(None);
525        let registration_client = RegistrationClient::new(client);
526
527        let api_client = ApiClient::new_mocked(|mock| {
528            mock.accounts_api
529                .expect_post_keys()
530                .once()
531                .returning(move |_body| {
532                    Ok(KeysResponseModel {
533                        object: None,
534                        key: None,
535                        public_key: None,
536                        private_key: None,
537                        account_keys: None,
538                    })
539                });
540            mock.organization_users_api
541                .expect_put_reset_password_enrollment()
542                .once()
543                .returning(move |_org_id, _user_id, _body| Ok(()));
544            mock.devices_api
545                .expect_put_keys()
546                .once()
547                .returning(move |_device_id, _body| {
548                    Ok(DeviceResponseModel {
549                        object: None,
550                        id: None,
551                        name: None,
552                        r#type: None,
553                        identifier: None,
554                        creation_date: None,
555                        is_trusted: None,
556                        encrypted_user_key: None,
557                        encrypted_public_key: None,
558                    })
559                });
560        });
561
562        let request = TdeRegistrationRequest {
563            org_id: TEST_ORG_ID.parse().unwrap(),
564            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
565            user_id: TEST_USER_ID.parse().unwrap(),
566            device_identifier: TEST_DEVICE_ID.to_string(),
567            trust_device: true,
568        };
569
570        let result =
571            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
572                .await;
573
574        assert!(result.is_ok());
575        // Assert that the mock expectations were met
576        if let ApiClient::Mock(mut mock) = api_client {
577            mock.accounts_api.checkpoint();
578            mock.organization_users_api.checkpoint();
579            mock.devices_api.checkpoint();
580        }
581    }
582
583    #[tokio::test]
584    async fn test_post_keys_for_tde_registration_trust_device_false() {
585        let client = Client::new(None);
586        let registration_client = RegistrationClient::new(client);
587
588        let api_client = ApiClient::new_mocked(|mock| {
589            mock.accounts_api
590                .expect_post_keys()
591                .once()
592                .returning(move |_body| {
593                    Ok(KeysResponseModel {
594                        object: None,
595                        key: None,
596                        public_key: None,
597                        private_key: None,
598                        account_keys: None,
599                    })
600                });
601            mock.organization_users_api
602                .expect_put_reset_password_enrollment()
603                .once()
604                .returning(move |_org_id, _user_id, _body| Ok(()));
605            // Explicitly expect that put_keys is never called when trust_device is false
606            mock.devices_api.expect_put_keys().never();
607        });
608
609        let request = TdeRegistrationRequest {
610            org_id: TEST_ORG_ID.parse().unwrap(),
611            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
612            user_id: TEST_USER_ID.parse().unwrap(),
613            device_identifier: TEST_DEVICE_ID.to_string(),
614            trust_device: false, // trust_device is false
615        };
616
617        let result =
618            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
619                .await;
620
621        assert!(result.is_ok());
622        // Assert that the mock expectations were met (put_keys should not have been called)
623        if let ApiClient::Mock(mut mock) = api_client {
624            mock.accounts_api.checkpoint();
625            mock.organization_users_api.checkpoint();
626            mock.devices_api.checkpoint();
627        }
628    }
629
630    #[tokio::test]
631    async fn test_post_keys_for_tde_registration_post_keys_failure() {
632        let client = Client::new(None);
633        let registration_client = RegistrationClient::new(client);
634
635        let api_client = ApiClient::new_mocked(|mock| {
636            mock.accounts_api
637                .expect_post_keys()
638                .once()
639                .returning(move |_body| {
640                    Err(bitwarden_api_api::apis::Error::Serde(
641                        serde_json::Error::io(std::io::Error::other("API error")),
642                    ))
643                });
644            // Subsequent API calls should not be made if post_keys fails
645            mock.organization_users_api
646                .expect_put_reset_password_enrollment()
647                .never();
648            mock.devices_api.expect_put_keys().never();
649        });
650
651        let request = TdeRegistrationRequest {
652            org_id: TEST_ORG_ID.parse().unwrap(),
653            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
654            user_id: TEST_USER_ID.parse().unwrap(),
655            device_identifier: TEST_DEVICE_ID.to_string(),
656            trust_device: true,
657        };
658
659        let result =
660            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
661                .await;
662
663        assert!(result.is_err());
664        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
665
666        // Assert that the mock expectations were met
667        if let ApiClient::Mock(mut mock) = api_client {
668            mock.accounts_api.checkpoint();
669            mock.organization_users_api.checkpoint();
670            mock.devices_api.checkpoint();
671        }
672    }
673
674    #[tokio::test]
675    async fn test_post_keys_for_tde_registration_reset_password_enrollment_failure() {
676        let client = Client::new(None);
677        let registration_client = RegistrationClient::new(client);
678
679        let api_client = ApiClient::new_mocked(|mock| {
680            mock.accounts_api
681                .expect_post_keys()
682                .once()
683                .returning(move |_body| {
684                    Ok(KeysResponseModel {
685                        object: None,
686                        key: None,
687                        public_key: None,
688                        private_key: None,
689                        account_keys: None,
690                    })
691                });
692            mock.organization_users_api
693                .expect_put_reset_password_enrollment()
694                .once()
695                .returning(move |_org_id, _user_id, _body| {
696                    Err(bitwarden_api_api::apis::Error::Serde(
697                        serde_json::Error::io(std::io::Error::other("API error")),
698                    ))
699                });
700            // Device key enrollment should not be made if reset password enrollment fails
701            mock.devices_api.expect_put_keys().never();
702        });
703
704        let request = TdeRegistrationRequest {
705            org_id: TEST_ORG_ID.parse().unwrap(),
706            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
707            user_id: TEST_USER_ID.parse().unwrap(),
708            device_identifier: TEST_DEVICE_ID.to_string(),
709            trust_device: true,
710        };
711
712        let result =
713            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
714                .await;
715
716        assert!(result.is_err());
717        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
718
719        // Assert that the mock expectations were met
720        if let ApiClient::Mock(mut mock) = api_client {
721            mock.accounts_api.checkpoint();
722            mock.organization_users_api.checkpoint();
723            mock.devices_api.checkpoint();
724        }
725    }
726
727    #[tokio::test]
728    async fn test_post_keys_for_tde_registration_device_keys_failure() {
729        let client = Client::new(None);
730        let registration_client = RegistrationClient::new(client);
731
732        let api_client = ApiClient::new_mocked(|mock| {
733            mock.accounts_api
734                .expect_post_keys()
735                .once()
736                .returning(move |_body| {
737                    Ok(KeysResponseModel {
738                        object: None,
739                        key: None,
740                        public_key: None,
741                        private_key: None,
742                        account_keys: None,
743                    })
744                });
745            mock.organization_users_api
746                .expect_put_reset_password_enrollment()
747                .once()
748                .returning(move |_org_id, _user_id, _body| Ok(()));
749            mock.devices_api
750                .expect_put_keys()
751                .once()
752                .returning(move |_device_id, _body| {
753                    Err(bitwarden_api_api::apis::Error::Serde(
754                        serde_json::Error::io(std::io::Error::other("API error")),
755                    ))
756                });
757        });
758
759        let request = TdeRegistrationRequest {
760            org_id: TEST_ORG_ID.parse().unwrap(),
761            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
762            user_id: TEST_USER_ID.parse().unwrap(),
763            device_identifier: TEST_DEVICE_ID.to_string(),
764            trust_device: true, // trust_device is true, so device enrollment should be attempted
765        };
766
767        let result =
768            internal_post_keys_for_tde_registration(&registration_client, &api_client, request)
769                .await;
770
771        assert!(result.is_err());
772        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
773
774        // Assert that the mock expectations were met
775        if let ApiClient::Mock(mut mock) = api_client {
776            mock.accounts_api.checkpoint();
777            mock.organization_users_api.checkpoint();
778            mock.devices_api.checkpoint();
779        }
780    }
781
782    #[tokio::test]
783    async fn test_post_keys_for_key_connector_registration_success() {
784        let client = Client::new(None);
785        let registration_client = RegistrationClient::new(client);
786
787        let api_client = ApiClient::new_mocked(|mock| {
788            mock.accounts_key_management_api
789                .expect_post_set_key_connector_key()
790                .once()
791                .returning(move |_body| Ok(()));
792        });
793
794        let key_connector_api_client =
795            bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
796                mock.user_keys_api
797                    .expect_get_user_key()
798                    .once()
799                    .returning(move || {
800                        Err(bitwarden_api_key_connector::apis::Error::ResponseError(
801                            bitwarden_api_key_connector::apis::ResponseContent {
802                                status: reqwest::StatusCode::NOT_FOUND,
803                                content: "Not Found".to_string(),
804                            },
805                        ))
806                    });
807                mock.user_keys_api
808                    .expect_post_user_key()
809                    .once()
810                    .returning(move |_body| Ok(()));
811            });
812
813        let result = internal_post_keys_for_key_connector_registration(
814            &registration_client,
815            &api_client,
816            &key_connector_api_client,
817            TEST_SSO_ORG_IDENTIFIER.to_string(),
818        )
819        .await;
820        assert!(result.is_ok());
821
822        // Assert that the mock expectations were met
823        if let ApiClient::Mock(mut mock) = api_client {
824            mock.accounts_key_management_api.checkpoint();
825        }
826        if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
827            key_connector_api_client
828        {
829            mock.user_keys_api.checkpoint();
830        }
831    }
832
833    #[tokio::test]
834    async fn test_post_keys_for_key_connector_registration_key_connector_api_failure() {
835        let client = Client::new(None);
836        let registration_client = RegistrationClient::new(client);
837
838        let api_client = ApiClient::new_mocked(|mock| {
839            // Should not be called if Key Connector API fails
840            mock.accounts_key_management_api
841                .expect_post_set_key_connector_key()
842                .never();
843        });
844
845        let key_connector_api_client =
846            bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
847                mock.user_keys_api
848                    .expect_get_user_key()
849                    .once()
850                    .returning(move || {
851                        Err(bitwarden_api_key_connector::apis::Error::ResponseError(
852                            bitwarden_api_key_connector::apis::ResponseContent {
853                                status: reqwest::StatusCode::NOT_FOUND,
854                                content: "Not Found".to_string(),
855                            },
856                        ))
857                    });
858                mock.user_keys_api
859                    .expect_post_user_key()
860                    .once()
861                    .returning(move |_body| {
862                        Err(bitwarden_api_key_connector::apis::Error::Serde(
863                            serde_json::Error::io(std::io::Error::other("API error")),
864                        ))
865                    });
866            });
867
868        let result = internal_post_keys_for_key_connector_registration(
869            &registration_client,
870            &api_client,
871            &key_connector_api_client,
872            TEST_SSO_ORG_IDENTIFIER.to_string(),
873        )
874        .await;
875
876        assert!(result.is_err());
877        assert!(matches!(
878            result.unwrap_err(),
879            RegistrationError::KeyConnectorApi
880        ));
881
882        // Assert that the mock expectations were met
883        if let ApiClient::Mock(mut mock) = api_client {
884            mock.accounts_key_management_api.checkpoint();
885        }
886        if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
887            key_connector_api_client
888        {
889            mock.user_keys_api.checkpoint();
890        }
891    }
892
893    #[tokio::test]
894    async fn test_post_keys_for_key_connector_registration_api_failure() {
895        let client = Client::new(None);
896        let registration_client = RegistrationClient::new(client);
897
898        let api_client = ApiClient::new_mocked(|mock| {
899            mock.accounts_key_management_api
900                .expect_post_set_key_connector_key()
901                .once()
902                .returning(move |_body| {
903                    Err(bitwarden_api_api::apis::Error::Serde(
904                        serde_json::Error::io(std::io::Error::other("API error")),
905                    ))
906                });
907        });
908
909        let key_connector_api_client =
910            bitwarden_api_key_connector::apis::ApiClient::new_mocked(|mock| {
911                mock.user_keys_api
912                    .expect_get_user_key()
913                    .once()
914                    .returning(move || {
915                        Err(bitwarden_api_key_connector::apis::Error::ResponseError(
916                            bitwarden_api_key_connector::apis::ResponseContent {
917                                status: reqwest::StatusCode::NOT_FOUND,
918                                content: "Not Found".to_string(),
919                            },
920                        ))
921                    });
922                mock.user_keys_api
923                    .expect_post_user_key()
924                    .once()
925                    .returning(move |_body| Ok(()));
926            });
927
928        let result = internal_post_keys_for_key_connector_registration(
929            &registration_client,
930            &api_client,
931            &key_connector_api_client,
932            TEST_SSO_ORG_IDENTIFIER.to_string(),
933        )
934        .await;
935
936        assert!(result.is_err());
937        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
938
939        // Assert that the mock expectations were met
940        if let ApiClient::Mock(mut mock) = api_client {
941            mock.accounts_key_management_api.checkpoint();
942        }
943        if let bitwarden_api_key_connector::apis::ApiClient::Mock(mut mock) =
944            key_connector_api_client
945        {
946            mock.user_keys_api.checkpoint();
947        }
948    }
949
950    #[tokio::test]
951    async fn test_post_keys_for_jit_password_registration_success() {
952        let client = Client::new(None);
953        let registration_client = RegistrationClient::new(client);
954
955        let expected_hint = "test hint";
956
957        let api_client = ApiClient::new_mocked(|mock| {
958            mock.accounts_api
959                .expect_post_set_password()
960                .once()
961                .withf(move |body| {
962                    if let Some(req) = body {
963                        assert_eq!(req.org_identifier, TEST_SSO_ORG_IDENTIFIER);
964                        assert_eq!(req.master_password_hint, Some(expected_hint.to_string()));
965                        assert!(req.account_keys.is_some());
966                        let account_keys = req.account_keys.as_ref().unwrap();
967                        assert!(
968                            account_keys
969                                .user_key_encrypted_account_private_key
970                                .is_some()
971                        );
972                        assert!(account_keys.account_public_key.is_some());
973                        assert!(account_keys.public_key_encryption_key_pair.is_some());
974                        let public_key_encryption_key_pair = account_keys
975                            .public_key_encryption_key_pair
976                            .as_ref()
977                            .unwrap();
978                        assert!(public_key_encryption_key_pair.public_key.is_some());
979                        assert!(public_key_encryption_key_pair.signed_public_key.is_some());
980                        assert!(public_key_encryption_key_pair.wrapped_private_key.is_some());
981                        assert!(account_keys.signature_key_pair.is_some());
982                        let signature_key_pair = account_keys.signature_key_pair.as_ref().unwrap();
983                        assert_eq!(
984                            signature_key_pair.signature_algorithm,
985                            Some("ed25519".to_string())
986                        );
987                        assert!(signature_key_pair.verifying_key.is_some());
988                        assert!(signature_key_pair.wrapped_signing_key.is_some());
989                        assert!(account_keys.security_state.is_some());
990                        let security_state = account_keys.security_state.as_ref().unwrap();
991                        assert!(security_state.security_state.is_some());
992                        assert_eq!(security_state.security_version, 2);
993                        assert!(req.master_password_unlock.is_some());
994                        let master_password_unlock = req.master_password_unlock.as_ref().unwrap();
995                        assert_eq!(master_password_unlock.salt, "[email protected]".to_string());
996                        assert_eq!(
997                            master_password_unlock.kdf,
998                            Box::new(KdfRequestModel {
999                                kdf_type: KdfType::Argon2id,
1000                                iterations: 6,
1001                                memory: Some(32),
1002                                parallelism: Some(4),
1003                            })
1004                        );
1005                        assert!(req.master_password_authentication.is_some());
1006                        let master_password_authentication =
1007                            req.master_password_authentication.as_ref().unwrap();
1008                        assert_eq!(
1009                            master_password_authentication.salt,
1010                            "[email protected]".to_string()
1011                        );
1012                        assert_eq!(
1013                            master_password_authentication.kdf,
1014                            Box::new(KdfRequestModel {
1015                                kdf_type: KdfType::Argon2id,
1016                                iterations: 6,
1017                                memory: Some(32),
1018                                parallelism: Some(4),
1019                            })
1020                        );
1021                        true
1022                    } else {
1023                        false
1024                    }
1025                })
1026                .returning(move |_body| Ok(()));
1027            mock.organization_users_api
1028                .expect_put_reset_password_enrollment()
1029                .once()
1030                .withf(move |org_id, user_id, body| {
1031                    assert_eq!(*org_id, uuid::uuid!(TEST_ORG_ID));
1032                    assert_eq!(*user_id, uuid::uuid!(TEST_USER_ID));
1033                    if let Some(enrollment_request) = body {
1034                        assert!(enrollment_request.reset_password_key.is_some());
1035                        assert!(enrollment_request.master_password_hash.is_some());
1036                        true
1037                    } else {
1038                        false
1039                    }
1040                })
1041                .returning(move |_org_id, _user_id, _body| Ok(()));
1042        });
1043
1044        let request = JitMasterPasswordRegistrationRequest {
1045            org_id: TEST_ORG_ID.parse().unwrap(),
1046            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
1047            organization_sso_identifier: TEST_SSO_ORG_IDENTIFIER.to_string(),
1048            user_id: TEST_USER_ID.parse().unwrap(),
1049            salt: "[email protected]".to_string(),
1050            master_password: "test-password-123".to_string(),
1051            master_password_hint: Some(expected_hint.to_string()),
1052            reset_password_enroll: true,
1053        };
1054
1055        let result = internal_post_keys_for_jit_password_registration(
1056            &registration_client,
1057            &api_client,
1058            request,
1059        )
1060        .await;
1061
1062        assert!(result.is_ok());
1063        let result = result.unwrap();
1064        assert!(matches!(
1065            result.account_cryptographic_state,
1066            WrappedAccountCryptographicState::V2 { .. }
1067        ));
1068        assert_eq!(result.master_password_unlock.salt, "[email protected]");
1069        assert!(matches!(
1070            result.master_password_unlock.master_key_wrapped_user_key,
1071            EncString::Aes256Cbc_HmacSha256_B64 { .. }
1072        ));
1073        assert_eq!(
1074            result.master_password_unlock.kdf,
1075            Kdf::Argon2id {
1076                iterations: NonZeroU32::new(6).unwrap(),
1077                memory: NonZeroU32::new(32).unwrap(),
1078                parallelism: NonZeroU32::new(4).unwrap(),
1079            }
1080        );
1081
1082        // Assert that the mock expectations were met
1083        if let ApiClient::Mock(mut mock) = api_client {
1084            mock.accounts_api.checkpoint();
1085            mock.organization_users_api.checkpoint();
1086        }
1087    }
1088
1089    #[tokio::test]
1090    async fn test_post_keys_for_jit_password_registration_api_failure() {
1091        let client = Client::new(None);
1092        let registration_client = RegistrationClient::new(client);
1093
1094        let api_client = ApiClient::new_mocked(|mock| {
1095            mock.accounts_api
1096                .expect_post_set_password()
1097                .once()
1098                .returning(move |_body| {
1099                    Err(bitwarden_api_api::apis::Error::Serde(
1100                        serde_json::Error::io(std::io::Error::other("API error")),
1101                    ))
1102                });
1103            mock.organization_users_api
1104                .expect_put_reset_password_enrollment()
1105                .never();
1106        });
1107
1108        let request = JitMasterPasswordRegistrationRequest {
1109            org_id: TEST_ORG_ID.parse().unwrap(),
1110            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
1111            organization_sso_identifier: TEST_SSO_ORG_IDENTIFIER.to_string(),
1112            user_id: TEST_USER_ID.parse().unwrap(),
1113            salt: "[email protected]".to_string(),
1114            master_password: "test-password-123".to_string(),
1115            master_password_hint: Some("test hint".to_string()),
1116            reset_password_enroll: true,
1117        };
1118
1119        let result = internal_post_keys_for_jit_password_registration(
1120            &registration_client,
1121            &api_client,
1122            request,
1123        )
1124        .await;
1125
1126        assert!(result.is_err());
1127        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
1128
1129        // Assert that the mock expectations were met
1130        if let ApiClient::Mock(mut mock) = api_client {
1131            mock.accounts_api.checkpoint();
1132            mock.organization_users_api.checkpoint();
1133        }
1134    }
1135
1136    #[tokio::test]
1137    async fn test_post_keys_for_jit_password_registration_reset_password_enrollment_failure() {
1138        let client = Client::new(None);
1139        let registration_client = RegistrationClient::new(client);
1140
1141        let api_client = ApiClient::new_mocked(|mock| {
1142            mock.accounts_api
1143                .expect_post_set_password()
1144                .once()
1145                .returning(move |_body| Ok(()));
1146            mock.organization_users_api
1147                .expect_put_reset_password_enrollment()
1148                .once()
1149                .returning(move |_org_id, _user_id, _body| {
1150                    Err(bitwarden_api_api::apis::Error::Serde(
1151                        serde_json::Error::io(std::io::Error::other("API error")),
1152                    ))
1153                });
1154        });
1155
1156        let request = JitMasterPasswordRegistrationRequest {
1157            org_id: TEST_ORG_ID.parse().unwrap(),
1158            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
1159            organization_sso_identifier: TEST_SSO_ORG_IDENTIFIER.to_string(),
1160            user_id: TEST_USER_ID.parse().unwrap(),
1161            salt: "[email protected]".to_string(),
1162            master_password: "test-password-123".to_string(),
1163            master_password_hint: Some("test hint".to_string()),
1164            reset_password_enroll: true,
1165        };
1166
1167        let result = internal_post_keys_for_jit_password_registration(
1168            &registration_client,
1169            &api_client,
1170            request,
1171        )
1172        .await;
1173
1174        assert!(result.is_err());
1175        assert!(matches!(result.unwrap_err(), RegistrationError::Api));
1176
1177        // Assert that the mock expectations were met
1178        if let ApiClient::Mock(mut mock) = api_client {
1179            mock.accounts_api.checkpoint();
1180            mock.organization_users_api.checkpoint();
1181        }
1182    }
1183
1184    #[tokio::test]
1185    async fn test_post_keys_for_jit_password_registration_reset_password_enroll_false() {
1186        let client = Client::new(None);
1187        let registration_client = RegistrationClient::new(client);
1188
1189        let api_client = ApiClient::new_mocked(|mock| {
1190            mock.accounts_api
1191                .expect_post_set_password()
1192                .once()
1193                .returning(move |_body| Ok(()));
1194            mock.organization_users_api
1195                .expect_put_reset_password_enrollment()
1196                .never();
1197        });
1198
1199        let request = JitMasterPasswordRegistrationRequest {
1200            org_id: TEST_ORG_ID.parse().unwrap(),
1201            org_public_key: TEST_ORG_PUBLIC_KEY.into(),
1202            organization_sso_identifier: TEST_SSO_ORG_IDENTIFIER.to_string(),
1203            user_id: TEST_USER_ID.parse().unwrap(),
1204            salt: "[email protected]".to_string(),
1205            master_password: "test-password-123".to_string(),
1206            master_password_hint: Some("test hint".to_string()),
1207            reset_password_enroll: false,
1208        };
1209
1210        let result = internal_post_keys_for_jit_password_registration(
1211            &registration_client,
1212            &api_client,
1213            request,
1214        )
1215        .await;
1216
1217        assert!(result.is_ok());
1218
1219        // Assert that the mock expectations were met
1220        if let ApiClient::Mock(mut mock) = api_client {
1221            mock.accounts_api.checkpoint();
1222            mock.organization_users_api.checkpoint();
1223        }
1224    }
1225}