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}