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    #[serde(rename = "google_cloud_kms")]
54    GoogleCloudKms {
55        service_account: GoogleCloudKmsSignerServiceAccountResponseConfig,
56        key: GoogleCloudKmsSignerKeyResponseConfig,
57    },
58    Plain {},
59}
60
61#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
62pub struct GoogleCloudKmsSignerServiceAccountResponseConfig {
63    pub project_id: String,
64    pub client_id: String,
65    pub auth_uri: String,
66    pub token_uri: String,
67    pub auth_provider_x509_cert_url: String,
68    pub client_x509_cert_url: String,
69    pub universe_domain: String,
70    // pub private_key: Option<String>, hidden from response due to security concerns
71    // pub private_key_id: Option<String>, hidden from response due to security concerns
72    // pub client_email: Option<String>, hidden from response due to security concerns
73}
74
75#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
76pub struct GoogleCloudKmsSignerKeyResponseConfig {
77    pub location: String,
78    pub key_ring_id: String,
79    pub key_id: String,
80    pub key_version: u32,
81}
82
83impl From<SignerConfig> for SignerConfigResponse {
84    fn from(config: SignerConfig) -> Self {
85        match config {
86            SignerConfig::Local(_) => SignerConfigResponse::Plain {},
87            SignerConfig::Vault(c) => SignerConfigResponse::Vault {
88                address: c.address,
89                namespace: c.namespace,
90                key_name: c.key_name,
91                mount_point: c.mount_point,
92            },
93            SignerConfig::VaultTransit(c) => SignerConfigResponse::VaultTransit {
94                key_name: c.key_name,
95                address: c.address,
96                namespace: c.namespace,
97                pubkey: c.pubkey,
98                mount_point: c.mount_point,
99            },
100            SignerConfig::AwsKms(c) => SignerConfigResponse::AwsKms {
101                region: c.region,
102                key_id: c.key_id,
103            },
104            SignerConfig::Turnkey(c) => SignerConfigResponse::Turnkey {
105                api_public_key: c.api_public_key,
106                organization_id: c.organization_id,
107                private_key_id: c.private_key_id,
108                public_key: c.public_key,
109            },
110            SignerConfig::GoogleCloudKms(c) => SignerConfigResponse::GoogleCloudKms {
111                service_account: GoogleCloudKmsSignerServiceAccountResponseConfig {
112                    project_id: c.service_account.project_id,
113                    client_id: c.service_account.client_id,
114                    auth_uri: c.service_account.auth_uri,
115                    token_uri: c.service_account.token_uri,
116                    auth_provider_x509_cert_url: c.service_account.auth_provider_x509_cert_url,
117                    client_x509_cert_url: c.service_account.client_x509_cert_url,
118                    universe_domain: c.service_account.universe_domain,
119                },
120                key: GoogleCloudKmsSignerKeyResponseConfig {
121                    location: c.key.location,
122                    key_ring_id: c.key.key_ring_id,
123                    key_id: c.key.key_id,
124                    key_version: c.key.key_version,
125                },
126            },
127        }
128    }
129}
130
131#[derive(Debug, Serialize, Deserialize, ToSchema)]
132pub struct SignerResponse {
133    /// The unique identifier of the signer
134    pub id: String,
135    /// The type of signer (local, aws_kms, google_cloud_kms, vault, etc.)
136    pub r#type: SignerType,
137    /// Non-secret configuration details
138    pub config: SignerConfigResponse,
139}
140
141impl From<SignerRepoModel> for SignerResponse {
142    fn from(repo_model: SignerRepoModel) -> Self {
143        // Convert to domain model
144        let domain_signer = Signer::from(repo_model);
145
146        Self {
147            id: domain_signer.id.clone(),
148            r#type: domain_signer.signer_type(),
149            config: SignerConfigResponse::from(domain_signer.config),
150        }
151    }
152}
153
154impl From<Signer> for SignerResponse {
155    fn from(signer: Signer) -> Self {
156        Self {
157            id: signer.id.clone(),
158            r#type: signer.signer_type(),
159            config: SignerConfigResponse::from(signer.config),
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use crate::models::{LocalSignerConfigStorage, SignerConfigStorage};
168    use secrets::SecretVec;
169
170    #[test]
171    fn test_signer_response_from_repo_model() {
172        let repo_model = SignerRepoModel {
173            id: "test-signer".to_string(),
174            config: SignerConfigStorage::Local(LocalSignerConfigStorage {
175                raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
176            }),
177        };
178
179        let response = SignerResponse::from(repo_model);
180
181        assert_eq!(response.id, "test-signer");
182        assert_eq!(response.r#type, SignerType::Local);
183        assert_eq!(response.config, SignerConfigResponse::Plain {});
184    }
185
186    #[test]
187    fn test_signer_response_from_domain_model() {
188        use crate::models::signer::{AwsKmsSignerConfig, SignerConfig};
189
190        let aws_config = AwsKmsSignerConfig {
191            key_id: "test-key-id".to_string(),
192            region: Some("us-east-1".to_string()),
193        };
194
195        let signer = crate::models::Signer::new(
196            "domain-signer".to_string(),
197            SignerConfig::AwsKms(aws_config),
198        );
199
200        let response = SignerResponse::from(signer);
201
202        assert_eq!(response.id, "domain-signer");
203        assert_eq!(response.r#type, SignerType::AwsKms);
204        assert_eq!(
205            response.config,
206            SignerConfigResponse::AwsKms {
207                region: Some("us-east-1".to_string()),
208                key_id: "test-key-id".to_string(),
209            }
210        );
211    }
212
213    #[test]
214    fn test_signer_type_mapping_from_config() {
215        let test_cases = vec![
216            (
217                SignerConfigStorage::Local(LocalSignerConfigStorage {
218                    raw_key: SecretVec::new(32, |v| v.copy_from_slice(&[1; 32])),
219                }),
220                SignerType::Local,
221                SignerConfigResponse::Plain {},
222            ),
223            (
224                SignerConfigStorage::AwsKms(crate::models::AwsKmsSignerConfigStorage {
225                    region: Some("us-east-1".to_string()),
226                    key_id: "test-key".to_string(),
227                }),
228                SignerType::AwsKms,
229                SignerConfigResponse::AwsKms {
230                    region: Some("us-east-1".to_string()),
231                    key_id: "test-key".to_string(),
232                },
233            ),
234        ];
235
236        for (config, expected_type, expected_config) in test_cases {
237            let repo_model = SignerRepoModel {
238                id: "test".to_string(),
239                config,
240            };
241
242            let response = SignerResponse::from(repo_model);
243            assert_eq!(
244                response.r#type, expected_type,
245                "Type mapping failed for {:?}",
246                expected_type
247            );
248            assert_eq!(response.config, expected_config);
249        }
250    }
251
252    #[test]
253    fn test_response_serialization() {
254        let response = SignerResponse {
255            id: "test-signer".to_string(),
256            r#type: SignerType::Local,
257            config: SignerConfigResponse::Plain {},
258        };
259
260        let json = serde_json::to_string(&response).unwrap();
261        assert!(json.contains("\"id\":\"test-signer\""));
262        assert!(json.contains("\"type\":\"local\""));
263    }
264
265    #[test]
266    fn test_response_deserialization() {
267        let json = r#"{
268            "id": "test-signer",
269            "type": "aws_kms",
270            "config": {
271                "region": "us-east-1",
272                "key_id": "test-key-id"
273            }
274        }"#;
275
276        let response: SignerResponse = serde_json::from_str(json).unwrap();
277        assert_eq!(response.id, "test-signer");
278        assert_eq!(response.r#type, SignerType::AwsKms);
279        assert_eq!(
280            response.config,
281            SignerConfigResponse::AwsKms {
282                region: Some("us-east-1".to_string()),
283                key_id: "test-key-id".to_string(),
284            }
285        );
286    }
287
288    #[test]
289    fn test_response_deserialization_all_types() {
290        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}}}"#;
291
292        let response: SignerResponse = serde_json::from_str(json).unwrap();
293        assert_eq!(response.r#type, SignerType::GoogleCloudKms);
294    }
295}