bitwarden_core/client/
client.rs

1use std::sync::{Arc, OnceLock, RwLock};
2
3use bitwarden_crypto::KeyStore;
4#[cfg(feature = "internal")]
5use bitwarden_state::registry::StateRegistry;
6use reqwest::header::{self, HeaderValue};
7
8use super::internal::InternalClient;
9#[cfg(feature = "internal")]
10use crate::client::flags::Flags;
11use crate::client::{
12    client_settings::{ClientName, ClientSettings},
13    internal::{ApiConfigurations, ClientManagedTokens, SdkManagedTokens, Tokens},
14};
15
16/// The main struct to interact with the Bitwarden SDK.
17#[derive(Debug, Clone)]
18pub struct Client {
19    // Important: The [`Client`] struct requires its `Clone` implementation to return an owned
20    // reference to the same instance. This is required to properly use the FFI API, where we can't
21    // just use normal Rust references effectively. For this to happen, any mutable state needs
22    // to be behind an Arc, ideally as part of the existing [`InternalClient`] struct.
23    #[doc(hidden)]
24    pub internal: Arc<InternalClient>,
25}
26
27impl Client {
28    /// Create a new Bitwarden client with SDK-managed tokens.
29    pub fn new(settings: Option<ClientSettings>) -> Self {
30        Self::new_internal(settings, Tokens::SdkManaged(SdkManagedTokens::default()))
31    }
32
33    /// Create a new Bitwarden client with client-managed tokens.
34    pub fn new_with_client_tokens(
35        settings: Option<ClientSettings>,
36        tokens: Arc<dyn ClientManagedTokens>,
37    ) -> Self {
38        Self::new_internal(settings, Tokens::ClientManaged(tokens))
39    }
40
41    fn new_internal(settings_input: Option<ClientSettings>, tokens: Tokens) -> Self {
42        let settings = settings_input.unwrap_or_default();
43
44        let external_http_client = new_http_client_builder()
45            .build()
46            .expect("External HTTP Client build should not fail");
47
48        let headers = build_default_headers(&settings);
49
50        let bw_http_client = new_http_client_builder()
51            .default_headers(headers)
52            .build()
53            .expect("Bw HTTP Client build should not fail");
54
55        let bw_http_client = reqwest_middleware::ClientBuilder::new(bw_http_client).build();
56
57        let identity = bitwarden_api_identity::apis::configuration::Configuration {
58            base_path: settings.identity_url,
59            user_agent: Some(settings.user_agent.clone()),
60            client: bw_http_client.clone(),
61            basic_auth: None,
62            oauth_access_token: None,
63            bearer_access_token: None,
64            api_key: None,
65        };
66
67        let api = bitwarden_api_api::apis::configuration::Configuration {
68            base_path: settings.api_url,
69            user_agent: Some(settings.user_agent),
70            client: bw_http_client,
71            basic_auth: None,
72            oauth_access_token: None,
73            bearer_access_token: None,
74            api_key: None,
75        };
76
77        Self {
78            internal: Arc::new(InternalClient {
79                user_id: OnceLock::new(),
80                tokens: RwLock::new(tokens),
81                login_method: RwLock::new(None),
82                #[cfg(feature = "internal")]
83                flags: RwLock::new(Flags::default()),
84                __api_configurations: RwLock::new(ApiConfigurations::new(
85                    identity,
86                    api,
87                    settings.device_type,
88                )),
89                external_http_client,
90                key_store: KeyStore::default(),
91                #[cfg(feature = "internal")]
92                security_state: RwLock::new(None),
93                #[cfg(feature = "internal")]
94                repository_map: StateRegistry::new(),
95            }),
96        }
97    }
98}
99
100fn new_http_client_builder() -> reqwest::ClientBuilder {
101    #[allow(unused_mut)]
102    let mut client_builder = reqwest::Client::builder();
103
104    #[cfg(not(target_arch = "wasm32"))]
105    {
106        use rustls::ClientConfig;
107        use rustls_platform_verifier::ConfigVerifierExt;
108        client_builder = client_builder.use_preconfigured_tls(
109            ClientConfig::with_platform_verifier().expect("Failed to create platform verifier"),
110        );
111
112        // Enforce HTTPS for all requests in non-debug builds
113        #[cfg(not(debug_assertions))]
114        {
115            client_builder = client_builder.https_only(true);
116        }
117    }
118
119    client_builder
120}
121
122/// Build default headers for Bitwarden HttpClient
123fn build_default_headers(settings: &ClientSettings) -> header::HeaderMap {
124    let mut headers = header::HeaderMap::new();
125
126    // Handle optional headers
127
128    if let Some(device_identifier) = &settings.device_identifier {
129        headers.append(
130            "Device-Identifier",
131            HeaderValue::from_str(device_identifier)
132                .expect("Device identifier should be a valid header value"),
133        );
134    }
135
136    if let Some(client_type) = Into::<Option<ClientName>>::into(settings.device_type) {
137        headers.append(
138            "Bitwarden-Client-Name",
139            HeaderValue::from_str(&client_type.to_string())
140                .expect("All ASCII strings are valid header values"),
141        );
142    }
143
144    if let Some(version) = &settings.bitwarden_client_version {
145        headers.append(
146            "Bitwarden-Client-Version",
147            HeaderValue::from_str(version).expect("Version should be a valid header value"),
148        );
149    }
150
151    if let Some(package_type) = &settings.bitwarden_package_type {
152        headers.append(
153            "Bitwarden-Package-Type",
154            HeaderValue::from_str(package_type)
155                .expect("Package type should be a valid header value"),
156        );
157    }
158
159    // Handle required headers
160
161    headers.append(
162        "Device-Type",
163        HeaderValue::from_str(&(settings.device_type as u8).to_string())
164            .expect("All numbers are valid ASCII"),
165    );
166
167    // TODO: PM-29938 - Since we now add this header always, we need to remove this from the
168    // auto-generated code
169    headers.append(
170        reqwest::header::USER_AGENT,
171        HeaderValue::from_str(&settings.user_agent)
172            .expect("User agent should be a valid header value"),
173    );
174
175    headers
176}