1use crate::api;
2use crate::error::Result;
3use crate::utils;
4
5use onionsalt::crypto;
6use reqwest::header;
7
8pub struct Client {
10 http_client: reqwest::Client,
11 api_base: String,
12}
13
14impl Client {
15 pub fn new(api_key: &str) -> Result<Client> {
17 ClientBuilder::new().build(api_key)
18 }
19
20 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
359pub 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 self.reqwest_client_builder.is_none() {
385 self.reqwest_client_builder = Some(reqwest::ClientBuilder::new());
386 }
387
388 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 pub fn reqwest_client_builder(mut self, value: reqwest::ClientBuilder) -> ClientBuilder {
416 self.reqwest_client_builder = Some(value);
417 self
418 }
419}