openzeppelin_relayer/models/signer/
response.rs

1//! API response models for signer endpoints.
2//!
3//! This module handles outgoing HTTP responses for signer operations, providing:
4//!
5//! - **Response Models**: Structures for returning signer data via API
6//! - **Data Sanitization**: Ensures sensitive information is not exposed
7//! - **Domain Conversion**: Transformation from domain/repository objects to API responses
8//!
9//! Serves as the exit point for signer data to external clients, ensuring
10//! proper data formatting and security considerations.
11
12use crate::models::{Signer, SignerConfig, SignerRepoModel, SignerType};
13use serde::{Deserialize, Serialize};
14use utoipa::ToSchema;
15
16/// Signer configuration response
17/// Does not include sensitive information like private keys
18#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
19#[serde(untagged)]
20#[serde(rename_all = "lowercase")]
21pub enum SignerConfigResponse {
22    #[serde(rename = "plain")]
23    Vault {
24        address: String,
25        namespace: Option<String>,
26        key_name: String,
27        mount_point: Option<String>,
28        // role_id: Option<String>, hidden from response due to security concerns
29        // secret_id: Option<String>, hidden from response due to security concerns
30    },
31    #[serde(rename = "vault_transit")]
32    VaultTransit {
33        key_name: String,
34        address: String,
35        namespace: Option<String>,
36        pubkey: String,
37        mount_point: Option<String>,
38        // role_id: Option<String>, hidden from response due to security concerns
39        // secret_id: Option<String>, hidden from response due to security concerns
40    },
41    #[serde(rename = "aws_kms")]
42    AwsKms {
43        region: Option<String>,
44        key_id: String,
45    },
46    Turnkey {
47        api_public_key: String,
48        organization_id: String,
49        private_key_id: String,
50        public_key: String,
51        // api_private_key: Option<String>, hidden from response due to security concerns
52    },
53    Cdp {
54        api_key_id: String,
55        account_address: String,
56        // api_key_secret: SecretString, hidden from response due to security concerns
57        // wallet_secret: SecretString, hidden from response due to security concerns
58    },
59    #[serde(rename = "google_cloud_kms")]
60    GoogleCloudKms {
61        service_account: GoogleCloudKmsSignerServiceAccountResponseConfig,
62        key: GoogleCloudKmsSignerKeyResponseConfig,
63    },
64    Plain {},
65}
66
67#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
68pub struct GoogleCloudKmsSignerServiceAccountResponseConfig {
69    pub project_id: String,
70    pub client_id: String,
71    pub auth_uri: String,
72    pub token_uri: String,
73    pub auth_provider_x509_cert_url: String,
74    pub client_x509_cert_url: String,
75    pub universe_domain: String,
76    // pub private_key: Option<String>, hidden from response due to security concerns
77    // pub private_key_id: Option<String>, hidden from response due to security concerns
78    // pub client_email: Option<String>, hidden from response due to security concerns
79}
80
81#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
82pub struct GoogleCloudKmsSignerKeyResponseConfig {
83    pub location: String,
84    pub key_ring_id: String,
85    pub key_id: String,
86    pub key_version: u32,
87}
88
89impl From<SignerConfig> for SignerConfigResponse {
90    fn from(config: SignerConfig) -> Self {
91        match config {
92            SignerConfig::Local(_) => SignerConfigResponse::Plain {},
93            SignerConfig::Vault(c) => SignerConfigResponse::Vault {
94                address: c.address,
95                namespace: c.namespace,
96                key_name: c.key_name,
97                mount_point: c.mount_point,
98            },
99            SignerConfig::VaultTransit(c) => SignerConfigResponse::VaultTransit {
100                key_name: c.key_name,
101                address: c.address,
102                namespace: c.namespace,
103                pubkey: c.pubkey,
104                mount_point: c.mount_point,
105            },
106            SignerConfig::AwsKms(c) => SignerConfigResponse::AwsKms {
107                region: c.region,
108                key_id: c.key_id,
109            },
110            SignerConfig::Turnkey(c) => SignerConfigResponse::Turnkey {
111                api_public_key: c.api_public_key,
112                organization_id: c.organization_id,
113                private_key_id: c.private_key_id,
114                public_key: c.public_key,
115            },
116            SignerConfig::Cdp(c) => SignerConfigResponse::Cdp {
117                api_key_id: c.api_key_id,
118                account_address: c.account_address,
119            },
120            SignerConfig::GoogleCloudKms(c) => SignerConfigResponse::GoogleCloudKms {
121                service_account: GoogleCloudKmsSignerServiceAccountResponseConfig {
122                    project_id: c.service_account.project_id,
123                    client_id: c.service_account.client_id,
124                    auth_uri: c.service_account.auth_uri,
125                    token_uri: c.service_account.token_uri,
126                    auth_provider_x509_cert_url: c.service_account.auth_provider_x509_cert_url,
127                    client_x509_cert_url: c.service_account.client_x509_cert_url,
128                    universe_domain: c.service_account.universe_domain,
129                },
130                key: GoogleCloudKmsSignerKeyResponseConfig {
131                    location: c.key.location,
132                    key_ring_id: c.key.key_ring_id,
133                    key_id: c.key.key_id,
134                    key_version: c.key.key_version,
135                },
136            },
137        }
138    }
139}
140
141#[derive(Debug, Serialize, Deserialize, ToSchema)]
142pub struct SignerResponse {
143    /// The unique identifier of the signer
144    pub id: String,
145    /// The type of signer (local, aws_kms, google_cloud_kms, vault, etc.)
146    pub r#type: SignerType,
147    /// Non-secret configuration details
148    pub config: SignerConfigResponse,
149}
150
151impl From<SignerRepoModel> for SignerResponse {
152    fn from(repo_model: SignerRepoModel) -> Self {
153        // Convert to domain model
154        let domain_signer = Signer::from(repo_model);
155
156        Self {
157            id: domain_signer.id.clone(),
158            r#type: domain_signer.signer_type(),
159            config: SignerConfigResponse::from(domain_signer.config),
160        }
161    }
162}
163
164impl From<Signer> for SignerResponse {
165    fn from(signer: Signer) -> Self {
166        Self {
167            id: signer.id.clone(),
168            r#type: signer.signer_type(),
169            config: SignerConfigResponse::from(signer.config),
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use crate::models::{LocalSignerConfigStorage, SignerConfigStorage};
178    use secrets::SecretVec;
179
180    #[test]
181    fn test_signer_response_from_repo_model() {
182        let repo_model = SignerRepoModel {
183            id: "test-signer".to_string(),
184            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
185                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
186            }),
187        };
188
189        let response = SignerResponse::from(repo_model);
190
191        assert_eq!(response.id, "test-signer");
192        assert_eq!(response.r#type, SignerType::Local);
193        assert_eq!(response.config, SignerConfigResponse::Plain {});
194    }
195
196    #[test]
197    fn test_signer_response_from_domain_model() {
198        use crate::models::signer::{AwsKmsSignerConfig, SignerConfig};
199
200        let aws_config = AwsKmsSignerConfig {
201            key_id: "test-key-id".to_string(),
202            region: Some("us-east-1".to_string()),
203        };
204
205        let signer = crate::models::Signer::new(
206            "domain-signer".to_string(),
207            SignerConfig::AwsKms(aws_config),
208        );
209
210        let response = SignerResponse::from(signer);
211
212        assert_eq!(response.id, "domain-signer");
213        assert_eq!(response.r#type, SignerType::AwsKms);
214        assert_eq!(
215            response.config,
216            SignerConfigResponse::AwsKms {
217                region: Some("us-east-1".to_string()),
218                key_id: "test-key-id".to_string(),
219            }
220        );
221    }
222
223    #[test]
224    fn test_signer_type_mapping_from_config() {
225        let test_cases = vec![
226            (
227                SignerConfigStorage::Local(LocalSignerConfigStorage {
228                    raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
229                }),
230                SignerType::Local,
231                SignerConfigResponse::Plain {},
232            ),
233            (
234                SignerConfigStorage::AwsKms(crate::models::AwsKmsSignerConfigStorage {
235                    region: Some("us-east-1".to_string()),
236                    key_id: "test-key".to_string(),
237                }),
238                SignerType::AwsKms,
239                SignerConfigResponse::AwsKms {
240                    region: Some("us-east-1".to_string()),
241                    key_id: "test-key".to_string(),
242                },
243            ),
244        ];
245
246        for (config, expected_type, expected_config) in test_cases {
247            let repo_model = SignerRepoModel {
248                id: "test".to_string(),
249                config,
250            };
251
252            let response = SignerResponse::from(repo_model);
253            assert_eq!(
254                response.r#type, expected_type,
255                "Type mapping failed for {:?}",
256                expected_type
257            );
258            assert_eq!(response.config, expected_config);
259        }
260    }
261
262    #[test]
263    fn test_response_serialization() {
264        let response = SignerResponse {
265            id: "test-signer".to_string(),
266            r#type: SignerType::Local,
267            config: SignerConfigResponse::Plain {},
268        };
269
270        let json = serde_json::to_string(&response).unwrap();
271        assert!(json.contains("\"id\":\"test-signer\""));
272        assert!(json.contains("\"type\":\"local\""));
273    }
274
275    #[test]
276    fn test_response_deserialization() {
277        let json = r#"{
278            "id": "test-signer",
279            "type": "aws_kms",
280            "config": {
281                "region": "us-east-1",
282                "key_id": "test-key-id"
283            }
284        }"#;
285
286        let response: SignerResponse = serde_json::from_str(json).unwrap();
287        assert_eq!(response.id, "test-signer");
288        assert_eq!(response.r#type, SignerType::AwsKms);
289        assert_eq!(
290            response.config,
291            SignerConfigResponse::AwsKms {
292                region: Some("us-east-1".to_string()),
293                key_id: "test-key-id".to_string(),
294            }
295        );
296    }
297
298    #[test]
299    fn test_response_deserialization_all_types() {
300        let json = r#"{"id": "test", "type": "google_cloud_kms", "config": {"service_account": {"project_id": "proj", "client_id": "cid", "auth_uri": "auth", "token_uri": "token", "auth_provider_x509_cert_url": "cert", "client_x509_cert_url": "client_cert", "universe_domain": "domain"}, "key": {"location": "loc", "key_ring_id": "ring", "key_id": "key", "key_version": 1}}}"#;
301
302        let response: SignerResponse = serde_json::from_str(json).unwrap();
303        assert_eq!(response.r#type, SignerType::GoogleCloudKms);
304    }
305
306    #[test]
307    fn test_cdp_signer_response_conversion() {
308        use crate::models::signer::{CdpSignerConfig, SignerConfig};
309        use crate::models::SecretString;
310
311        let cdp_config = CdpSignerConfig {
312            api_key_id: "test-api-key-id".to_string(),
313            api_key_secret: SecretString::new("secret"),
314            wallet_secret: SecretString::new("wallet-secret"),
315            account_address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string(),
316        };
317
318        let signer =
319            crate::models::Signer::new("cdp-signer".to_string(), SignerConfig::Cdp(cdp_config));
320
321        let response = SignerResponse::from(signer);
322
323        assert_eq!(response.id, "cdp-signer");
324        assert_eq!(response.r#type, SignerType::Cdp);
325        assert_eq!(
326            response.config,
327            SignerConfigResponse::Cdp {
328                api_key_id: "test-api-key-id".to_string(),
329                account_address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string(),
330            }
331        );
332    }
333
334    #[test]
335    fn test_cdp_response_serialization() {
336        let response = SignerResponse {
337            id: "test-cdp-signer".to_string(),
338            r#type: SignerType::Cdp,
339            config: SignerConfigResponse::Cdp {
340                api_key_id: "test-api-key-id".to_string(),
341                account_address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string(),
342            },
343        };
344
345        let json = serde_json::to_string(&response).unwrap();
346        assert!(json.contains("\"id\":\"test-cdp-signer\""));
347        assert!(json.contains("\"type\":\"cdp\""));
348        assert!(json.contains("\"api_key_id\":\"test-api-key-id\""));
349        assert!(json.contains("\"account_address\":\"0x742d35Cc6634C0532925a3b844Bc454e4438f44f\""));
350
351        // Verify that secrets are not included
352        assert!(!json.contains("api_key_secret"));
353        assert!(!json.contains("wallet_secret"));
354    }
355
356    #[test]
357    fn test_cdp_response_deserialization() {
358        let json = r#"{
359            "id": "test-cdp-signer",
360            "type": "cdp",
361            "config": {
362                "api_key_id": "test-api-key-id",
363                "account_address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44f"
364            }
365        }"#;
366
367        let response: SignerResponse = serde_json::from_str(json).unwrap();
368        assert_eq!(response.id, "test-cdp-signer");
369        assert_eq!(response.r#type, SignerType::Cdp);
370        assert_eq!(
371            response.config,
372            SignerConfigResponse::Cdp {
373                api_key_id: "test-api-key-id".to_string(),
374                account_address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string(),
375            }
376        );
377    }
378}