infisical_api/
client.rs

1use crate::api;
2use crate::error::Result;
3use crate::utils;
4
5use onionsalt::crypto;
6use reqwest::header;
7
8/// `Client` provides a wrapper around the Infisical API that gives easy access to its endpoints
9pub struct Client {
10    http_client: reqwest::Client,
11    api_base: String,
12}
13
14impl Client {
15    /// Constructs a new `Client` using the default Infisical Cloud API endpoint and reqwest Client
16    pub fn new(api_key: &str) -> Result<Client> {
17        ClientBuilder::new().build(api_key)
18    }
19
20    /// Creates a new `ClientBuilder` to allow for `Client` customization.
21    ///
22    /// This is the same as `ClientBuilder::new()`.
23    pub fn builder() -> ClientBuilder {
24        ClientBuilder::new()
25    }
26
27    pub async fn get_user(&self) -> Result<api::models::User> {
28        let request = api::models::GetMyUserRequest {
29            base_url: self.api_base.clone(),
30        };
31
32        let response = api::get_my_user(&self.http_client, request)
33            .await
34            .map_err(crate::error::reqwest)?;
35
36        Ok(response.user)
37    }
38
39    pub async fn get_my_organizations(&self) -> Result<Vec<api::models::Organization>> {
40        let request = api::models::GetMyOrganizationsRequest {
41            base_url: self.api_base.clone(),
42        };
43
44        let response = api::get_my_organizations(&self.http_client, request)
45            .await
46            .map_err(crate::error::reqwest)?;
47
48        Ok(response.organizations)
49    }
50
51    pub async fn get_organization_memberships(
52        &self,
53        organization_id: &str,
54    ) -> Result<Vec<api::models::OrganizationMembership>> {
55        let request = api::models::GetOrganizationMembershipsRequest {
56            base_url: self.api_base.clone(),
57            organization_id: organization_id.to_string(),
58        };
59
60        let response = api::get_organization_memberships(&self.http_client, request)
61            .await
62            .map_err(crate::error::reqwest)?;
63
64        Ok(response.memberships)
65    }
66
67    pub async fn update_organization_membership(
68        &self,
69        organization_id: &str,
70        membership_id: &str,
71        role: &str,
72    ) -> Result<api::models::OrganizationMembership> {
73        let request = api::models::UpdateOrganizationMembershipRequest {
74            base_url: self.api_base.clone(),
75            organization_id: organization_id.to_string(),
76            membership_id: membership_id.to_string(),
77            role: role.to_string(),
78        };
79
80        let response = api::update_organization_membership(&self.http_client, request)
81            .await
82            .map_err(crate::error::reqwest)?;
83
84        Ok(response.membership)
85    }
86
87    pub async fn delete_organization_membership(
88        &self,
89        organization_id: &str,
90        membership_id: &str,
91    ) -> Result<api::models::OrganizationMembership> {
92        let request = api::models::DeleteOrganizationMembershipRequest {
93            base_url: self.api_base.clone(),
94            organization_id: organization_id.to_string(),
95            membership_id: membership_id.to_string(),
96        };
97
98        let response = api::delete_organization_membership(&self.http_client, request)
99            .await
100            .map_err(crate::error::reqwest)?;
101
102        Ok(response.membership)
103    }
104
105    pub async fn get_organization_projects(
106        &self,
107        organization_id: &str,
108    ) -> Result<Vec<api::models::Workspace>> {
109        let request = api::models::GetProjectsRequest {
110            base_url: self.api_base.clone(),
111            organization_id: organization_id.to_string(),
112        };
113
114        let response = api::get_organization_projects(&self.http_client, request)
115            .await
116            .map_err(crate::error::reqwest)?;
117
118        Ok(response.workspaces)
119    }
120
121    pub async fn get_project_memberships(
122        &self,
123        workspace_id: &str,
124    ) -> Result<Vec<api::models::ProjectMembership>> {
125        let request = api::models::GetProjectMembershipsRequest {
126            base_url: self.api_base.clone(),
127            workspace_id: workspace_id.to_string(),
128        };
129
130        let response = api::get_project_memberships(&self.http_client, request)
131            .await
132            .map_err(crate::error::reqwest)?;
133
134        Ok(response.memberships)
135    }
136
137    pub async fn update_project_membership(
138        &self,
139        workspace_id: &str,
140        membership_id: &str,
141        role: &str,
142    ) -> Result<api::models::ProjectMembership> {
143        let request = api::models::UpdateProjectMembershipRequest {
144            base_url: self.api_base.clone(),
145            workspace_id: workspace_id.to_string(),
146            membership_id: membership_id.to_string(),
147            role: role.to_string(),
148        };
149
150        let response = api::update_project_membership(&self.http_client, request)
151            .await
152            .map_err(crate::error::reqwest)?;
153
154        Ok(response.membership)
155    }
156
157    pub async fn delete_project_membership(
158        &self,
159        workspace_id: &str,
160        membership_id: &str,
161    ) -> Result<api::models::ProjectMembership> {
162        let request = api::models::DeleteProjectMembershipRequest {
163            base_url: self.api_base.clone(),
164            workspace_id: workspace_id.to_string(),
165            membership_id: membership_id.to_string(),
166        };
167
168        let response = api::delete_project_membership(&self.http_client, request)
169            .await
170            .map_err(crate::error::reqwest)?;
171
172        Ok(response.membership)
173    }
174
175    pub async fn get_encrypted_project_key(
176        &self,
177        workspace_id: &str,
178    ) -> Result<api::models::GetProjectKeyResponse> {
179        let request = api::models::GetProjectKeyRequest {
180            base_url: self.api_base.clone(),
181            workspace_id: workspace_id.to_string(),
182        };
183
184        api::get_project_key(&self.http_client, request)
185            .await
186            .map_err(crate::error::reqwest)
187    }
188
189    pub async fn get_decrypted_project_key(
190        &self,
191        workspace_id: &str,
192        private_key: &str,
193    ) -> Result<String> {
194        let response = self.get_encrypted_project_key(workspace_id).await?;
195
196        let mut encrypted_project_key = vec![0; 16];
197        encrypted_project_key.extend(utils::base64::decode(&response.encrypted_key));
198        let project_nonce = utils::base64::decode(&response.nonce);
199        let public_key = utils::base64::decode(&response.sender.public_key);
200        let private_key = utils::base64::decode(&private_key);
201
202        let project_nonce: [u8; 24] = project_nonce[..24]
203            .try_into()
204            .map_err(crate::error::decrypt)?;
205        let public_key: [u8; 32] = public_key[..32].try_into().map_err(crate::error::decrypt)?;
206        let private_key: [u8; 32] = private_key[..32]
207            .try_into()
208            .map_err(crate::error::decrypt)?;
209
210        let project_nonce = crypto::Nonce(project_nonce);
211        let public_key = crypto::PublicKey(public_key);
212        let private_key = crypto::SecretKey(private_key);
213
214        let mut project_key = Vec::with_capacity(encrypted_project_key.len());
215        for _ in 0..encrypted_project_key.len() {
216            project_key.push(0)
217        }
218        crypto::box_open(
219            &mut project_key,
220            &encrypted_project_key,
221            &project_nonce,
222            &public_key,
223            &private_key,
224        )?;
225        project_key.drain(..32);
226        Ok(String::from_utf8(project_key).map_err(crate::error::utf8)?)
227    }
228
229    pub async fn get_project_logs(
230        &self,
231        workspace_id: &str,
232        user_id: &str,
233        offset: &str,
234        limit: &str,
235        sort_by: &str,
236        action_names: &str,
237    ) -> Result<Vec<api::models::ProjectLog>> {
238        let request = api::models::GetProjectLogsRequest {
239            base_url: self.api_base.clone(),
240            workspace_id: workspace_id.to_string(),
241            user_id: user_id.to_string(),
242            offset: offset.to_string(),
243            limit: limit.to_string(),
244            sort_by: sort_by.to_string(),
245            action_names: action_names.to_string(),
246        };
247
248        let response = api::get_project_logs(&self.http_client, request)
249            .await
250            .map_err(crate::error::reqwest)?;
251
252        Ok(response.logs)
253    }
254
255    pub async fn get_project_snapshots(
256        &self,
257        workspace_id: &str,
258        offset: &str,
259        limit: &str,
260    ) -> Result<Vec<api::models::SecretSnapshot>> {
261        let request = api::models::GetProjectSnapshotsRequest {
262            base_url: self.api_base.clone(),
263            workspace_id: workspace_id.to_string(),
264            offset: offset.to_string(),
265            limit: limit.to_string(),
266        };
267
268        let response = api::get_project_snapshots(&self.http_client, request)
269            .await
270            .map_err(crate::error::reqwest)?;
271
272        Ok(response.secret_snapshots)
273    }
274
275    pub async fn roll_back_to_snapshot(
276        &self,
277        workspace_id: &str,
278        version: u8,
279    ) -> Result<Vec<api::models::EncryptedSecret>> {
280        let request = api::models::RollbackProjectToSnapshotRequest {
281            base_url: self.api_base.clone(),
282            workspace_id: workspace_id.to_string(),
283            version,
284        };
285
286        let response = api::roll_back_to_snapshot(&self.http_client, request)
287            .await
288            .map_err(crate::error::reqwest)?;
289
290        Ok(response.secrets)
291    }
292
293    pub async fn create_project_secrets(
294        &self,
295        workspace_id: &str,
296        environment: &str,
297        secrets: Vec<api::models::SecretToCreate>,
298    ) -> Result<Vec<api::models::EncryptedSecret>> {
299        let request = api::models::CreateProjectSecretsRequest {
300            base_url: self.api_base.clone(),
301            workspace_id: workspace_id.to_string(),
302            environment: environment.to_string(),
303            secrets,
304        };
305
306        let response = api::create_project_secrets(&self.http_client, request)
307            .await
308            .map_err(crate::error::reqwest)?;
309
310        Ok(response.secrets)
311    }
312
313    pub async fn get_encrypted_project_secrets(
314        &self,
315        workspace_id: &str,
316        environment: &str,
317    ) -> Result<Vec<api::models::EncryptedSecret>> {
318        let request = api::models::GetProjectSecretsRequest {
319            base_url: self.api_base.clone(),
320            workspace_id: workspace_id.to_string(),
321            environment: environment.to_string(),
322            content: String::from(""),
323        };
324
325        let response = api::get_project_secrets(&self.http_client, request)
326            .await
327            .map_err(crate::error::reqwest)?;
328
329        Ok(response.secrets)
330    }
331
332    pub async fn get_decrypted_project_secrets(
333        &self,
334        workspace_id: &str,
335        environment: &str,
336        private_key: &str,
337    ) -> Result<Vec<api::models::DecryptedSecret>> {
338        let encrypted_secrets: Vec<api::models::EncryptedSecret> = self
339            .get_encrypted_project_secrets(workspace_id, environment)
340            .await?;
341
342        encrypted_secrets
343            .iter()
344            .map(|enc_secret| api::models::EncryptedSecret::decrypt(enc_secret, private_key))
345            .collect()
346    }
347
348    pub async fn get_user_decrypted_private_key(&self, infisical_secret: &str) -> Result<String> {
349        let user = self.get_user().await?;
350        utils::aes256gcm::decrypt(
351            &user.encrypted_private_key,
352            &user.iv,
353            &user.tag,
354            &infisical_secret,
355        )
356    }
357}
358
359/// `ClientBuilder` can be used to create a `Client` with a custom API endpoint and/or [`Reqwest
360/// Client`]
361///
362/// [`Reqwest Client`]: reqwest::Client
363pub struct ClientBuilder {
364    api_base: String,
365    reqwest_client_builder: Option<reqwest::ClientBuilder>,
366}
367
368impl Default for ClientBuilder {
369    fn default() -> Self {
370        Self::new()
371    }
372}
373
374impl ClientBuilder {
375    pub fn new() -> ClientBuilder {
376        ClientBuilder {
377            api_base: String::from("https://app.infisical.com/api"),
378            reqwest_client_builder: None,
379        }
380    }
381
382    pub fn build(mut self, api_key: &str) -> Result<Client> {
383        // If a custom client was not provided then we create our own default client
384        if self.reqwest_client_builder.is_none() {
385            self.reqwest_client_builder = Some(reqwest::ClientBuilder::new());
386        }
387
388        // Add the API key as a default header since every endpoint expects it
389        let mut headers = header::HeaderMap::new();
390        headers.insert(
391            "X-API-KEY",
392            header::HeaderValue::try_from(api_key).map_err(crate::error::reqwest)?,
393        );
394
395        match self.reqwest_client_builder {
396            Some(mut reqwest_client_builder) => {
397                reqwest_client_builder = reqwest_client_builder.default_headers(headers);
398                Ok(Client {
399                    http_client: reqwest_client_builder
400                        .build()
401                        .map_err(crate::error::builder)?,
402                    api_base: self.api_base.clone(),
403                })
404            }
405            None => unreachable!("There will always be a reqwest_client_builder at this point"),
406        }
407    }
408
409    pub fn api_base(mut self, value: &str) -> ClientBuilder {
410        self.api_base = value.to_string();
411        self
412    }
413
414    /// Setter for the reqwest_client_builder struct member
415    pub fn reqwest_client_builder(mut self, value: reqwest::ClientBuilder) -> ClientBuilder {
416        self.reqwest_client_builder = Some(value);
417        self
418    }
419}