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                code: status.as_u16(),
127                message: response.text().await.unwrap_or_default(),
128            });
129        }
130
131        let keys: Vec<PublicKeyEntity> = response.json().await?;
132        Ok((keys, pagination))
133    }
134
135    /// Get a specific public key by ID
136    ///
137    /// # Arguments
138    ///
139    /// * `id` - Public key ID
140    pub async fn get(&self, id: i64) -> Result<PublicKeyEntity> {
141        let endpoint = format!("/public_keys/{}", id);
142        let response = self.client.get_raw(&endpoint).await?;
143        Ok(serde_json::from_value(response)?)
144    }
145
146    /// Create a new public key by uploading an existing key
147    ///
148    /// # Arguments
149    ///
150    /// * `user_id` - User ID (use 0 for current user)
151    /// * `title` - Internal reference for key
152    /// * `public_key` - Actual contents of SSH key
153    pub async fn create(
154        &self,
155        user_id: i64,
156        title: &str,
157        public_key: &str,
158    ) -> Result<PublicKeyEntity> {
159        let body = json!({
160            "user_id": user_id,
161            "title": title,
162            "public_key": public_key,
163        });
164
165        let response = self.client.post_raw("/public_keys", body).await?;
166        Ok(serde_json::from_value(response)?)
167    }
168
169    /// Generate a new SSH key pair
170    ///
171    /// # Arguments
172    ///
173    /// * `user_id` - User ID (use 0 for current user)
174    /// * `title` - Internal reference for key
175    /// * `algorithm` - Type of key (rsa, dsa, ecdsa, ed25519)
176    /// * `length` - Length of key (or signature size for ecdsa)
177    /// * `password` - Password for the private key (optional)
178    pub async fn generate(
179        &self,
180        user_id: i64,
181        title: &str,
182        algorithm: &str,
183        length: Option<i64>,
184        password: Option<&str>,
185    ) -> Result<PublicKeyEntity> {
186        let mut body = json!({
187            "user_id": user_id,
188            "title": title,
189            "generate_keypair": true,
190            "generate_algorithm": algorithm,
191        });
192
193        if let Some(length) = length {
194            body["generate_length"] = json!(length);
195        }
196
197        if let Some(password) = password {
198            body["generate_private_key_password"] = json!(password);
199        }
200
201        let response = self.client.post_raw("/public_keys", body).await?;
202        Ok(serde_json::from_value(response)?)
203    }
204
205    /// Update a public key's title
206    ///
207    /// # Arguments
208    ///
209    /// * `id` - Public key ID
210    /// * `title` - New title for the key
211    pub async fn update(&self, id: i64, title: &str) -> Result<PublicKeyEntity> {
212        let body = json!({
213            "title": title,
214        });
215
216        let endpoint = format!("/public_keys/{}", id);
217        let response = self.client.patch_raw(&endpoint, body).await?;
218        Ok(serde_json::from_value(response)?)
219    }
220
221    /// Delete a public key
222    ///
223    /// # Arguments
224    ///
225    /// * `id` - Public key ID
226    pub async fn delete(&self, id: i64) -> Result<()> {
227        let endpoint = format!("/public_keys/{}", id);
228        self.client.delete_raw(&endpoint).await?;
229        Ok(())
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn test_handler_creation() {
239        let client = FilesClient::builder().api_key("test-key").build().unwrap();
240        let _handler = PublicKeyHandler::new(client);
241    }
242}