files_sdk/users/
public_keys.rs

1//! Public Key operations for SSH key management
2//!
3//! This module provides operations for managing SSH public keys:
4//! - List public keys for a user
5//! - Create new public keys (upload or generate)
6//! - Update public key metadata
7//! - Delete public keys
8
9use crate::{FilesClient, PaginationInfo, Result};
10use serde::{Deserialize, Serialize};
11use serde_json::json;
12
13/// Represents a public SSH key in Files.com
14#[derive(Debug, Serialize, Deserialize, Clone)]
15pub struct PublicKeyEntity {
16    /// Public key ID
17    pub id: Option<i64>,
18
19    /// Public key title
20    pub title: Option<String>,
21
22    /// User ID this public key is associated with
23    pub user_id: Option<i64>,
24
25    /// Username of the user this public key is associated with
26    pub username: Option<String>,
27
28    /// Public key fingerprint (MD5)
29    pub fingerprint: Option<String>,
30
31    /// Public key fingerprint (SHA256)
32    pub fingerprint_sha256: Option<String>,
33
34    /// Public key created at date/time
35    pub created_at: Option<String>,
36
37    /// Key's most recent login time via SFTP
38    pub last_login_at: Option<String>,
39
40    /// Only returned when generating keys. Can be invalid, not_generated, generating, complete
41    pub status: Option<String>,
42
43    /// Only returned when generating keys. Private key generated for the user
44    pub generated_private_key: Option<String>,
45
46    /// Only returned when generating keys. Public key generated for the user
47    pub generated_public_key: Option<String>,
48}
49
50/// Handler for public key operations
51#[derive(Debug, Clone)]
52pub struct PublicKeyHandler {
53    client: FilesClient,
54}
55
56impl PublicKeyHandler {
57    /// Creates a new PublicKeyHandler
58    pub fn new(client: FilesClient) -> Self {
59        Self { client }
60    }
61
62    /// List public keys
63    ///
64    /// # Arguments
65    ///
66    /// * `user_id` - User ID (use 0 for current user, None for all users if admin)
67    /// * `cursor` - Pagination cursor
68    /// * `per_page` - Number of records per page
69    ///
70    /// # Examples
71    ///
72    /// ```rust,no_run
73    /// # use files_sdk::{FilesClient, PublicKeyHandler};
74    /// # #[tokio::main]
75    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
76    /// let client = FilesClient::builder().api_key("key").build()?;
77    /// let handler = PublicKeyHandler::new(client);
78    ///
79    /// // List keys for current user
80    /// let (keys, pagination) = handler.list(Some(0), None, None).await?;
81    /// for key in keys {
82    ///     println!("{}: {}", key.title.unwrap_or_default(), key.fingerprint.unwrap_or_default());
83    /// }
84    /// # Ok(())
85    /// # }
86    /// ```
87    pub async fn list(
88        &self,
89        user_id: Option<i64>,
90        cursor: Option<String>,
91        per_page: Option<i64>,
92    ) -> Result<(Vec<PublicKeyEntity>, PaginationInfo)> {
93        let mut endpoint = "/public_keys".to_string();
94        let mut query_params = Vec::new();
95
96        if let Some(user_id) = user_id {
97            query_params.push(format!("user_id={}", user_id));
98        }
99
100        if let Some(cursor) = cursor {
101            query_params.push(format!("cursor={}", cursor));
102        }
103
104        if let Some(per_page) = per_page {
105            query_params.push(format!("per_page={}", per_page));
106        }
107
108        if !query_params.is_empty() {
109            endpoint.push('?');
110            endpoint.push_str(&query_params.join("&"));
111        }
112
113        let url = format!("{}{}", self.client.inner.base_url, endpoint);
114        let response = reqwest::Client::new()
115            .get(&url)
116            .header("X-FilesAPI-Key", &self.client.inner.api_key)
117            .send()
118            .await?;
119
120        let headers = response.headers().clone();
121        let pagination = PaginationInfo::from_headers(&headers);
122
123        let status = response.status();
124        if !status.is_success() {
125            return Err(crate::FilesError::ApiError {
126                endpoint: None,
127                code: status.as_u16(),
128                message: response.text().await.unwrap_or_default(),
129            });
130        }
131
132        let keys: Vec<PublicKeyEntity> = response.json().await?;
133        Ok((keys, pagination))
134    }
135
136    /// Get a specific public key by ID
137    ///
138    /// # Arguments
139    ///
140    /// * `id` - Public key ID
141    pub async fn get(&self, id: i64) -> Result<PublicKeyEntity> {
142        let endpoint = format!("/public_keys/{}", id);
143        let response = self.client.get_raw(&endpoint).await?;
144        Ok(serde_json::from_value(response)?)
145    }
146
147    /// Create a new public key by uploading an existing key
148    ///
149    /// # Arguments
150    ///
151    /// * `user_id` - User ID (use 0 for current user)
152    /// * `title` - Internal reference for key
153    /// * `public_key` - Actual contents of SSH key
154    pub async fn create(
155        &self,
156        user_id: i64,
157        title: &str,
158        public_key: &str,
159    ) -> Result<PublicKeyEntity> {
160        let body = json!({
161            "user_id": user_id,
162            "title": title,
163            "public_key": public_key,
164        });
165
166        let response = self.client.post_raw("/public_keys", body).await?;
167        Ok(serde_json::from_value(response)?)
168    }
169
170    /// Generate a new SSH key pair
171    ///
172    /// # Arguments
173    ///
174    /// * `user_id` - User ID (use 0 for current user)
175    /// * `title` - Internal reference for key
176    /// * `algorithm` - Type of key (rsa, dsa, ecdsa, ed25519)
177    /// * `length` - Length of key (or signature size for ecdsa)
178    /// * `password` - Password for the private key (optional)
179    pub async fn generate(
180        &self,
181        user_id: i64,
182        title: &str,
183        algorithm: &str,
184        length: Option<i64>,
185        password: Option<&str>,
186    ) -> Result<PublicKeyEntity> {
187        let mut body = json!({
188            "user_id": user_id,
189            "title": title,
190            "generate_keypair": true,
191            "generate_algorithm": algorithm,
192        });
193
194        if let Some(length) = length {
195            body["generate_length"] = json!(length);
196        }
197
198        if let Some(password) = password {
199            body["generate_private_key_password"] = json!(password);
200        }
201
202        let response = self.client.post_raw("/public_keys", body).await?;
203        Ok(serde_json::from_value(response)?)
204    }
205
206    /// Update a public key's title
207    ///
208    /// # Arguments
209    ///
210    /// * `id` - Public key ID
211    /// * `title` - New title for the key
212    pub async fn update(&self, id: i64, title: &str) -> Result<PublicKeyEntity> {
213        let body = json!({
214            "title": title,
215        });
216
217        let endpoint = format!("/public_keys/{}", id);
218        let response = self.client.patch_raw(&endpoint, body).await?;
219        Ok(serde_json::from_value(response)?)
220    }
221
222    /// Delete a public key
223    ///
224    /// # Arguments
225    ///
226    /// * `id` - Public key ID
227    pub async fn delete(&self, id: i64) -> Result<()> {
228        let endpoint = format!("/public_keys/{}", id);
229        self.client.delete_raw(&endpoint).await?;
230        Ok(())
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    #[test]
239    fn test_handler_creation() {
240        let client = FilesClient::builder().api_key("test-key").build().unwrap();
241        let _handler = PublicKeyHandler::new(client);
242    }
243}