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