Skip to main content

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