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