openzeppelin_relayer/models/signer/
request.rs

1//! API request models and validation for signer endpoints.
2//!
3//! This module handles incoming HTTP requests for signer operations, providing:
4//!
5//! - **Request Models**: Structures for creating and updating signers via API
6//! - **Input Validation**: Sanitization and validation of user-provided data
7//! - **Domain Conversion**: Transformation from API requests to domain objects
8//!
9//! Serves as the entry point for signer data from external clients, ensuring
10//! all input is properly validated before reaching the core business logic.
11
12use crate::models::{
13    ApiError, AwsKmsSignerConfig, AzureKeyVaultAuthType, AzureKeyVaultSignerConfig,
14    CdpSignerConfig, GoogleCloudKmsSignerConfig, GoogleCloudKmsSignerKeyConfig,
15    GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig, SecretString, Signer,
16    SignerConfig, TurnkeySignerConfig, VaultSignerConfig, VaultTransitSignerConfig,
17};
18use secrets::SecretVec;
19use serde::{Deserialize, Serialize};
20use utoipa::ToSchema;
21use zeroize::Zeroize;
22
23/// Local signer configuration for API requests
24#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
25#[serde(deny_unknown_fields)]
26pub struct LocalSignerRequestConfig {
27    pub key: String,
28}
29
30/// AWS KMS signer configuration for API requests
31#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
32#[serde(deny_unknown_fields)]
33pub struct AwsKmsSignerRequestConfig {
34    pub region: String,
35    pub key_id: String,
36}
37
38/// Azure Key Vault signer configuration for API requests
39#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
40#[serde(deny_unknown_fields)]
41pub struct AzureKeyVaultSignerRequestConfig {
42    pub auth_type: Option<AzureKeyVaultAuthType>,
43    pub tenant_id: Option<String>,
44    pub client_id: Option<String>,
45    pub client_secret: Option<String>,
46    pub federated_token_file: Option<String>,
47    pub vault_url: String,
48    pub key_name: String,
49    #[schema(nullable = false)]
50    pub key_version: Option<String>,
51}
52
53/// Vault signer configuration for API requests
54#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
55#[serde(deny_unknown_fields)]
56pub struct VaultSignerRequestConfig {
57    pub address: String,
58    #[schema(nullable = false)]
59    pub namespace: Option<String>,
60    pub role_id: String,
61    pub secret_id: String,
62    pub key_name: String,
63    #[schema(nullable = false)]
64    pub mount_point: Option<String>,
65}
66
67/// Vault Transit signer configuration for API requests
68#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
69#[serde(deny_unknown_fields)]
70pub struct VaultTransitSignerRequestConfig {
71    pub key_name: String,
72    pub address: String,
73    #[schema(nullable = false)]
74    pub namespace: Option<String>,
75    pub role_id: String,
76    pub secret_id: String,
77    pub pubkey: String,
78    #[schema(nullable = false)]
79    pub mount_point: Option<String>,
80}
81
82/// Turnkey signer configuration for API requests
83#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
84#[serde(deny_unknown_fields)]
85pub struct TurnkeySignerRequestConfig {
86    pub api_public_key: String,
87    pub api_private_key: String,
88    pub organization_id: String,
89    pub private_key_id: String,
90    pub public_key: String,
91}
92
93/// Google Cloud KMS service account configuration for API requests
94#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
95#[serde(deny_unknown_fields)]
96pub struct GoogleCloudKmsSignerServiceAccountRequestConfig {
97    pub private_key: String,
98    pub private_key_id: String,
99    pub project_id: String,
100    pub client_email: String,
101    pub client_id: String,
102    pub auth_uri: String,
103    pub token_uri: String,
104    pub auth_provider_x509_cert_url: String,
105    pub client_x509_cert_url: String,
106    pub universe_domain: String,
107}
108
109/// Google Cloud KMS key configuration for API requests
110#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
111#[serde(deny_unknown_fields)]
112pub struct GoogleCloudKmsSignerKeyRequestConfig {
113    pub location: String,
114    pub key_ring_id: String,
115    pub key_id: String,
116    pub key_version: u32,
117}
118
119/// Google Cloud KMS signer configuration for API requests
120#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
121#[serde(deny_unknown_fields)]
122pub struct GoogleCloudKmsSignerRequestConfig {
123    pub service_account: GoogleCloudKmsSignerServiceAccountRequestConfig,
124    pub key: GoogleCloudKmsSignerKeyRequestConfig,
125}
126
127/// CDP signer configuration for API requests
128#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
129#[serde(deny_unknown_fields)]
130pub struct CdpSignerRequestConfig {
131    pub api_key_id: String,
132    pub api_key_secret: String,
133    pub wallet_secret: String,
134    pub account_address: String,
135}
136
137/// Signer configuration enum for API requests (without type discriminator)
138#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
139#[serde(untagged)]
140pub enum SignerConfigRequest {
141    Local(LocalSignerRequestConfig),
142    AwsKms(AwsKmsSignerRequestConfig),
143    AzureKeyVault(AzureKeyVaultSignerRequestConfig),
144    Vault(VaultSignerRequestConfig),
145    VaultTransit(VaultTransitSignerRequestConfig),
146    Turnkey(TurnkeySignerRequestConfig),
147    Cdp(CdpSignerRequestConfig),
148    GoogleCloudKms(GoogleCloudKmsSignerRequestConfig),
149}
150
151/// Signer type enum for API requests
152#[derive(Debug, Serialize, Deserialize, ToSchema)]
153#[serde(rename_all = "lowercase")]
154pub enum SignerTypeRequest {
155    #[serde(rename = "plain")]
156    Local,
157    #[serde(rename = "aws_kms")]
158    AwsKms,
159    #[serde(rename = "azure_key_vault")]
160    AzureKeyVault,
161    Vault,
162    #[serde(rename = "vault_transit")]
163    VaultTransit,
164    Turnkey,
165    Cdp,
166    #[serde(rename = "google_cloud_kms")]
167    GoogleCloudKms,
168}
169
170impl zeroize::Zeroize for SignerTypeRequest {
171    fn zeroize(&mut self) {
172        // No sensitive data to zeroize in this enum
173    }
174}
175
176/// Request model for creating a new signer
177#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
178#[serde(deny_unknown_fields)]
179pub struct SignerCreateRequest {
180    /// Optional ID - if not provided, a UUID will be generated
181    #[schema(nullable = false)]
182    pub id: Option<String>,
183    /// The type of signer
184    #[serde(rename = "type")]
185    pub signer_type: SignerTypeRequest,
186    /// The signer configuration
187    pub config: SignerConfigRequest,
188}
189
190/// Request model for updating an existing signer
191/// At the moment, we don't allow updating signers
192#[derive(Debug, Serialize, Deserialize, ToSchema, Zeroize)]
193#[serde(deny_unknown_fields)]
194pub struct SignerUpdateRequest {}
195
196impl From<GoogleCloudKmsSignerServiceAccountRequestConfig>
197    for GoogleCloudKmsSignerServiceAccountConfig
198{
199    fn from(config: GoogleCloudKmsSignerServiceAccountRequestConfig) -> Self {
200        Self {
201            private_key: SecretString::new(&config.private_key),
202            private_key_id: SecretString::new(&config.private_key_id),
203            project_id: SecretString::new(&config.project_id),
204            client_email: SecretString::new(&config.client_email),
205            client_id: SecretString::new(&config.client_id),
206            auth_uri: SecretString::new(&config.auth_uri),
207            token_uri: SecretString::new(&config.token_uri),
208            auth_provider_x509_cert_url: SecretString::new(&config.auth_provider_x509_cert_url),
209            client_x509_cert_url: SecretString::new(&config.client_x509_cert_url),
210            universe_domain: SecretString::new(&config.universe_domain),
211        }
212    }
213}
214
215impl From<GoogleCloudKmsSignerKeyRequestConfig> for GoogleCloudKmsSignerKeyConfig {
216    fn from(config: GoogleCloudKmsSignerKeyRequestConfig) -> Self {
217        Self {
218            location: SecretString::new(&config.location),
219            key_ring_id: SecretString::new(&config.key_ring_id),
220            key_id: SecretString::new(&config.key_id),
221            key_version: config.key_version,
222        }
223    }
224}
225
226impl TryFrom<SignerConfigRequest> for SignerConfig {
227    type Error = ApiError;
228
229    fn try_from(config: SignerConfigRequest) -> Result<Self, Self::Error> {
230        let domain_config = match config {
231            SignerConfigRequest::Local(local_config) => {
232                // Decode hex string to raw bytes for cryptographic key
233                let key_bytes = hex::decode(&local_config.key)
234                    .map_err(|e| ApiError::BadRequest(format!(
235                        "Invalid hex key format: {e}. Key must be a 64-character hex string (32 bytes)."
236                    )))?;
237
238                let raw_key = SecretVec::new(key_bytes.len(), |buffer| {
239                    buffer.copy_from_slice(&key_bytes);
240                });
241
242                SignerConfig::Local(LocalSignerConfig { raw_key })
243            }
244            SignerConfigRequest::AwsKms(aws_config) => SignerConfig::AwsKms(AwsKmsSignerConfig {
245                region: Some(aws_config.region),
246                key_id: aws_config.key_id,
247            }),
248            SignerConfigRequest::AzureKeyVault(azure_config) => {
249                SignerConfig::AzureKeyVault(AzureKeyVaultSignerConfig {
250                    auth_type: azure_config.auth_type,
251                    tenant_id: azure_config
252                        .tenant_id
253                        .map(|value| SecretString::new(&value)),
254                    client_id: azure_config
255                        .client_id
256                        .map(|value| SecretString::new(&value)),
257                    client_secret: azure_config
258                        .client_secret
259                        .map(|value| SecretString::new(&value)),
260                    federated_token_file: azure_config
261                        .federated_token_file
262                        .map(|value| SecretString::new(&value)),
263                    vault_url: SecretString::new(&azure_config.vault_url),
264                    key_name: SecretString::new(&azure_config.key_name),
265                    key_version: azure_config.key_version,
266                })
267            }
268            SignerConfigRequest::Vault(vault_config) => SignerConfig::Vault(VaultSignerConfig {
269                address: vault_config.address,
270                namespace: vault_config.namespace,
271                role_id: SecretString::new(&vault_config.role_id),
272                secret_id: SecretString::new(&vault_config.secret_id),
273                key_name: vault_config.key_name,
274                mount_point: vault_config.mount_point,
275            }),
276            SignerConfigRequest::VaultTransit(vault_transit_config) => {
277                SignerConfig::VaultTransit(VaultTransitSignerConfig {
278                    key_name: vault_transit_config.key_name,
279                    address: vault_transit_config.address,
280                    namespace: vault_transit_config.namespace,
281                    role_id: SecretString::new(&vault_transit_config.role_id),
282                    secret_id: SecretString::new(&vault_transit_config.secret_id),
283                    pubkey: vault_transit_config.pubkey,
284                    mount_point: vault_transit_config.mount_point,
285                })
286            }
287            SignerConfigRequest::Turnkey(turnkey_config) => {
288                SignerConfig::Turnkey(TurnkeySignerConfig {
289                    api_public_key: turnkey_config.api_public_key,
290                    api_private_key: SecretString::new(&turnkey_config.api_private_key),
291                    organization_id: turnkey_config.organization_id,
292                    private_key_id: turnkey_config.private_key_id,
293                    public_key: turnkey_config.public_key,
294                })
295            }
296            SignerConfigRequest::Cdp(cdp_config) => SignerConfig::Cdp(CdpSignerConfig {
297                api_key_id: cdp_config.api_key_id,
298                api_key_secret: SecretString::new(&cdp_config.api_key_secret),
299                wallet_secret: SecretString::new(&cdp_config.wallet_secret),
300                account_address: cdp_config.account_address,
301            }),
302            SignerConfigRequest::GoogleCloudKms(gcp_kms_config) => {
303                SignerConfig::GoogleCloudKms(Box::new(GoogleCloudKmsSignerConfig {
304                    service_account: gcp_kms_config.service_account.into(),
305                    key: gcp_kms_config.key.into(),
306                }))
307            }
308        };
309
310        // Validate the configuration using domain model validation
311        domain_config.validate().map_err(ApiError::from)?;
312
313        Ok(domain_config)
314    }
315}
316
317impl TryFrom<SignerCreateRequest> for Signer {
318    type Error = ApiError;
319
320    fn try_from(request: SignerCreateRequest) -> Result<Self, Self::Error> {
321        // Generate UUID if no ID provided
322        let id = request
323            .id
324            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
325
326        // Validate that the signer type matches the config variant
327        let config_matches_type = matches!(
328            (&request.signer_type, &request.config),
329            (SignerTypeRequest::Local, SignerConfigRequest::Local(_))
330                | (SignerTypeRequest::AwsKms, SignerConfigRequest::AwsKms(_))
331                | (
332                    SignerTypeRequest::AzureKeyVault,
333                    SignerConfigRequest::AzureKeyVault(_)
334                )
335                | (SignerTypeRequest::Vault, SignerConfigRequest::Vault(_))
336                | (
337                    SignerTypeRequest::VaultTransit,
338                    SignerConfigRequest::VaultTransit(_)
339                )
340                | (SignerTypeRequest::Turnkey, SignerConfigRequest::Turnkey(_))
341                | (SignerTypeRequest::Cdp, SignerConfigRequest::Cdp(_))
342                | (
343                    SignerTypeRequest::GoogleCloudKms,
344                    SignerConfigRequest::GoogleCloudKms(_)
345                )
346        );
347
348        if !config_matches_type {
349            return Err(ApiError::BadRequest(format!(
350                "Signer type '{:?}' does not match the provided configuration",
351                request.signer_type
352            )));
353        }
354
355        // Convert request config to domain config (with validation)
356        let config = SignerConfig::try_from(request.config)?;
357
358        // Create the signer
359        let signer = Signer::new(id, config);
360
361        // Validate using domain model validation (this will also validate the config)
362        signer.validate().map_err(ApiError::from)?;
363
364        Ok(signer)
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371    use crate::models::signer::SignerType;
372
373    #[test]
374    fn test_json_deserialization_local_signer() {
375        let json = r#"{
376            "id": "test-local-signer",
377            "type": "plain",
378            "config": {
379                "key": "1111111111111111111111111111111111111111111111111111111111111111"
380            }
381        }"#;
382
383        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
384
385        assert!(
386            result.is_ok(),
387            "Failed to deserialize local signer: {:?}",
388            result.err()
389        );
390
391        let request = result.unwrap();
392        assert_eq!(request.id, Some("test-local-signer".to_string()));
393
394        match request.config {
395            SignerConfigRequest::Local(local_config) => {
396                assert_eq!(
397                    local_config.key,
398                    "1111111111111111111111111111111111111111111111111111111111111111"
399                );
400            }
401            _ => panic!("Expected Local config variant"),
402        }
403    }
404
405    #[test]
406    fn test_json_deserialization_aws_kms_signer() {
407        let json = r#"{
408            "id": "test-aws-signer",
409            "type": "aws_kms",
410            "config": {
411                "region": "us-east-1",
412                "key_id": "test-key-id"
413            }
414        }"#;
415
416        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
417
418        assert!(
419            result.is_ok(),
420            "Failed to deserialize AWS KMS signer: {:?}",
421            result.err()
422        );
423
424        let request = result.unwrap();
425        assert_eq!(request.id, Some("test-aws-signer".to_string()));
426
427        match request.config {
428            SignerConfigRequest::AwsKms(aws_config) => {
429                assert_eq!(aws_config.region, "us-east-1");
430                assert_eq!(aws_config.key_id, "test-key-id");
431            }
432            _ => panic!("Expected AwsKms config variant"),
433        }
434    }
435
436    #[test]
437    fn test_json_deserialization_vault_signer() {
438        let json = r#"{
439            "id": "test-vault-signer",
440            "type": "vault",
441            "config": {
442                "address": "https://vault.example.com",
443                "namespace": null,
444                "role_id": "test-role-id",
445                "secret_id": "test-secret-id",
446                "key_name": "test-key",
447                "mount_point": null
448            }
449        }"#;
450
451        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
452
453        assert!(
454            result.is_ok(),
455            "Failed to deserialize Vault signer: {:?}",
456            result.err()
457        );
458
459        let request = result.unwrap();
460        assert_eq!(request.id, Some("test-vault-signer".to_string()));
461
462        match request.config {
463            SignerConfigRequest::Vault(vault_config) => {
464                assert_eq!(vault_config.address, "https://vault.example.com");
465                assert_eq!(vault_config.namespace, None);
466                assert_eq!(vault_config.role_id, "test-role-id");
467                assert_eq!(vault_config.secret_id, "test-secret-id");
468                assert_eq!(vault_config.key_name, "test-key");
469                assert_eq!(vault_config.mount_point, None);
470            }
471            _ => panic!("Expected Vault config variant"),
472        }
473    }
474
475    #[test]
476    fn test_json_deserialization_turnkey_signer() {
477        let json = r#"{
478            "id": "test-turnkey-signer",
479            "type": "turnkey",
480            "config": {
481                "api_public_key": "test-public-key",
482                "api_private_key": "test-private-key",
483                "organization_id": "test-org",
484                "private_key_id": "test-private-key-id",
485                "public_key": "test-public-key"
486            }
487        }"#;
488
489        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
490
491        assert!(
492            result.is_ok(),
493            "Failed to deserialize Turnkey signer: {:?}",
494            result.err()
495        );
496
497        let request = result.unwrap();
498        assert_eq!(request.id, Some("test-turnkey-signer".to_string()));
499
500        match request.config {
501            SignerConfigRequest::Turnkey(turnkey_config) => {
502                assert_eq!(turnkey_config.api_public_key, "test-public-key");
503                assert_eq!(turnkey_config.api_private_key, "test-private-key");
504                assert_eq!(turnkey_config.organization_id, "test-org");
505                assert_eq!(turnkey_config.private_key_id, "test-private-key-id");
506                assert_eq!(turnkey_config.public_key, "test-public-key");
507            }
508            _ => panic!("Expected Turnkey config variant"),
509        }
510    }
511
512    #[test]
513    fn test_json_serialization_local_signer() {
514        let request = SignerCreateRequest {
515            id: Some("test-local-signer".to_string()),
516            signer_type: SignerTypeRequest::Local,
517            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
518                key: "1111111111111111111111111111111111111111111111111111111111111111".to_string(),
519            }),
520        };
521
522        let json_result = serde_json::to_string_pretty(&request);
523
524        assert!(
525            json_result.is_ok(),
526            "Failed to serialize local signer: {:?}",
527            json_result.err()
528        );
529
530        let json = json_result.unwrap();
531
532        // Verify it can be deserialized back
533        let deserialize_result: Result<SignerCreateRequest, _> = serde_json::from_str(&json);
534        assert!(
535            deserialize_result.is_ok(),
536            "Failed to deserialize back: {:?}",
537            deserialize_result.err()
538        );
539    }
540
541    #[test]
542    fn test_json_serialization_aws_kms_signer() {
543        let request = SignerCreateRequest {
544            id: Some("test-aws-signer".to_string()),
545            signer_type: SignerTypeRequest::AwsKms,
546            config: SignerConfigRequest::AwsKms(AwsKmsSignerRequestConfig {
547                region: "us-east-1".to_string(),
548                key_id: "test-key-id".to_string(),
549            }),
550        };
551
552        let json_result = serde_json::to_string_pretty(&request);
553
554        assert!(
555            json_result.is_ok(),
556            "Failed to serialize AWS KMS signer: {:?}",
557            json_result.err()
558        );
559
560        let json = json_result.unwrap();
561
562        // Verify it can be deserialized back
563        let deserialize_result: Result<SignerCreateRequest, _> = serde_json::from_str(&json);
564        assert!(
565            deserialize_result.is_ok(),
566            "Failed to deserialize back: {:?}",
567            deserialize_result.err()
568        );
569    }
570
571    #[test]
572    fn test_type_config_mismatch_validation() {
573        // Create a request where the type doesn't match the config
574        let json = r#"{
575            "id": "test-mismatch-signer",
576            "type": "aws_kms",
577            "config": {
578                "key": "1111111111111111111111111111111111111111111111111111111111111111"
579            }
580        }"#;
581
582        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
583
584        // This should deserialize successfully due to untagged enum
585        assert!(result.is_ok(), "JSON deserialization should succeed");
586
587        let request = result.unwrap();
588
589        // But the conversion to Signer should fail due to type mismatch validation
590        let signer_result = Signer::try_from(request);
591        assert!(
592            signer_result.is_err(),
593            "Type mismatch should cause validation error"
594        );
595
596        if let Err(ApiError::BadRequest(msg)) = signer_result {
597            assert!(
598                msg.contains("does not match"),
599                "Error should mention type mismatch: {msg}"
600            );
601        } else {
602            panic!("Expected BadRequest error for type mismatch");
603        }
604    }
605
606    // Keep existing tests for backward compatibility
607    #[test]
608    fn test_valid_aws_kms_create_request() {
609        let request = SignerCreateRequest {
610            id: Some("test-aws-signer".to_string()),
611            signer_type: SignerTypeRequest::AwsKms,
612            config: SignerConfigRequest::AwsKms(AwsKmsSignerRequestConfig {
613                region: "us-east-1".to_string(),
614                key_id: "test-key-id".to_string(),
615            }),
616        };
617
618        let result = Signer::try_from(request);
619        assert!(result.is_ok());
620
621        let signer = result.unwrap();
622        assert_eq!(signer.id, "test-aws-signer");
623        assert_eq!(signer.signer_type(), SignerType::AwsKms);
624
625        // Verify the config was properly converted
626        if let Some(aws_config) = signer.config.get_aws_kms() {
627            assert_eq!(aws_config.region, Some("us-east-1".to_string()));
628            assert_eq!(aws_config.key_id, "test-key-id");
629        } else {
630            panic!("Expected AWS KMS config");
631        }
632    }
633
634    #[test]
635    fn test_valid_vault_create_request() {
636        let request = SignerCreateRequest {
637            id: Some("test-vault-signer".to_string()),
638            signer_type: SignerTypeRequest::Vault,
639            config: SignerConfigRequest::Vault(VaultSignerRequestConfig {
640                address: "https://vault.example.com".to_string(),
641                namespace: None,
642                role_id: "test-role-id".to_string(),
643                secret_id: "test-secret-id".to_string(),
644                key_name: "test-key".to_string(),
645                mount_point: None,
646            }),
647        };
648
649        let result = Signer::try_from(request);
650        assert!(result.is_ok());
651
652        let signer = result.unwrap();
653        assert_eq!(signer.id, "test-vault-signer");
654        assert_eq!(signer.signer_type(), SignerType::Vault);
655    }
656
657    #[test]
658    fn test_invalid_aws_kms_empty_key_id() {
659        let request = SignerCreateRequest {
660            id: Some("test-signer".to_string()),
661            signer_type: SignerTypeRequest::AwsKms,
662            config: SignerConfigRequest::AwsKms(AwsKmsSignerRequestConfig {
663                region: "us-east-1".to_string(),
664                key_id: "".to_string(), // Empty key ID should fail validation
665            }),
666        };
667
668        let result = Signer::try_from(request);
669        assert!(result.is_err());
670
671        if let Err(ApiError::BadRequest(msg)) = result {
672            assert!(msg.contains("Key ID cannot be empty"));
673        } else {
674            panic!("Expected BadRequest error for empty key ID");
675        }
676    }
677
678    #[test]
679    fn test_invalid_vault_empty_address() {
680        let request = SignerCreateRequest {
681            id: Some("test-signer".to_string()),
682            signer_type: SignerTypeRequest::Vault,
683            config: SignerConfigRequest::Vault(VaultSignerRequestConfig {
684                address: "".to_string(), // Empty address should fail validation
685                namespace: None,
686                role_id: "test-role".to_string(),
687                secret_id: "test-secret".to_string(),
688                key_name: "test-key".to_string(),
689                mount_point: None,
690            }),
691        };
692
693        let result = Signer::try_from(request);
694        assert!(result.is_err());
695    }
696
697    #[test]
698    fn test_invalid_vault_invalid_url() {
699        let request = SignerCreateRequest {
700            id: Some("test-signer".to_string()),
701            signer_type: SignerTypeRequest::Vault,
702            config: SignerConfigRequest::Vault(VaultSignerRequestConfig {
703                address: "not-a-url".to_string(), // Invalid URL should fail validation
704                namespace: None,
705                role_id: "test-role".to_string(),
706                secret_id: "test-secret".to_string(),
707                key_name: "test-key".to_string(),
708                mount_point: None,
709            }),
710        };
711
712        let result = Signer::try_from(request);
713        assert!(result.is_err());
714
715        if let Err(ApiError::BadRequest(msg)) = result {
716            assert!(msg.contains("Address must be a valid URL"));
717        } else {
718            panic!("Expected BadRequest error for invalid URL");
719        }
720    }
721
722    #[test]
723    fn test_create_request_generates_uuid_when_no_id() {
724        let request = SignerCreateRequest {
725            id: None,
726            signer_type: SignerTypeRequest::Local,
727            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
728                key: "1111111111111111111111111111111111111111111111111111111111111111".to_string(), // 32 bytes as hex
729            }),
730        };
731
732        let result = Signer::try_from(request);
733        assert!(result.is_ok());
734
735        let signer = result.unwrap();
736        assert!(!signer.id.is_empty());
737        assert_eq!(signer.signer_type(), SignerType::Local);
738
739        // Verify it's a valid UUID format
740        assert!(uuid::Uuid::parse_str(&signer.id).is_ok());
741    }
742
743    #[test]
744    fn test_invalid_id_format() {
745        let request = SignerCreateRequest {
746            id: Some("invalid@id".to_string()), // Invalid characters
747            signer_type: SignerTypeRequest::Local,
748            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
749                key: "2222222222222222222222222222222222222222222222222222222222222222".to_string(), // 32 bytes as hex
750            }),
751        };
752
753        let result = Signer::try_from(request);
754        assert!(result.is_err());
755
756        if let Err(ApiError::BadRequest(msg)) = result {
757            assert!(msg.contains("ID must contain only letters, numbers, dashes and underscores"));
758        } else {
759            panic!("Expected BadRequest error with validation message");
760        }
761    }
762
763    #[test]
764    fn test_test_signer_creation() {
765        let request = SignerCreateRequest {
766            id: Some("test-signer".to_string()),
767            signer_type: SignerTypeRequest::Local,
768            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
769                key: "3333333333333333333333333333333333333333333333333333333333333333".to_string(), // 32 bytes as hex
770            }),
771        };
772
773        let result = Signer::try_from(request);
774        assert!(result.is_ok());
775
776        let signer = result.unwrap();
777        assert_eq!(signer.id, "test-signer");
778        assert_eq!(signer.signer_type(), SignerType::Local);
779    }
780
781    #[test]
782    fn test_local_signer_creation() {
783        let request = SignerCreateRequest {
784            id: Some("local-signer".to_string()),
785            signer_type: SignerTypeRequest::Local,
786            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
787                key: "4444444444444444444444444444444444444444444444444444444444444444".to_string(), // 32 bytes as hex
788            }),
789        };
790
791        let result = Signer::try_from(request);
792        assert!(result.is_ok());
793
794        let signer = result.unwrap();
795        assert_eq!(signer.id, "local-signer");
796        assert_eq!(signer.signer_type(), SignerType::Local);
797    }
798
799    #[test]
800    fn test_valid_turnkey_create_request() {
801        let request = SignerCreateRequest {
802            id: Some("test-turnkey-signer".to_string()),
803            signer_type: SignerTypeRequest::Turnkey,
804            config: SignerConfigRequest::Turnkey(TurnkeySignerRequestConfig {
805                api_public_key: "test-public-key".to_string(),
806                api_private_key: "test-private-key".to_string(),
807                organization_id: "test-org".to_string(),
808                private_key_id: "test-private-key-id".to_string(),
809                public_key: "test-public-key".to_string(),
810            }),
811        };
812
813        let result = Signer::try_from(request);
814        assert!(result.is_ok());
815
816        let signer = result.unwrap();
817        assert_eq!(signer.id, "test-turnkey-signer");
818        assert_eq!(signer.signer_type(), SignerType::Turnkey);
819
820        if let Some(turnkey_config) = signer.config.get_turnkey() {
821            assert_eq!(turnkey_config.api_public_key, "test-public-key");
822            assert_eq!(turnkey_config.organization_id, "test-org");
823        } else {
824            panic!("Expected Turnkey config");
825        }
826    }
827
828    #[test]
829    fn test_valid_vault_transit_create_request() {
830        let request = SignerCreateRequest {
831            id: Some("test-vault-transit-signer".to_string()),
832            signer_type: SignerTypeRequest::VaultTransit,
833            config: SignerConfigRequest::VaultTransit(VaultTransitSignerRequestConfig {
834                key_name: "test-key".to_string(),
835                address: "https://vault.example.com".to_string(),
836                namespace: None,
837                role_id: "test-role".to_string(),
838                secret_id: "test-secret".to_string(),
839                pubkey: "test-pubkey".to_string(),
840                mount_point: None,
841            }),
842        };
843
844        let result = Signer::try_from(request);
845        assert!(result.is_ok());
846
847        let signer = result.unwrap();
848        assert_eq!(signer.id, "test-vault-transit-signer");
849        assert_eq!(signer.signer_type(), SignerType::VaultTransit);
850    }
851
852    #[test]
853    fn test_valid_google_cloud_kms_create_request() {
854        let request = SignerCreateRequest {
855            id: Some("test-gcp-kms-signer".to_string()),
856            signer_type: SignerTypeRequest::GoogleCloudKms,
857            config: SignerConfigRequest::GoogleCloudKms(GoogleCloudKmsSignerRequestConfig {
858                service_account: GoogleCloudKmsSignerServiceAccountRequestConfig {
859                    private_key: "test-private-key".to_string(),
860                    private_key_id: "test-key-id".to_string(),
861                    project_id: "test-project".to_string(),
862                    client_email: "test@email.com".to_string(),
863                    client_id: "test-client-id".to_string(),
864                    auth_uri: "https://accounts.google.com/o/oauth2/auth".to_string(),
865                    token_uri: "https://oauth2.googleapis.com/token".to_string(),
866                    auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs".to_string(),
867                    client_x509_cert_url: "https://www.googleapis.com/robot/v1/metadata/x509/test%40test.iam.gserviceaccount.com".to_string(),
868                    universe_domain: "googleapis.com".to_string(),
869                },
870                key: GoogleCloudKmsSignerKeyRequestConfig {
871                    location: "global".to_string(),
872                    key_ring_id: "test-ring".to_string(),
873                    key_id: "test-key".to_string(),
874                    key_version: 1,
875                },
876            }),
877        };
878
879        let result = Signer::try_from(request);
880        assert!(result.is_ok());
881
882        let signer = result.unwrap();
883        assert_eq!(signer.id, "test-gcp-kms-signer");
884        assert_eq!(signer.signer_type(), SignerType::GoogleCloudKms);
885    }
886
887    #[test]
888    fn test_invalid_local_hex_key() {
889        let request = SignerCreateRequest {
890            id: Some("test-signer".to_string()),
891            signer_type: SignerTypeRequest::Local,
892            config: SignerConfigRequest::Local(LocalSignerRequestConfig {
893                key: "invalid-hex".to_string(), // Invalid hex
894            }),
895        };
896
897        let result = Signer::try_from(request);
898        assert!(result.is_err());
899        if let Err(ApiError::BadRequest(msg)) = result {
900            assert!(msg.contains("Invalid hex key format"));
901        }
902    }
903
904    #[test]
905    fn test_invalid_turnkey_empty_key() {
906        let request = SignerCreateRequest {
907            id: Some("test-signer".to_string()),
908            signer_type: SignerTypeRequest::Turnkey,
909            config: SignerConfigRequest::Turnkey(TurnkeySignerRequestConfig {
910                api_public_key: "".to_string(), // Empty
911                api_private_key: "test-private-key".to_string(),
912                organization_id: "test-org".to_string(),
913                private_key_id: "test-private-key-id".to_string(),
914                public_key: "test-public-key".to_string(),
915            }),
916        };
917
918        let result = Signer::try_from(request);
919        assert!(result.is_err());
920        if let Err(ApiError::BadRequest(msg)) = result {
921            assert!(msg.contains("API public key cannot be empty"));
922        }
923    }
924
925    #[test]
926    fn test_json_deserialization_cdp_signer() {
927        let json = r#"{
928            "id": "test-cdp-signer",
929            "type": "cdp",
930            "config": {
931                "api_key_id": "test-api-key-id",
932                "api_key_secret": "dGVzdC1hcGkta2V5LXNlY3JldA==",
933                "wallet_secret": "dGVzdC13YWxsZXQtc2VjcmV0",
934                "account_address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44f"
935            }
936        }"#;
937
938        let result: Result<SignerCreateRequest, _> = serde_json::from_str(json);
939
940        assert!(
941            result.is_ok(),
942            "Failed to deserialize CDP signer: {:?}",
943            result.err()
944        );
945
946        let request = result.unwrap();
947        assert_eq!(request.id, Some("test-cdp-signer".to_string()));
948
949        match request.config {
950            SignerConfigRequest::Cdp(cdp_config) => {
951                assert_eq!(cdp_config.api_key_id, "test-api-key-id");
952                assert_eq!(cdp_config.api_key_secret, "dGVzdC1hcGkta2V5LXNlY3JldA==");
953                assert_eq!(cdp_config.wallet_secret, "dGVzdC13YWxsZXQtc2VjcmV0");
954                assert_eq!(
955                    cdp_config.account_address,
956                    "0x742d35Cc6634C0532925a3b844Bc454e4438f44f"
957                );
958            }
959            _ => panic!("Expected CDP config variant"),
960        }
961    }
962
963    #[test]
964    fn test_valid_cdp_create_request() {
965        let request = SignerCreateRequest {
966            id: Some("test-cdp-signer".to_string()),
967            signer_type: SignerTypeRequest::Cdp,
968            config: SignerConfigRequest::Cdp(CdpSignerRequestConfig {
969                api_key_id: "test-api-key-id".to_string(),
970                api_key_secret: "dGVzdC1hcGkta2V5LXNlY3JldA==".to_string(), // Valid base64: "test-api-key-secret"
971                wallet_secret: "dGVzdC13YWxsZXQtc2VjcmV0".to_string(), // Valid base64: "test-wallet-secret"
972                account_address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string(),
973            }),
974        };
975
976        let result = Signer::try_from(request);
977        assert!(result.is_ok());
978
979        let signer = result.unwrap();
980        assert_eq!(signer.id, "test-cdp-signer");
981        assert_eq!(signer.signer_type(), SignerType::Cdp);
982
983        if let Some(cdp_config) = signer.config.get_cdp() {
984            assert_eq!(cdp_config.api_key_id, "test-api-key-id");
985            assert_eq!(
986                cdp_config.account_address,
987                "0x742d35Cc6634C0532925a3b844Bc454e4438f44f"
988            );
989        } else {
990            panic!("Expected CDP config");
991        }
992    }
993
994    #[test]
995    fn test_invalid_cdp_empty_api_key_id() {
996        let request = SignerCreateRequest {
997            id: Some("test-signer".to_string()),
998            signer_type: SignerTypeRequest::Cdp,
999            config: SignerConfigRequest::Cdp(CdpSignerRequestConfig {
1000                api_key_id: "".to_string(),                                 // Empty
1001                api_key_secret: "dGVzdC1hcGkta2V5LXNlY3JldA==".to_string(), // Valid base64: "test-api-key-secret"
1002                wallet_secret: "dGVzdC13YWxsZXQtc2VjcmV0".to_string(), // Valid base64: "test-wallet-secret"
1003                account_address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44f".to_string(),
1004            }),
1005        };
1006
1007        let result = Signer::try_from(request);
1008        assert!(result.is_err());
1009        if let Err(ApiError::BadRequest(msg)) = result {
1010            assert!(msg.contains("API Key ID cannot be empty"));
1011        }
1012    }
1013}