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            client: bw_http_client.clone().into(),
68        };
69
70        // Create the client for the API service, with authentication middleware.
71        let auth_middleware = token_handler.initialize_middleware(
72            login_method.clone(),
73            identity.clone(),
74            key_store.clone(),
75        );
76        let bw_http_client = reqwest_middleware::ClientBuilder::new(bw_http_client)
77            .with_arc(auth_middleware)
78            .build();
79        let api = bitwarden_api_api::Configuration {
80            base_path: settings.api_url,
81            client: bw_http_client,
82        };
83
84        Self {
85            internal: Arc::new(InternalClient {
86                user_id: OnceLock::new(),
87                token_handler,
88                login_method,
89                #[cfg(feature = "internal")]
90                flags: RwLock::new(Flags::default()),
91                api_configurations: ApiConfigurations::new(identity, api, settings.device_type),
92                external_http_client,
93                key_store,
94                #[cfg(feature = "internal")]
95                security_state: RwLock::new(None),
96                #[cfg(feature = "internal")]
97                repository_map: StateRegistry::new(),
98            }),
99        }
100    }
101}
102
103fn new_http_client_builder() -> reqwest::ClientBuilder {
104    #[allow(unused_mut)]
105    let mut client_builder = reqwest::Client::builder();
106
107    #[cfg(not(target_arch = "wasm32"))]
108    {
109        use rustls::ClientConfig;
110        use rustls_platform_verifier::ConfigVerifierExt;
111        client_builder = client_builder.use_preconfigured_tls(
112            ClientConfig::with_platform_verifier().expect("Failed to create platform verifier"),
113        );
114
115        // Enforce HTTPS for all requests in non-debug builds
116        #[cfg(not(debug_assertions))]
117        {
118            client_builder = client_builder.https_only(true);
119        }
120    }
121
122    client_builder
123}
124
125/// Build default headers for Bitwarden HttpClient
126fn build_default_headers(settings: &ClientSettings) -> header::HeaderMap {
127    let mut headers = header::HeaderMap::new();
128
129    // Handle optional headers
130
131    if let Some(device_identifier) = &settings.device_identifier {
132        headers.append(
133            "Device-Identifier",
134            HeaderValue::from_str(device_identifier)
135                .expect("Device identifier should be a valid header value"),
136        );
137    }
138
139    if let Some(client_type) = Into::<Option<ClientName>>::into(settings.device_type) {
140        headers.append(
141            "Bitwarden-Client-Name",
142            HeaderValue::from_str(&client_type.to_string())
143                .expect("All ASCII strings are valid header values"),
144        );
145    }
146
147    if let Some(version) = &settings.bitwarden_client_version {
148        headers.append(
149            "Bitwarden-Client-Version",
150            HeaderValue::from_str(version).expect("Version should be a valid header value"),
151        );
152    }
153
154    if let Some(package_type) = &settings.bitwarden_package_type {
155        headers.append(
156            "Bitwarden-Package-Type",
157            HeaderValue::from_str(package_type)
158                .expect("Package type should be a valid header value"),
159        );
160    }
161
162    // Handle required headers
163
164    headers.append(
165        "Device-Type",
166        HeaderValue::from_str(&(settings.device_type as u8).to_string())
167            .expect("All numbers are valid ASCII"),
168    );
169
170    headers.append(
171        reqwest::header::USER_AGENT,
172        HeaderValue::from_str(&settings.user_agent)
173            .expect("User agent should be a valid header value"),
174    );
175
176    headers
177}