files_sdk/users/
api_keys.rs

1//! API Key management operations
2//!
3//! Provides secure API key management for programmatic access to Files.com.
4//! API keys are the recommended authentication method for applications and scripts.
5//!
6//! # Features
7//!
8//! - Create and revoke API keys
9//! - Set expiration dates
10//! - Configure permission sets
11//! - Track key usage and last access
12//! - Manage keys for specific users
13//!
14//! # Security Best Practices
15//!
16//! - Store API keys securely (environment variables, secret managers)
17//! - Set expiration dates for temporary access
18//! - Use permission sets to limit key capabilities
19//! - Rotate keys periodically
20//! - Delete unused keys immediately
21//!
22//! # Example
23//!
24//! ```no_run
25//! use files_sdk::{FilesClient, ApiKeyHandler};
26//!
27//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
28//! let client = FilesClient::builder()
29//!     .api_key("your-api-key")
30//!     .build()?;
31//!
32//! let handler = ApiKeyHandler::new(client);
33//!
34//! // Create a new API key with expiration
35//! let key = handler.create(
36//!     Some("Automation Script Key"),
37//!     Some("For nightly backup automation"),
38//!     Some("2025-12-31T23:59:59Z"),
39//!     None
40//! ).await?;
41//!
42//! // IMPORTANT: Save this key securely - it won't be shown again
43//! println!("New API Key: {}", key.key.unwrap());
44//! println!("Key ID: {}", key.id.unwrap());
45//!
46//! // List all API keys
47//! let (keys, _) = handler.list(None, None, Some(50)).await?;
48//! for api_key in keys {
49//!     println!("{}: Last used {}",
50//!         api_key.name.unwrap_or_default(),
51//!         api_key.last_use_at.unwrap_or_default());
52//! }
53//! # Ok(())
54//! # }
55//! ```
56
57use crate::{FilesClient, PaginationInfo, Result};
58use serde::{Deserialize, Serialize};
59use serde_json::json;
60
61/// API Key entity from Files.com API
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ApiKeyEntity {
64    /// API Key ID
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub id: Option<i64>,
67
68    /// API Key name
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub name: Option<String>,
71
72    /// API Key description
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub description: Option<String>,
75
76    /// Descriptive label
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub descriptive_label: Option<String>,
79
80    /// The actual API key (only returned on creation)
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub key: Option<String>,
83
84    /// User ID this key belongs to
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub user_id: Option<i64>,
87
88    /// Platform this key is for
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub platform: Option<String>,
91
92    /// Permission set for this key
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub permission_set: Option<String>,
95
96    /// URL this key is associated with
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub url: Option<String>,
99
100    /// Created at timestamp
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub created_at: Option<String>,
103
104    /// Expires at timestamp
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub expires_at: Option<String>,
107
108    /// Last use at timestamp
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub last_use_at: Option<String>,
111}
112
113/// Handler for API key operations
114#[derive(Debug, Clone)]
115pub struct ApiKeyHandler {
116    client: FilesClient,
117}
118
119impl ApiKeyHandler {
120    /// Creates a new ApiKeyHandler
121    pub fn new(client: FilesClient) -> Self {
122        Self { client }
123    }
124
125    /// List API keys
126    ///
127    /// Returns a paginated list of API keys with usage information and metadata.
128    ///
129    /// # Arguments
130    ///
131    /// * `user_id` - Filter by specific user ID (None for all users)
132    /// * `cursor` - Pagination cursor from previous response
133    /// * `per_page` - Number of results per page (max 10,000)
134    ///
135    /// # Returns
136    ///
137    /// A tuple containing:
138    /// - Vector of `ApiKeyEntity` objects
139    /// - `PaginationInfo` with cursors for next/previous pages
140    ///
141    /// # Example
142    ///
143    /// ```no_run
144    /// use files_sdk::{FilesClient, ApiKeyHandler};
145    ///
146    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
147    /// let client = FilesClient::builder().api_key("key").build()?;
148    /// let handler = ApiKeyHandler::new(client);
149    ///
150    /// // List all API keys
151    /// let (keys, pagination) = handler.list(None, None, Some(50)).await?;
152    ///
153    /// for key in keys {
154    ///     println!("{}: Created {} - Last used {}",
155    ///         key.name.unwrap_or_default(),
156    ///         key.created_at.unwrap_or_default(),
157    ///         key.last_use_at.unwrap_or_default());
158    /// }
159    ///
160    /// // Get keys for specific user
161    /// let (user_keys, _) = handler.list(Some(12345), None, None).await?;
162    /// # Ok(())
163    /// # }
164    /// ```
165    pub async fn list(
166        &self,
167        user_id: Option<i64>,
168        cursor: Option<String>,
169        per_page: Option<i32>,
170    ) -> Result<(Vec<ApiKeyEntity>, PaginationInfo)> {
171        let mut path = "/api_keys?".to_string();
172
173        if let Some(uid) = user_id {
174            path.push_str(&format!("user_id={}&", uid));
175        }
176        if let Some(c) = cursor {
177            path.push_str(&format!("cursor={}&", c));
178        }
179        if let Some(pp) = per_page {
180            path.push_str(&format!("per_page={}&", pp));
181        }
182
183        let response = self.client.get_raw(&path).await?;
184        let keys: Vec<ApiKeyEntity> = serde_json::from_value(response)?;
185
186        let pagination = PaginationInfo {
187            cursor_next: None,
188            cursor_prev: None,
189        };
190
191        Ok((keys, pagination))
192    }
193
194    /// Get details of a specific API key
195    ///
196    /// # Arguments
197    ///
198    /// * `id` - API Key ID
199    ///
200    /// # Returns
201    ///
202    /// An `ApiKeyEntity` with key details (note: the actual key value is not included)
203    ///
204    /// # Example
205    ///
206    /// ```no_run
207    /// use files_sdk::{FilesClient, ApiKeyHandler};
208    ///
209    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
210    /// let client = FilesClient::builder().api_key("key").build()?;
211    /// let handler = ApiKeyHandler::new(client);
212    ///
213    /// let key = handler.get(12345).await?;
214    /// println!("Key name: {}", key.name.unwrap_or_default());
215    /// println!("Expires: {}", key.expires_at.unwrap_or_default());
216    /// # Ok(())
217    /// # }
218    /// ```
219    pub async fn get(&self, id: i64) -> Result<ApiKeyEntity> {
220        let path = format!("/api_keys/{}", id);
221        let response = self.client.get_raw(&path).await?;
222        Ok(serde_json::from_value(response)?)
223    }
224
225    /// Create a new API key
226    ///
227    /// Generates a new API key for programmatic access. The key value is only
228    /// returned once during creation - save it securely immediately.
229    ///
230    /// # Arguments
231    ///
232    /// * `name` - Descriptive name for the key
233    /// * `description` - Detailed description of key purpose
234    /// * `expires_at` - ISO 8601 expiration timestamp (e.g., "2025-12-31T23:59:59Z")
235    /// * `permission_set` - Named permission set to limit key capabilities
236    ///
237    /// # Returns
238    ///
239    /// An `ApiKeyEntity` with the `key` field containing the actual API key.
240    /// This is the only time the key value will be visible.
241    ///
242    /// # Example
243    ///
244    /// ```no_run
245    /// use files_sdk::{FilesClient, ApiKeyHandler};
246    ///
247    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
248    /// let client = FilesClient::builder().api_key("key").build()?;
249    /// let handler = ApiKeyHandler::new(client);
250    ///
251    /// // Create key with expiration
252    /// let key = handler.create(
253    ///     Some("CI/CD Pipeline Key"),
254    ///     Some("For automated deployments"),
255    ///     Some("2025-12-31T23:59:59Z"),
256    ///     None
257    /// ).await?;
258    ///
259    /// // CRITICAL: Save this key now - it won't be shown again!
260    /// println!("API Key: {}", key.key.unwrap());
261    /// println!("Store this securely in your CI/CD secrets");
262    /// # Ok(())
263    /// # }
264    /// ```
265    pub async fn create(
266        &self,
267        name: Option<&str>,
268        description: Option<&str>,
269        expires_at: Option<&str>,
270        permission_set: Option<&str>,
271    ) -> Result<ApiKeyEntity> {
272        let mut body = json!({});
273
274        if let Some(n) = name {
275            body["name"] = json!(n);
276        }
277        if let Some(d) = description {
278            body["description"] = json!(d);
279        }
280        if let Some(e) = expires_at {
281            body["expires_at"] = json!(e);
282        }
283        if let Some(p) = permission_set {
284            body["permission_set"] = json!(p);
285        }
286
287        let response = self.client.post_raw("/api_keys", body).await?;
288        Ok(serde_json::from_value(response)?)
289    }
290
291    /// Update an API key's metadata
292    ///
293    /// Modifies key information such as name, description, or expiration.
294    /// The actual key value cannot be changed.
295    ///
296    /// # Arguments
297    ///
298    /// * `id` - API Key ID to update
299    /// * `name` - New descriptive name
300    /// * `description` - New description
301    /// * `expires_at` - New expiration timestamp
302    ///
303    /// # Returns
304    ///
305    /// The updated `ApiKeyEntity`
306    ///
307    /// # Example
308    ///
309    /// ```no_run
310    /// use files_sdk::{FilesClient, ApiKeyHandler};
311    ///
312    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
313    /// let client = FilesClient::builder().api_key("key").build()?;
314    /// let handler = ApiKeyHandler::new(client);
315    ///
316    /// // Extend expiration date
317    /// let key = handler.update(
318    ///     12345,
319    ///     None,
320    ///     None,
321    ///     Some("2026-12-31T23:59:59Z")
322    /// ).await?;
323    /// # Ok(())
324    /// # }
325    /// ```
326    pub async fn update(
327        &self,
328        id: i64,
329        name: Option<&str>,
330        description: Option<&str>,
331        expires_at: Option<&str>,
332    ) -> Result<ApiKeyEntity> {
333        let mut body = json!({});
334
335        if let Some(n) = name {
336            body["name"] = json!(n);
337        }
338        if let Some(d) = description {
339            body["description"] = json!(d);
340        }
341        if let Some(e) = expires_at {
342            body["expires_at"] = json!(e);
343        }
344
345        let path = format!("/api_keys/{}", id);
346        let response = self.client.patch_raw(&path, body).await?;
347        Ok(serde_json::from_value(response)?)
348    }
349
350    /// Delete an API key permanently
351    ///
352    /// Revokes the API key immediately. Any requests using this key will fail.
353    /// This operation cannot be undone.
354    ///
355    /// # Arguments
356    ///
357    /// * `id` - API Key ID to delete
358    ///
359    /// # Example
360    ///
361    /// ```no_run
362    /// use files_sdk::{FilesClient, ApiKeyHandler};
363    ///
364    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
365    /// let client = FilesClient::builder().api_key("key").build()?;
366    /// let handler = ApiKeyHandler::new(client);
367    ///
368    /// // Revoke compromised or unused key
369    /// handler.delete(12345).await?;
370    /// println!("API key revoked successfully");
371    /// # Ok(())
372    /// # }
373    /// ```
374    pub async fn delete(&self, id: i64) -> Result<()> {
375        let path = format!("/api_keys/{}", id);
376        self.client.delete_raw(&path).await?;
377        Ok(())
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384
385    #[test]
386    fn test_handler_creation() {
387        let client = FilesClient::builder().api_key("test-key").build().unwrap();
388        let _handler = ApiKeyHandler::new(client);
389    }
390}