Skip to main content

slim_bindings/
identity_config.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use slim_auth::auth_provider::{AuthProvider, AuthVerifier};
5use slim_config::auth::identity::{
6    IdentityProviderConfig as CoreIdentityProviderConfig,
7    IdentityVerifierConfig as CoreIdentityVerifierConfig,
8};
9use slim_config::auth::jwt::Config as JwtAuthConfig;
10use slim_config::auth::jwt::{Claims as JwtClaims, JwtKey};
11use slim_config::auth::static_jwt::Config as StaticJwtConfig;
12use std::time::Duration;
13
14#[cfg_attr(target_family = "windows", allow(unused_imports))]
15use crate::common_config::SpireConfig;
16use crate::errors::SlimError;
17
18/// Static JWT (Bearer token) authentication configuration
19/// The token is loaded from a file and automatically reloaded when changed
20#[derive(uniffi::Record, Clone, Debug, PartialEq)]
21pub struct StaticJwtAuth {
22    /// Path to file containing the JWT token
23    pub token_file: String,
24    /// Duration for caching the token before re-reading from file (default: 3600 seconds)
25    pub duration: Duration,
26}
27
28impl From<StaticJwtAuth> for StaticJwtConfig {
29    fn from(config: StaticJwtAuth) -> Self {
30        StaticJwtConfig::with_file(&config.token_file).with_duration(config.duration)
31    }
32}
33
34impl From<StaticJwtConfig> for StaticJwtAuth {
35    fn from(config: StaticJwtConfig) -> Self {
36        StaticJwtAuth {
37            token_file: config.source().file.clone(),
38            duration: config.duration(),
39        }
40    }
41}
42
43/// JWT signing/verification algorithm
44#[derive(uniffi::Enum, Clone, Debug, PartialEq)]
45pub enum JwtAlgorithm {
46    HS256,
47    HS384,
48    HS512,
49    ES256,
50    ES384,
51    RS256,
52    RS384,
53    RS512,
54    PS256,
55    PS384,
56    PS512,
57    EdDSA,
58}
59
60impl From<JwtAlgorithm> for slim_auth::jwt::Algorithm {
61    fn from(algo: JwtAlgorithm) -> Self {
62        match algo {
63            JwtAlgorithm::HS256 => slim_auth::jwt::Algorithm::HS256,
64            JwtAlgorithm::HS384 => slim_auth::jwt::Algorithm::HS384,
65            JwtAlgorithm::HS512 => slim_auth::jwt::Algorithm::HS512,
66            JwtAlgorithm::ES256 => slim_auth::jwt::Algorithm::ES256,
67            JwtAlgorithm::ES384 => slim_auth::jwt::Algorithm::ES384,
68            JwtAlgorithm::RS256 => slim_auth::jwt::Algorithm::RS256,
69            JwtAlgorithm::RS384 => slim_auth::jwt::Algorithm::RS384,
70            JwtAlgorithm::RS512 => slim_auth::jwt::Algorithm::RS512,
71            JwtAlgorithm::PS256 => slim_auth::jwt::Algorithm::PS256,
72            JwtAlgorithm::PS384 => slim_auth::jwt::Algorithm::PS384,
73            JwtAlgorithm::PS512 => slim_auth::jwt::Algorithm::PS512,
74            JwtAlgorithm::EdDSA => slim_auth::jwt::Algorithm::EdDSA,
75        }
76    }
77}
78
79impl From<slim_auth::jwt::Algorithm> for JwtAlgorithm {
80    fn from(algo: slim_auth::jwt::Algorithm) -> Self {
81        match algo {
82            slim_auth::jwt::Algorithm::HS256 => JwtAlgorithm::HS256,
83            slim_auth::jwt::Algorithm::HS384 => JwtAlgorithm::HS384,
84            slim_auth::jwt::Algorithm::HS512 => JwtAlgorithm::HS512,
85            slim_auth::jwt::Algorithm::ES256 => JwtAlgorithm::ES256,
86            slim_auth::jwt::Algorithm::ES384 => JwtAlgorithm::ES384,
87            slim_auth::jwt::Algorithm::RS256 => JwtAlgorithm::RS256,
88            slim_auth::jwt::Algorithm::RS384 => JwtAlgorithm::RS384,
89            slim_auth::jwt::Algorithm::RS512 => JwtAlgorithm::RS512,
90            slim_auth::jwt::Algorithm::PS256 => JwtAlgorithm::PS256,
91            slim_auth::jwt::Algorithm::PS384 => JwtAlgorithm::PS384,
92            slim_auth::jwt::Algorithm::PS512 => JwtAlgorithm::PS512,
93            slim_auth::jwt::Algorithm::EdDSA => JwtAlgorithm::EdDSA,
94        }
95    }
96}
97
98/// JWT key format
99#[derive(uniffi::Enum, Clone, Debug, PartialEq)]
100pub enum JwtKeyFormat {
101    Pem,
102    Jwk,
103    Jwks,
104}
105
106impl From<JwtKeyFormat> for slim_auth::jwt::KeyFormat {
107    fn from(format: JwtKeyFormat) -> Self {
108        match format {
109            JwtKeyFormat::Pem => slim_auth::jwt::KeyFormat::Pem,
110            JwtKeyFormat::Jwk => slim_auth::jwt::KeyFormat::Jwk,
111            JwtKeyFormat::Jwks => slim_auth::jwt::KeyFormat::Jwks,
112        }
113    }
114}
115
116impl From<slim_auth::jwt::KeyFormat> for JwtKeyFormat {
117    fn from(format: slim_auth::jwt::KeyFormat) -> Self {
118        match format {
119            slim_auth::jwt::KeyFormat::Pem => JwtKeyFormat::Pem,
120            slim_auth::jwt::KeyFormat::Jwk => JwtKeyFormat::Jwk,
121            slim_auth::jwt::KeyFormat::Jwks => JwtKeyFormat::Jwks,
122        }
123    }
124}
125
126/// JWT key data source
127#[derive(uniffi::Enum, Clone, Debug, PartialEq)]
128pub enum JwtKeyData {
129    /// String with encoded key(s)
130    Data { value: String },
131    /// File path to the key(s)
132    File { path: String },
133}
134
135impl From<JwtKeyData> for slim_auth::jwt::KeyData {
136    fn from(data: JwtKeyData) -> Self {
137        match data {
138            JwtKeyData::Data { value } => slim_auth::jwt::KeyData::Data(value),
139            JwtKeyData::File { path } => slim_auth::jwt::KeyData::File(path),
140        }
141    }
142}
143
144impl From<slim_auth::jwt::KeyData> for JwtKeyData {
145    fn from(data: slim_auth::jwt::KeyData) -> Self {
146        match data {
147            slim_auth::jwt::KeyData::Data(value) => JwtKeyData::Data { value },
148            slim_auth::jwt::KeyData::File(path) => JwtKeyData::File { path },
149        }
150    }
151}
152
153/// JWT key configuration
154#[derive(uniffi::Record, Clone, Debug, PartialEq)]
155pub struct JwtKeyConfig {
156    /// Algorithm used for signing/verifying the JWT
157    pub algorithm: JwtAlgorithm,
158    /// Key format - PEM, JWK or JWKS
159    pub format: JwtKeyFormat,
160    /// Encoded key or file path
161    pub key: JwtKeyData,
162}
163
164impl From<JwtKeyConfig> for slim_auth::jwt::Key {
165    fn from(config: JwtKeyConfig) -> Self {
166        slim_auth::jwt::Key {
167            algorithm: config.algorithm.into(),
168            format: config.format.into(),
169            key: config.key.into(),
170        }
171    }
172}
173
174impl From<slim_auth::jwt::Key> for JwtKeyConfig {
175    fn from(key: slim_auth::jwt::Key) -> Self {
176        JwtKeyConfig {
177            algorithm: key.algorithm.into(),
178            format: key.format.into(),
179            key: key.key.into(),
180        }
181    }
182}
183
184/// JWT key type (encoding, decoding, or autoresolve)
185#[derive(uniffi::Enum, Clone, Debug, PartialEq)]
186pub enum JwtKeyType {
187    /// Encoding key for signing JWTs (client-side)
188    Encoding { key: JwtKeyConfig },
189    /// Decoding key for verifying JWTs (server-side)
190    Decoding { key: JwtKeyConfig },
191    /// Automatically resolve keys based on claims
192    Autoresolve,
193}
194
195impl From<JwtKeyType> for JwtKey {
196    fn from(key_type: JwtKeyType) -> Self {
197        match key_type {
198            JwtKeyType::Encoding { key } => JwtKey::Encoding(key.into()),
199            JwtKeyType::Decoding { key } => JwtKey::Decoding(key.into()),
200            JwtKeyType::Autoresolve => JwtKey::Autoresolve,
201        }
202    }
203}
204
205impl From<JwtKey> for JwtKeyType {
206    fn from(key: JwtKey) -> Self {
207        match key {
208            JwtKey::Encoding(k) => JwtKeyType::Encoding { key: k.into() },
209            JwtKey::Decoding(k) => JwtKeyType::Decoding { key: k.into() },
210            JwtKey::Autoresolve => JwtKeyType::Autoresolve,
211        }
212    }
213}
214
215/// JWT authentication configuration for client-side signing
216#[derive(uniffi::Record, Clone, Debug, PartialEq)]
217pub struct ClientJwtAuth {
218    /// JWT key configuration (encoding key for signing)
219    pub key: JwtKeyType,
220    /// JWT audience claims to include
221    pub audience: Option<Vec<String>>,
222    /// JWT issuer to include
223    pub issuer: Option<String>,
224    /// JWT subject to include
225    pub subject: Option<String>,
226    /// Token validity duration (default: 3600 seconds)
227    pub duration: Duration,
228}
229
230impl From<ClientJwtAuth> for JwtAuthConfig {
231    fn from(config: ClientJwtAuth) -> Self {
232        let mut claims = JwtClaims::default();
233
234        if let Some(audience) = config.audience {
235            claims = claims.with_audience(&audience);
236        }
237
238        if let Some(issuer) = config.issuer {
239            claims = claims.with_issuer(issuer);
240        }
241
242        if let Some(subject) = config.subject {
243            claims = claims.with_subject(subject);
244        }
245
246        JwtAuthConfig::new(claims, config.duration, config.key.into())
247    }
248}
249
250impl From<JwtAuthConfig> for ClientJwtAuth {
251    fn from(config: JwtAuthConfig) -> Self {
252        let claims = config.claims();
253        ClientJwtAuth {
254            key: config.key().clone().into(),
255            audience: claims.audience().clone(),
256            issuer: claims.issuer().clone(),
257            subject: claims.subject().clone(),
258            duration: config.duration(),
259        }
260    }
261}
262
263/// JWT authentication configuration for server-side verification
264#[derive(uniffi::Record, Clone, Debug, PartialEq)]
265pub struct JwtAuth {
266    /// JWT key configuration (decoding key for verification)
267    pub key: JwtKeyType,
268    /// JWT audience claims to verify
269    pub audience: Option<Vec<String>>,
270    /// JWT issuer to verify
271    pub issuer: Option<String>,
272    /// JWT subject to verify
273    pub subject: Option<String>,
274    /// Token validity duration (default: 3600 seconds)
275    pub duration: Duration,
276}
277
278impl From<JwtAuth> for JwtAuthConfig {
279    fn from(config: JwtAuth) -> Self {
280        let mut claims = JwtClaims::default();
281
282        if let Some(audience) = config.audience {
283            claims = claims.with_audience(&audience);
284        }
285
286        if let Some(issuer) = config.issuer {
287            claims = claims.with_issuer(issuer);
288        }
289
290        if let Some(subject) = config.subject {
291            claims = claims.with_subject(subject);
292        }
293
294        JwtAuthConfig::new(claims, config.duration, config.key.into())
295    }
296}
297
298impl From<JwtAuthConfig> for JwtAuth {
299    fn from(config: JwtAuthConfig) -> Self {
300        let claims = config.claims();
301        JwtAuth {
302            key: config.key().clone().into(),
303            audience: claims.audience().clone(),
304            issuer: claims.issuer().clone(),
305            subject: claims.subject().clone(),
306            duration: config.duration(),
307        }
308    }
309}
310
311/// Identity provider configuration - used to prove identity to others
312#[derive(uniffi::Enum, Clone, Debug, PartialEq)]
313pub enum IdentityProviderConfig {
314    /// Shared secret authentication (symmetric key)
315    SharedSecret { id: String, data: String },
316    /// Static JWT loaded from file with auto-reload
317    StaticJwt { config: StaticJwtAuth },
318    /// Dynamic JWT generation with signing key
319    Jwt { config: ClientJwtAuth },
320    /// SPIRE-based identity provider (non-Windows only)
321    #[cfg(not(target_family = "windows"))]
322    Spire { config: SpireConfig },
323    /// No identity provider configured
324    None,
325}
326
327impl From<IdentityProviderConfig> for CoreIdentityProviderConfig {
328    fn from(config: IdentityProviderConfig) -> Self {
329        match config {
330            IdentityProviderConfig::SharedSecret { id, data } => {
331                CoreIdentityProviderConfig::SharedSecret { id, data }
332            }
333            IdentityProviderConfig::StaticJwt { config } => {
334                CoreIdentityProviderConfig::StaticJwt(config.into())
335            }
336            IdentityProviderConfig::Jwt { config } => {
337                CoreIdentityProviderConfig::Jwt(config.into())
338            }
339            #[cfg(not(target_family = "windows"))]
340            IdentityProviderConfig::Spire { config } => {
341                CoreIdentityProviderConfig::Spire(config.into())
342            }
343            IdentityProviderConfig::None => CoreIdentityProviderConfig::None,
344        }
345    }
346}
347
348impl From<CoreIdentityProviderConfig> for IdentityProviderConfig {
349    fn from(config: CoreIdentityProviderConfig) -> Self {
350        match config {
351            CoreIdentityProviderConfig::SharedSecret { id, data } => {
352                IdentityProviderConfig::SharedSecret { id, data }
353            }
354            CoreIdentityProviderConfig::StaticJwt(config) => IdentityProviderConfig::StaticJwt {
355                config: config.into(),
356            },
357            CoreIdentityProviderConfig::Jwt(config) => IdentityProviderConfig::Jwt {
358                config: config.into(),
359            },
360            #[cfg(not(target_family = "windows"))]
361            CoreIdentityProviderConfig::Spire(config) => IdentityProviderConfig::Spire {
362                config: config.into(),
363            },
364            CoreIdentityProviderConfig::None => IdentityProviderConfig::None,
365        }
366    }
367}
368
369/// Identity verifier configuration - used to verify identity of others
370#[derive(uniffi::Enum, Clone, Debug, PartialEq)]
371pub enum IdentityVerifierConfig {
372    /// Shared secret verification (symmetric key)
373    SharedSecret { id: String, data: String },
374    /// JWT verification with decoding key
375    Jwt { config: JwtAuth },
376    /// SPIRE-based identity verifier (non-Windows only)
377    #[cfg(not(target_family = "windows"))]
378    Spire { config: SpireConfig },
379    /// No identity verifier configured
380    None,
381}
382
383impl From<IdentityVerifierConfig> for CoreIdentityVerifierConfig {
384    fn from(config: IdentityVerifierConfig) -> Self {
385        match config {
386            IdentityVerifierConfig::SharedSecret { id, data } => {
387                CoreIdentityVerifierConfig::SharedSecret { id, data }
388            }
389            IdentityVerifierConfig::Jwt { config } => {
390                CoreIdentityVerifierConfig::Jwt(config.into())
391            }
392            #[cfg(not(target_family = "windows"))]
393            IdentityVerifierConfig::Spire { config } => {
394                CoreIdentityVerifierConfig::Spire(config.into())
395            }
396            IdentityVerifierConfig::None => CoreIdentityVerifierConfig::None,
397        }
398    }
399}
400
401impl From<CoreIdentityVerifierConfig> for IdentityVerifierConfig {
402    fn from(config: CoreIdentityVerifierConfig) -> Self {
403        match config {
404            CoreIdentityVerifierConfig::SharedSecret { id, data } => {
405                IdentityVerifierConfig::SharedSecret { id, data }
406            }
407            CoreIdentityVerifierConfig::Jwt(config) => IdentityVerifierConfig::Jwt {
408                config: config.into(),
409            },
410            #[cfg(not(target_family = "windows"))]
411            CoreIdentityVerifierConfig::Spire(config) => IdentityVerifierConfig::Spire {
412                config: config.into(),
413            },
414            CoreIdentityVerifierConfig::None => IdentityVerifierConfig::None,
415        }
416    }
417}
418
419impl TryFrom<IdentityProviderConfig> for AuthProvider {
420    type Error = SlimError;
421
422    fn try_from(config: IdentityProviderConfig) -> Result<Self, Self::Error> {
423        match config {
424            IdentityProviderConfig::SharedSecret { id, data } => {
425                Ok(AuthProvider::shared_secret_from_str(&id, &data)?)
426            }
427            IdentityProviderConfig::StaticJwt { config } => {
428                let core_config: StaticJwtConfig = config.into();
429                let provider = core_config.build_static_token_provider()?;
430                Ok(AuthProvider::static_token(provider))
431            }
432            IdentityProviderConfig::Jwt { config } => {
433                let core_config: JwtAuthConfig = config.into();
434                let provider = core_config.get_provider()?;
435                Ok(AuthProvider::jwt_signer(provider))
436            }
437            #[cfg(not(target_family = "windows"))]
438            IdentityProviderConfig::Spire { config } => {
439                let mut builder = slim_auth::spire::SpireIdentityManager::builder();
440
441                if let Some(socket_path) = config.socket_path {
442                    builder = builder.with_socket_path(socket_path);
443                }
444
445                if let Some(target_spiffe_id) = config.target_spiffe_id {
446                    builder = builder.with_target_spiffe_id(target_spiffe_id);
447                }
448
449                if !config.jwt_audiences.is_empty() {
450                    builder = builder.with_jwt_audiences(config.jwt_audiences);
451                }
452
453                let manager = builder.build()?;
454                Ok(AuthProvider::spire(manager))
455            }
456            IdentityProviderConfig::None => Err(SlimError::InvalidArgument {
457                message: "Cannot create AuthProvider from None variant".to_string(),
458            }),
459        }
460    }
461}
462
463impl TryFrom<IdentityVerifierConfig> for AuthVerifier {
464    type Error = SlimError;
465
466    fn try_from(config: IdentityVerifierConfig) -> Result<Self, Self::Error> {
467        match config {
468            IdentityVerifierConfig::SharedSecret { id, data } => {
469                Ok(AuthVerifier::shared_secret_from_str(&id, &data)?)
470            }
471            IdentityVerifierConfig::Jwt { config } => {
472                let core_config: JwtAuthConfig = config.into();
473                let verifier = core_config.get_verifier()?;
474                Ok(AuthVerifier::jwt_verifier(verifier))
475            }
476            #[cfg(not(target_family = "windows"))]
477            IdentityVerifierConfig::Spire { config } => {
478                let mut builder = slim_auth::spire::SpireIdentityManager::builder();
479
480                if let Some(socket_path) = config.socket_path {
481                    builder = builder.with_socket_path(socket_path);
482                }
483
484                if let Some(target_spiffe_id) = config.target_spiffe_id {
485                    builder = builder.with_target_spiffe_id(target_spiffe_id);
486                }
487
488                if !config.jwt_audiences.is_empty() {
489                    builder = builder.with_jwt_audiences(config.jwt_audiences);
490                }
491
492                let manager = builder.build()?;
493                Ok(AuthVerifier::spire(manager))
494            }
495            IdentityVerifierConfig::None => Err(SlimError::InvalidArgument {
496                message: "Cannot create AuthVerifier from None variant".to_string(),
497            }),
498        }
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use std::time::Duration;
506
507    // Test StaticJwtAuth conversions
508    #[test]
509    fn test_static_jwt_auth_conversion() {
510        let auth = StaticJwtAuth {
511            token_file: "/path/to/token.jwt".to_string(),
512            duration: Duration::from_secs(7200),
513        };
514
515        let core_config: StaticJwtConfig = auth.clone().into();
516        assert_eq!(core_config.source().file, auth.token_file);
517        assert_eq!(core_config.duration(), auth.duration);
518
519        let back_to_auth: StaticJwtAuth = core_config.into();
520        assert_eq!(back_to_auth, auth);
521    }
522
523    // Test JwtAlgorithm conversions
524    #[test]
525    fn test_jwt_algorithm_conversions() {
526        let algorithms = vec![
527            JwtAlgorithm::HS256,
528            JwtAlgorithm::HS384,
529            JwtAlgorithm::HS512,
530            JwtAlgorithm::ES256,
531            JwtAlgorithm::ES384,
532            JwtAlgorithm::RS256,
533            JwtAlgorithm::RS384,
534            JwtAlgorithm::RS512,
535            JwtAlgorithm::PS256,
536            JwtAlgorithm::PS384,
537            JwtAlgorithm::PS512,
538            JwtAlgorithm::EdDSA,
539        ];
540
541        for algo in algorithms {
542            let core_algo: slim_auth::jwt::Algorithm = algo.clone().into();
543            let back: JwtAlgorithm = core_algo.into();
544            assert_eq!(back, algo);
545        }
546    }
547
548    // Test JwtKeyFormat conversions
549    #[test]
550    fn test_jwt_key_format_conversions() {
551        let formats = vec![JwtKeyFormat::Pem, JwtKeyFormat::Jwk, JwtKeyFormat::Jwks];
552
553        for format in formats {
554            let core_format: slim_auth::jwt::KeyFormat = format.clone().into();
555            let back: JwtKeyFormat = core_format.into();
556            assert_eq!(back, format);
557        }
558    }
559
560    // Test JwtKeyData conversions
561    #[test]
562    fn test_jwt_key_data_conversions() {
563        let data_value = JwtKeyData::Data {
564            value: "test-key-data".to_string(),
565        };
566        let core_data: slim_auth::jwt::KeyData = data_value.clone().into();
567        let back: JwtKeyData = core_data.into();
568        assert_eq!(back, data_value);
569
570        let file_path = JwtKeyData::File {
571            path: "/path/to/key.pem".to_string(),
572        };
573        let core_file: slim_auth::jwt::KeyData = file_path.clone().into();
574        let back_file: JwtKeyData = core_file.into();
575        assert_eq!(back_file, file_path);
576    }
577
578    // Test JwtKeyConfig conversions
579    #[test]
580    fn test_jwt_key_config_conversions() {
581        let key_config = JwtKeyConfig {
582            algorithm: JwtAlgorithm::RS256,
583            format: JwtKeyFormat::Pem,
584            key: JwtKeyData::File {
585                path: "/path/to/key.pem".to_string(),
586            },
587        };
588
589        let core_key: slim_auth::jwt::Key = key_config.clone().into();
590        let back: JwtKeyConfig = core_key.into();
591        assert_eq!(back, key_config);
592    }
593
594    // Test JwtKeyType conversions
595    #[test]
596    fn test_jwt_key_type_conversions() {
597        let encoding_key = JwtKeyType::Encoding {
598            key: JwtKeyConfig {
599                algorithm: JwtAlgorithm::RS256,
600                format: JwtKeyFormat::Pem,
601                key: JwtKeyData::Data {
602                    value: "encoding-key".to_string(),
603                },
604            },
605        };
606
607        let core_encoding: JwtKey = encoding_key.clone().into();
608        let back_encoding: JwtKeyType = core_encoding.into();
609        assert_eq!(back_encoding, encoding_key);
610
611        let decoding_key = JwtKeyType::Decoding {
612            key: JwtKeyConfig {
613                algorithm: JwtAlgorithm::ES256,
614                format: JwtKeyFormat::Jwk,
615                key: JwtKeyData::File {
616                    path: "/path/to/key.jwk".to_string(),
617                },
618            },
619        };
620
621        let core_decoding: JwtKey = decoding_key.clone().into();
622        let back_decoding: JwtKeyType = core_decoding.into();
623        assert_eq!(back_decoding, decoding_key);
624
625        let autoresolve = JwtKeyType::Autoresolve;
626        let core_auto: JwtKey = autoresolve.clone().into();
627        let back_auto: JwtKeyType = core_auto.into();
628        assert_eq!(back_auto, autoresolve);
629    }
630
631    // Test ClientJwtAuth conversions
632    #[test]
633    fn test_client_jwt_auth_conversions() {
634        let client_auth = ClientJwtAuth {
635            key: JwtKeyType::Autoresolve,
636            audience: Some(vec!["api.example.com".to_string()]),
637            issuer: Some("auth.example.com".to_string()),
638            subject: Some("user@example.com".to_string()),
639            duration: Duration::from_secs(1800),
640        };
641
642        let core_config: JwtAuthConfig = client_auth.clone().into();
643        assert_eq!(core_config.duration(), client_auth.duration);
644
645        let back: ClientJwtAuth = core_config.into();
646        assert_eq!(back, client_auth);
647    }
648
649    // Test ClientJwtAuth with None fields
650    #[test]
651    fn test_client_jwt_auth_with_none_fields() {
652        let client_auth = ClientJwtAuth {
653            key: JwtKeyType::Autoresolve,
654            audience: None,
655            issuer: None,
656            subject: None,
657            duration: Duration::from_secs(3600),
658        };
659
660        let core_config: JwtAuthConfig = client_auth.clone().into();
661        let back: ClientJwtAuth = core_config.into();
662        assert_eq!(back.audience, None);
663        assert_eq!(back.issuer, None);
664        assert_eq!(back.subject, None);
665    }
666
667    // Test JwtAuth conversions
668    #[test]
669    fn test_jwt_auth_conversions() {
670        let jwt_auth = JwtAuth {
671            key: JwtKeyType::Decoding {
672                key: JwtKeyConfig {
673                    algorithm: JwtAlgorithm::RS256,
674                    format: JwtKeyFormat::Pem,
675                    key: JwtKeyData::File {
676                        path: "/path/to/key.pem".to_string(),
677                    },
678                },
679            },
680            audience: Some(vec!["service.example.com".to_string()]),
681            issuer: Some("issuer.example.com".to_string()),
682            subject: Some("subject".to_string()),
683            duration: Duration::from_secs(900),
684        };
685
686        let core_config: JwtAuthConfig = jwt_auth.clone().into();
687        let back: JwtAuth = core_config.into();
688        assert_eq!(back, jwt_auth);
689    }
690
691    // Test IdentityProviderConfig conversions
692    #[test]
693    fn test_identity_provider_config_shared_secret() {
694        let config = IdentityProviderConfig::SharedSecret {
695            id: "test-id".to_string(),
696            data: "secret-data".to_string(),
697        };
698
699        let core_config: CoreIdentityProviderConfig = config.clone().into();
700        let back: IdentityProviderConfig = core_config.into();
701        assert_eq!(back, config);
702    }
703
704    #[test]
705    fn test_identity_provider_config_static_jwt() {
706        let config = IdentityProviderConfig::StaticJwt {
707            config: StaticJwtAuth {
708                token_file: "/path/to/token.jwt".to_string(),
709                duration: Duration::from_secs(3600),
710            },
711        };
712
713        let core_config: CoreIdentityProviderConfig = config.clone().into();
714        let back: IdentityProviderConfig = core_config.into();
715        assert_eq!(back, config);
716    }
717
718    #[test]
719    fn test_identity_provider_config_jwt() {
720        let config = IdentityProviderConfig::Jwt {
721            config: ClientJwtAuth {
722                key: JwtKeyType::Autoresolve,
723                audience: Some(vec!["test".to_string()]),
724                issuer: None,
725                subject: None,
726                duration: Duration::from_secs(3600),
727            },
728        };
729
730        let core_config: CoreIdentityProviderConfig = config.clone().into();
731        let back: IdentityProviderConfig = core_config.into();
732        assert_eq!(back, config);
733    }
734
735    #[test]
736    fn test_identity_provider_config_none() {
737        let config = IdentityProviderConfig::None;
738        let core_config: CoreIdentityProviderConfig = config.clone().into();
739        let back: IdentityProviderConfig = core_config.into();
740        assert_eq!(back, config);
741    }
742
743    // Test IdentityVerifierConfig conversions
744    #[test]
745    fn test_identity_verifier_config_shared_secret() {
746        let config = IdentityVerifierConfig::SharedSecret {
747            id: "verifier-id".to_string(),
748            data: "verifier-secret".to_string(),
749        };
750
751        let core_config: CoreIdentityVerifierConfig = config.clone().into();
752        let back: IdentityVerifierConfig = core_config.into();
753        assert_eq!(back, config);
754    }
755
756    #[test]
757    fn test_identity_verifier_config_jwt() {
758        let config = IdentityVerifierConfig::Jwt {
759            config: JwtAuth {
760                key: JwtKeyType::Autoresolve,
761                audience: Some(vec!["verifier".to_string()]),
762                issuer: Some("auth".to_string()),
763                subject: None,
764                duration: Duration::from_secs(7200),
765            },
766        };
767
768        let core_config: CoreIdentityVerifierConfig = config.clone().into();
769        let back: IdentityVerifierConfig = core_config.into();
770        assert_eq!(back, config);
771    }
772
773    #[test]
774    fn test_identity_verifier_config_none() {
775        let config = IdentityVerifierConfig::None;
776        let core_config: CoreIdentityVerifierConfig = config.clone().into();
777        let back: IdentityVerifierConfig = core_config.into();
778        assert_eq!(back, config);
779    }
780
781    // Test TryFrom for IdentityProviderConfig to AuthProvider
782    #[test]
783    fn test_identity_provider_to_auth_provider_shared_secret() {
784        let config = IdentityProviderConfig::SharedSecret {
785            id: "test-id".to_string(),
786            data: "shared-secret-value-0123456789abcdef".to_string(), // Must be 32+ chars
787        };
788
789        let result: Result<AuthProvider, SlimError> = config.try_into();
790        assert!(result.is_ok());
791    }
792
793    #[test]
794    fn test_identity_provider_to_auth_provider_none_fails() {
795        let config = IdentityProviderConfig::None;
796        let result: Result<AuthProvider, SlimError> = config.try_into();
797        assert!(result.is_err());
798        if let Err(SlimError::InvalidArgument { message }) = result {
799            assert!(message.contains("Cannot create AuthProvider from None variant"));
800        }
801    }
802
803    // Test TryFrom for IdentityVerifierConfig to AuthVerifier
804    #[test]
805    fn test_identity_verifier_to_auth_verifier_shared_secret() {
806        let config = IdentityVerifierConfig::SharedSecret {
807            id: "verifier-id".to_string(),
808            data: "verifier-shared-secret-0123456789abcdef".to_string(), // Must be 32+ chars
809        };
810
811        let result: Result<AuthVerifier, SlimError> = config.try_into();
812        assert!(result.is_ok());
813    }
814
815    #[test]
816    fn test_identity_verifier_to_auth_verifier_none_fails() {
817        let config = IdentityVerifierConfig::None;
818        let result: Result<AuthVerifier, SlimError> = config.try_into();
819        assert!(result.is_err());
820        if let Err(SlimError::InvalidArgument { message }) = result {
821            assert!(message.contains("Cannot create AuthVerifier from None variant"));
822        }
823    }
824
825    // Test invalid shared secret (too short)
826    #[test]
827    fn test_identity_provider_invalid_shared_secret() {
828        let config = IdentityProviderConfig::SharedSecret {
829            id: "test-id".to_string(),
830            data: "tooshort".to_string(), // Must be at least 32 characters
831        };
832
833        let result: Result<AuthProvider, SlimError> = config.try_into();
834        assert!(result.is_err());
835        if let Err(SlimError::InvalidArgument { message }) = result {
836            assert!(message.contains("Failed to create SharedSecret provider"));
837        }
838    }
839
840    #[test]
841    fn test_identity_verifier_invalid_shared_secret() {
842        let config = IdentityVerifierConfig::SharedSecret {
843            id: "test-id".to_string(),
844            data: "tooshort".to_string(), // Too short - must be 32+ chars
845        };
846
847        let result: Result<AuthVerifier, SlimError> = config.try_into();
848        assert!(result.is_err());
849        if let Err(SlimError::InvalidArgument { message }) = result {
850            assert!(message.contains("Failed to create SharedSecret verifier"));
851        }
852    }
853
854    // Test SPIRE config conversions (non-Windows only)
855    #[cfg(not(target_family = "windows"))]
856    #[test]
857    fn test_identity_provider_config_spire() {
858        let spire_config = SpireConfig {
859            socket_path: Some("/tmp/spire.sock".to_string()),
860            target_spiffe_id: Some("spiffe://example.com/service".to_string()),
861            jwt_audiences: vec!["audience1".to_string(), "audience2".to_string()],
862            trust_domains: vec![],
863        };
864
865        let config = IdentityProviderConfig::Spire {
866            config: spire_config.clone(),
867        };
868
869        let core_config: CoreIdentityProviderConfig = config.clone().into();
870        let back: IdentityProviderConfig = core_config.into();
871
872        if let IdentityProviderConfig::Spire {
873            config: back_config,
874        } = back
875        {
876            assert_eq!(back_config.socket_path, spire_config.socket_path);
877            assert_eq!(back_config.target_spiffe_id, spire_config.target_spiffe_id);
878            assert_eq!(back_config.jwt_audiences, spire_config.jwt_audiences);
879        } else {
880            panic!("Expected Spire variant");
881        }
882    }
883
884    #[cfg(not(target_family = "windows"))]
885    #[test]
886    fn test_identity_verifier_config_spire() {
887        let spire_config = SpireConfig {
888            socket_path: None,
889            target_spiffe_id: None,
890            jwt_audiences: vec!["slim".to_string()],
891            trust_domains: vec!["example.com".to_string()],
892        };
893
894        let config = IdentityVerifierConfig::Spire {
895            config: spire_config.clone(),
896        };
897
898        let core_config: CoreIdentityVerifierConfig = config.clone().into();
899        let back: IdentityVerifierConfig = core_config.into();
900
901        if let IdentityVerifierConfig::Spire {
902            config: back_config,
903        } = back
904        {
905            assert_eq!(back_config.socket_path, spire_config.socket_path);
906            assert_eq!(back_config.jwt_audiences, spire_config.jwt_audiences);
907        } else {
908            panic!("Expected Spire variant");
909        }
910    }
911
912    // Test complex JWT key configurations
913    #[test]
914    fn test_complex_jwt_key_configurations() {
915        let configurations = vec![
916            JwtKeyConfig {
917                algorithm: JwtAlgorithm::HS256,
918                format: JwtKeyFormat::Pem,
919                key: JwtKeyData::Data {
920                    value: "secret-key".to_string(),
921                },
922            },
923            JwtKeyConfig {
924                algorithm: JwtAlgorithm::ES384,
925                format: JwtKeyFormat::Jwk,
926                key: JwtKeyData::File {
927                    path: "/etc/keys/ec-key.jwk".to_string(),
928                },
929            },
930            JwtKeyConfig {
931                algorithm: JwtAlgorithm::PS512,
932                format: JwtKeyFormat::Jwks,
933                key: JwtKeyData::File {
934                    path: "/etc/keys/jwks.json".to_string(),
935                },
936            },
937        ];
938
939        for config in configurations {
940            let core_key: slim_auth::jwt::Key = config.clone().into();
941            let back: JwtKeyConfig = core_key.into();
942            assert_eq!(back, config);
943        }
944    }
945
946    // Test all JWT algorithms are covered
947    #[test]
948    fn test_all_jwt_algorithms_covered() {
949        let all_algorithms = vec![
950            (JwtAlgorithm::HS256, slim_auth::jwt::Algorithm::HS256),
951            (JwtAlgorithm::HS384, slim_auth::jwt::Algorithm::HS384),
952            (JwtAlgorithm::HS512, slim_auth::jwt::Algorithm::HS512),
953            (JwtAlgorithm::ES256, slim_auth::jwt::Algorithm::ES256),
954            (JwtAlgorithm::ES384, slim_auth::jwt::Algorithm::ES384),
955            (JwtAlgorithm::RS256, slim_auth::jwt::Algorithm::RS256),
956            (JwtAlgorithm::RS384, slim_auth::jwt::Algorithm::RS384),
957            (JwtAlgorithm::RS512, slim_auth::jwt::Algorithm::RS512),
958            (JwtAlgorithm::PS256, slim_auth::jwt::Algorithm::PS256),
959            (JwtAlgorithm::PS384, slim_auth::jwt::Algorithm::PS384),
960            (JwtAlgorithm::PS512, slim_auth::jwt::Algorithm::PS512),
961            (JwtAlgorithm::EdDSA, slim_auth::jwt::Algorithm::EdDSA),
962        ];
963
964        for (adapter_algo, core_algo) in all_algorithms {
965            let converted_core: slim_auth::jwt::Algorithm = adapter_algo.clone().into();
966            let converted_back: JwtAlgorithm = converted_core.into();
967            assert_eq!(converted_back, adapter_algo);
968
969            let direct_back: JwtAlgorithm = core_algo.into();
970            assert_eq!(direct_back, adapter_algo);
971        }
972    }
973}