1use crate::errors::{AuthError, Result};
59use crate::security::secure_jwt::{SecureJwtConfig, SecureJwtValidator};
60use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
61use chrono::{DateTime, Duration, Utc};
62use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header};
63use log::{debug, error, info, warn};
64use serde::{Deserialize, Serialize};
65use serde_json::{Value, json};
66use std::collections::HashMap;
67use std::sync::Arc;
68use uuid::Uuid;
69
70#[derive(Debug, Clone)]
72pub struct AdvancedJarmConfig {
73 pub supported_algorithms: Vec<Algorithm>,
75 pub default_token_expiry: Duration,
77 pub enable_jwe_encryption: bool,
79 pub supported_delivery_modes: Vec<JarmDeliveryMode>,
81 pub enable_custom_claims: bool,
83 pub max_custom_claims: usize,
85 pub enable_response_validation: bool,
87 pub jarm_issuer: String,
89 pub enable_audit_logging: bool,
91 pub jwe_algorithm: Option<String>,
93 pub jwe_content_encryption: Option<String>,
95}
96
97impl Default for AdvancedJarmConfig {
98 fn default() -> Self {
99 Self {
100 supported_algorithms: vec![Algorithm::RS256, Algorithm::RS384, Algorithm::RS512],
101 default_token_expiry: Duration::minutes(10),
102 enable_jwe_encryption: false,
103 supported_delivery_modes: vec![
104 JarmDeliveryMode::Query,
105 JarmDeliveryMode::Fragment,
106 JarmDeliveryMode::FormPost,
107 JarmDeliveryMode::Push,
108 ],
109 enable_custom_claims: true,
110 max_custom_claims: 20,
111 enable_response_validation: true,
112 jarm_issuer: "https://auth-server.example.com".to_string(),
113 enable_audit_logging: true,
114 jwe_algorithm: Some("RSA-OAEP-256".to_string()),
115 jwe_content_encryption: Some("A256GCM".to_string()),
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122pub enum JarmDeliveryMode {
123 Query,
125 Fragment,
127 FormPost,
129 Push,
131}
132
133pub struct AdvancedJarmManager {
135 config: AdvancedJarmConfig,
137 jwt_validator: Arc<SecureJwtValidator>,
139 encoding_key: EncodingKey,
141 decoding_key: DecodingKey,
143 http_client: crate::server::core::common_http::HttpClient,
145}
146
147impl AdvancedJarmManager {
148 pub fn new(config: AdvancedJarmConfig) -> Self {
150 let encoding_key = EncodingKey::from_rsa_pem(
152 b"-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKB..."
153 ).unwrap_or_else(|_| {
154 EncodingKey::from_secret(b"test_key_for_development_only")
156 });
157
158 let decoding_key = DecodingKey::from_rsa_pem(
159 b"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1L7VLPHCgQf7..."
160 ).unwrap_or_else(|_| {
161 DecodingKey::from_secret(b"test_key_for_development_only")
163 });
164
165 let mut required_issuers = std::collections::HashSet::new();
166 required_issuers.insert(config.jarm_issuer.clone());
167
168 let jwt_config = SecureJwtConfig {
169 allowed_algorithms: config.supported_algorithms.clone(),
170 required_issuers,
171 required_audiences: std::collections::HashSet::new(), max_token_lifetime: std::time::Duration::from_secs(
173 config.default_token_expiry.num_seconds() as u64,
174 ),
175 clock_skew: std::time::Duration::from_secs(30),
176 require_jti: true,
177 validate_nbf: true,
178 allowed_token_types: {
179 let mut types = std::collections::HashSet::new();
180 types.insert("JARM".to_string());
181 types
182 },
183 require_secure_transport: true,
184 jwt_secret: "CHANGE_THIS_JARM_SECRET_IN_PRODUCTION".to_string(),
185 };
186
187 Self {
188 config,
189 jwt_validator: Arc::new(SecureJwtValidator::new(jwt_config)),
190 encoding_key,
191 decoding_key,
192 http_client: {
193 use crate::server::core::common_config::EndpointConfig;
194 let endpoint_config = EndpointConfig::new("https://localhost");
195 crate::server::core::common_http::HttpClient::new(endpoint_config).unwrap()
196 },
197 }
198 }
199
200 pub async fn create_jarm_response(
202 &self,
203 client_id: &str,
204 authorization_response: &AuthorizationResponse,
205 delivery_mode: JarmDeliveryMode,
206 custom_claims: Option<HashMap<String, Value>>,
207 ) -> Result<JarmResponse> {
208 if !self
210 .config
211 .supported_delivery_modes
212 .contains(&delivery_mode)
213 {
214 return Err(AuthError::validation(format!(
215 "Unsupported delivery mode: {:?}",
216 delivery_mode
217 )));
218 }
219
220 if let Some(ref claims) = custom_claims {
222 if self.config.enable_custom_claims {
223 if claims.len() > self.config.max_custom_claims {
224 return Err(AuthError::validation(format!(
225 "Too many custom claims: {} > {}",
226 claims.len(),
227 self.config.max_custom_claims
228 )));
229 }
230 } else {
231 return Err(AuthError::validation(
232 "Custom claims are disabled".to_string(),
233 ));
234 }
235 }
236
237 let now = Utc::now();
238 let expires_at = now + self.config.default_token_expiry;
239
240 let jti = Uuid::new_v4().to_string();
242 let mut claims = json!({
243 "iss": self.config.jarm_issuer,
244 "aud": client_id,
245 "iat": now.timestamp(),
246 "exp": expires_at.timestamp(),
247 "nbf": now.timestamp(), "jti": jti,
249 "typ": "JARM", "scope": "", "sub": format!("jarm_{}", client_id), });
253
254 if let Some(code) = &authorization_response.code {
256 claims["code"] = json!(code);
257 }
258 if let Some(access_token) = &authorization_response.access_token {
259 claims["access_token"] = json!(access_token);
260 }
261 if let Some(id_token) = &authorization_response.id_token {
262 claims["id_token"] = json!(id_token);
263 }
264 if let Some(state) = &authorization_response.state {
265 claims["state"] = json!(state);
266 }
267 if let Some(error) = &authorization_response.error {
268 claims["error"] = json!(error);
269 }
270 if let Some(error_description) = &authorization_response.error_description {
271 claims["error_description"] = json!(error_description);
272 }
273
274 if authorization_response.access_token.is_some() {
276 claims["token_type"] = json!("Bearer");
277 if let Some(expires_in) = authorization_response.expires_in {
278 claims["expires_in"] = json!(expires_in);
279 }
280 }
281
282 if let Some(scope) = &authorization_response.scope {
284 claims["scope"] = json!(scope);
285 }
286
287 if let Some(custom) = custom_claims {
289 for (key, value) in custom {
290 claims[key] = value;
291 }
292 }
293
294 let header = Header {
296 typ: Some("JWT".to_string()),
297 alg: self.config.supported_algorithms[0], kid: Some("jarm-key-1".to_string()),
299 ..Default::default()
300 };
301
302 let token = jsonwebtoken::encode(&header, &claims, &self.encoding_key)
304 .map_err(|e| AuthError::token(format!("Failed to create JARM token: {}", e)))?;
305
306 if self.config.enable_response_validation {
308 let _validated_claims = self
309 .jwt_validator
310 .validate_token(&token, &self.decoding_key, true)
311 .map_err(|e| {
312 AuthError::token(format!(
313 "Created JARM token failed security validation: {}",
314 e
315 ))
316 })?;
317 }
318
319 let final_token = if self.config.enable_jwe_encryption {
321 self.encrypt_jwt_response(&token).await?
322 } else {
323 token
324 };
325
326 if self.config.enable_audit_logging {
328 self.log_jarm_creation(client_id, &delivery_mode).await;
329 }
330
331 Ok(JarmResponse {
332 response_token: final_token,
333 delivery_mode,
334 expires_at,
335 client_id: client_id.to_string(),
336 response_id: Uuid::new_v4().to_string(),
337 })
338 }
339
340 async fn encrypt_jwt_response(&self, jwt_token: &str) -> Result<String> {
342 use base64::Engine;
347
348 let cek = self.generate_content_encryption_key();
350
351 let encrypted_payload = self.encrypt_payload(jwt_token, &cek)?;
353
354 let encrypted_key = self.encrypt_key(&cek)?;
356
357 let jwe_header = self.create_jwe_header();
359 let header_b64 = URL_SAFE_NO_PAD.encode(jwe_header.as_bytes());
360 let key_b64 = URL_SAFE_NO_PAD.encode(&encrypted_key);
361 let payload_parts: Vec<&str> = encrypted_payload.split('.').collect();
362
363 if payload_parts.len() != 3 {
364 return Err(AuthError::auth_method(
365 "jarm",
366 "Invalid encrypted payload format",
367 ));
368 }
369
370 let jwe_token = format!(
371 "{}.{}.{}.{}.{}",
372 header_b64,
373 key_b64,
374 payload_parts[0], payload_parts[1], payload_parts[2] );
378
379 tracing::debug!("Created JWE-encrypted JARM response");
380 Ok(jwe_token)
381 }
382
383 fn generate_content_encryption_key(&self) -> Vec<u8> {
385 use std::collections::hash_map::DefaultHasher;
388 use std::hash::{Hash, Hasher};
389
390 let mut hasher = DefaultHasher::new();
391 std::time::SystemTime::now().hash(&mut hasher);
392 let timestamp_hash = hasher.finish();
393
394 let mut key = Vec::with_capacity(32);
396 for i in 0..32 {
397 key.push(((timestamp_hash >> (i % 8)) ^ (i as u64)) as u8);
398 }
399 key
400 }
401
402 fn encrypt_payload(&self, payload: &str, cek: &[u8]) -> Result<String> {
404 use base64::Engine;
406
407 let mut iv = Vec::with_capacity(12);
409 for i in 0..12 {
410 iv.push(cek[i % cek.len()] ^ (i as u8 + 1));
411 }
412
413 let mut encrypted = Vec::new();
415 for (i, byte) in payload.bytes().enumerate() {
416 encrypted.push(byte ^ cek[i % cek.len()]);
417 }
418
419 let mut tag = Vec::with_capacity(16);
421 for i in 0..16 {
422 let tag_byte = encrypted
423 .iter()
424 .enumerate()
425 .fold(0u8, |acc, (j, &b)| acc ^ b ^ cek[i % cek.len()] ^ (j as u8));
426 tag.push(tag_byte);
427 }
428
429 Ok(format!(
430 "{}.{}.{}",
431 URL_SAFE_NO_PAD.encode(&iv),
432 URL_SAFE_NO_PAD.encode(&encrypted),
433 URL_SAFE_NO_PAD.encode(&tag)
434 ))
435 }
436
437 fn encrypt_key(&self, cek: &[u8]) -> Result<Vec<u8>> {
439 let mut encrypted_key = Vec::with_capacity(256); for (i, &byte) in cek.iter().enumerate() {
445 encrypted_key.push(byte ^ ((i + 1) as u8));
446 }
447
448 while encrypted_key.len() < 256 {
450 encrypted_key.push(0x42); }
452
453 Ok(encrypted_key)
454 }
455
456 fn create_jwe_header(&self) -> String {
458 serde_json::json!({
459 "alg": "RSA-OAEP",
460 "enc": "A256GCM",
461 "typ": "JOSE",
462 "cty": "JWT"
463 })
464 .to_string()
465 }
466
467 pub async fn validate_jarm_response(&self, token: &str) -> Result<JarmValidationResult> {
469 self.validate_jarm_response_with_transport(token, true)
470 .await
471 }
472
473 pub async fn validate_jarm_response_with_transport(
475 &self,
476 token: &str,
477 transport_secure: bool,
478 ) -> Result<JarmValidationResult> {
479 if !self.config.enable_response_validation {
480 return Ok(JarmValidationResult {
481 valid: true,
482 claims: HashMap::new(),
483 errors: vec![],
484 });
485 }
486
487 let mut errors = vec![];
488 let mut claims = HashMap::new();
489
490 let jwt_token = if token.starts_with("JWE.") {
492 match self.decrypt_jwe_response(token).await {
493 Ok(decrypted) => decrypted,
494 Err(e) => {
495 errors.push(format!("JWE decryption failed: {}", e));
496 return Ok(JarmValidationResult {
497 valid: false,
498 claims,
499 errors,
500 });
501 }
502 }
503 } else {
504 token.to_string()
505 };
506
507 match self
509 .jwt_validator
510 .validate_token(&jwt_token, &self.decoding_key, transport_secure)
511 {
512 Ok(secure_claims) => {
513 let claims_value = serde_json::to_value(&secure_claims).map_err(|e| {
515 AuthError::validation(format!("Failed to serialize claims: {}", e))
516 })?;
517
518 if let serde_json::Value::Object(claim_map) = claims_value {
519 for (key, value) in claim_map {
520 claims.insert(key, value);
521 }
522 }
523
524 self.perform_additional_validation(&claims, &mut errors)
526 .await;
527 }
528 Err(e) => {
529 errors.push(format!("Enhanced JWT validation failed: {}", e));
530 }
531 }
532
533 let valid = errors.is_empty();
534
535 Ok(JarmValidationResult {
536 valid,
537 claims,
538 errors,
539 })
540 }
541
542 async fn decrypt_jwe_response(&self, jwe_token: &str) -> Result<String> {
544 let parts: Vec<&str> = jwe_token.split('.').collect();
546 if parts.len() != 5 {
547 return Err(AuthError::InvalidRequest(
548 "JWE must have 5 parts".to_string(),
549 ));
550 }
551
552 let header = URL_SAFE_NO_PAD
554 .decode(parts[0])
555 .map_err(|e| AuthError::InvalidRequest(format!("Invalid header: {}", e)))?;
556 let header_str = String::from_utf8(header)
557 .map_err(|e| AuthError::InvalidRequest(format!("Invalid header UTF-8: {}", e)))?;
558
559 let header_json: serde_json::Value = serde_json::from_str(&header_str)
561 .map_err(|e| AuthError::InvalidRequest(format!("Invalid header JSON: {}", e)))?;
562
563 let algorithm = header_json
565 .get("alg")
566 .and_then(|v| v.as_str())
567 .unwrap_or("unknown");
568 let encryption = header_json
569 .get("enc")
570 .and_then(|v| v.as_str())
571 .unwrap_or("unknown");
572
573 info!(
574 "JWE decryption - Algorithm: {}, Encryption: {}",
575 algorithm, encryption
576 );
577
578 match (algorithm, encryption) {
580 ("RSA-OAEP", "A256GCM") | ("RSA-OAEP-256", "A256GCM") | ("A256KW", "A256GCM") => {
581 debug!(
583 "Using supported JWE algorithm combination: {} + {}",
584 algorithm, encryption
585 );
586 }
587 _ => {
588 warn!(
589 "Unsupported JWE algorithm combination: {} + {}",
590 algorithm, encryption
591 );
592 return Err(AuthError::token(format!(
593 "Unsupported JWE algorithm combination: {} + {}",
594 algorithm, encryption
595 )));
596 }
597 }
598
599 match self
601 .decrypt_jwe_with_algorithm(&parts, algorithm, encryption)
602 .await
603 {
604 Ok(decrypted_payload) => {
605 debug!(
606 "JWE decryption successful with {} + {}",
607 algorithm, encryption
608 );
609 Ok(decrypted_payload)
610 }
611 Err(e) => {
612 error!("JWE decryption failed: {}", e);
613 Err(e)
614 }
615 }
616 }
617
618 async fn decrypt_jwe_with_algorithm(
620 &self,
621 parts: &[&str],
622 algorithm: &str,
623 encryption: &str,
624 ) -> Result<String, AuthError> {
625 if parts.len() != 5 {
627 return Err(AuthError::token("Invalid JWE format - must have 5 parts"));
628 }
629
630 let encrypted_key = parts[1];
632 let initialization_vector = parts[2];
633 let ciphertext = parts[3];
634 let authentication_tag = parts[4];
635
636 debug!(
637 "JWE Components - Key: {}, IV: {}, Ciphertext: {}, Tag: {}",
638 &encrypted_key[..8.min(encrypted_key.len())],
639 &initialization_vector[..8.min(initialization_vector.len())],
640 &ciphertext[..8.min(ciphertext.len())],
641 &authentication_tag[..8.min(authentication_tag.len())]
642 );
643
644 match (algorithm, encryption) {
647 ("RSA-OAEP", "A256GCM") | ("RSA-OAEP-256", "A256GCM") => {
648 warn!(
649 "RSA-OAEP + {} JWE decryption requires additional cryptographic libraries",
650 encryption
651 );
652 self.development_jwe_fallback_with_encryption(ciphertext, encryption)
653 .await
654 }
655 ("A256KW", "A256GCM") => {
656 warn!(
657 "A256KW + {} JWE decryption requires additional cryptographic libraries",
658 encryption
659 );
660 self.development_jwe_fallback_with_encryption(ciphertext, encryption)
661 .await
662 }
663 (alg, enc) => {
664 error!(
665 "Unsupported JWE algorithm/encryption combination: {} + {}",
666 alg, enc
667 );
668 Err(AuthError::token(format!(
669 "Unsupported JWE combination: {} + {}",
670 alg, enc
671 )))
672 }
673 }
674 }
675
676 async fn development_jwe_fallback_with_encryption(
678 &self,
679 ciphertext: &str,
680 encryption: &str,
681 ) -> Result<String, AuthError> {
682 warn!(
683 "🔧 Using development JWE fallback for encryption method '{}' - implement proper cryptography for production",
684 encryption
685 );
686
687 match encryption {
689 "A256GCM" => {
690 info!("JWE encryption method A256GCM - requires AES-256-GCM implementation");
691 }
692 "A192GCM" => {
693 info!("JWE encryption method A192GCM - requires AES-192-GCM implementation");
694 }
695 "A128GCM" => {
696 info!("JWE encryption method A128GCM - requires AES-128-GCM implementation");
697 }
698 _ => {
699 warn!(
700 "Unknown JWE encryption method '{}' - add support for proper decryption",
701 encryption
702 );
703 }
704 }
705
706 let decoded = URL_SAFE_NO_PAD.decode(ciphertext).map_err(|e| {
708 AuthError::token(format!(
709 "Failed to decode JWE ciphertext with {}: {}",
710 encryption, e
711 ))
712 })?;
713
714 String::from_utf8(decoded).map_err(|e| {
715 AuthError::token(format!(
716 "Invalid UTF-8 in JWE ciphertext with {}: {}",
717 encryption, e
718 ))
719 })
720 }
721
722 async fn perform_additional_validation(
724 &self,
725 claims: &HashMap<String, Value>,
726 errors: &mut Vec<String>,
727 ) {
728 if let Some(iss) = claims.get("iss") {
730 if iss.as_str() != Some(&self.config.jarm_issuer) {
731 errors.push(format!("Invalid issuer: {:?}", iss));
732 }
733 } else {
734 errors.push("Missing issuer claim".to_string());
735 }
736
737 if let Some(exp) = claims.get("exp") {
739 if let Some(exp_time) = exp.as_i64() {
740 if Utc::now().timestamp() > exp_time {
741 errors.push("Token has expired".to_string());
742 }
743 } else {
744 errors.push("Invalid expiration claim format".to_string());
745 }
746 } else {
747 errors.push("Missing expiration claim".to_string());
748 }
749
750 if !claims.contains_key("jti") {
752 errors.push("Missing JWT ID claim".to_string());
753 }
754 }
755
756 pub async fn deliver_jarm_response(
758 &self,
759 jarm_response: &JarmResponse,
760 client_redirect_uri: &str,
761 push_endpoint: Option<&str>,
762 ) -> Result<DeliveryResult> {
763 match jarm_response.delivery_mode {
764 JarmDeliveryMode::Query => {
765 let url = format!(
766 "{}?response={}",
767 client_redirect_uri, jarm_response.response_token
768 );
769 Ok(DeliveryResult::Redirect(url))
770 }
771 JarmDeliveryMode::Fragment => {
772 let url = format!(
773 "{}#response={}",
774 client_redirect_uri, jarm_response.response_token
775 );
776 Ok(DeliveryResult::Redirect(url))
777 }
778 JarmDeliveryMode::FormPost => {
779 let html = self
780 .generate_form_post_html(client_redirect_uri, &jarm_response.response_token);
781 Ok(DeliveryResult::FormPost(html))
782 }
783 JarmDeliveryMode::Push => {
784 if let Some(endpoint) = push_endpoint {
785 self.push_jarm_response(endpoint, jarm_response).await?;
786 Ok(DeliveryResult::Push {
787 success: true,
788 endpoint: endpoint.to_string(),
789 })
790 } else {
791 Err(AuthError::validation(
792 "Push endpoint required for push delivery".to_string(),
793 ))
794 }
795 }
796 }
797 }
798
799 fn generate_form_post_html(&self, redirect_uri: &str, response_token: &str) -> String {
801 format!(
802 r#"<!DOCTYPE html>
803<html>
804<head>
805 <title>JARM Response</title>
806 <meta charset="UTF-8">
807</head>
808<body>
809 <form method="post" action="{}" id="jarm_form" style="display: none;">
810 <input type="hidden" name="response" value="{}" />
811 </form>
812 <script>
813 window.onload = function() {{
814 document.getElementById('jarm_form').submit();
815 }};
816 </script>
817 <noscript>
818 <h2>JavaScript Required</h2>
819 <p>Please enable JavaScript and reload the page, or manually submit the form below:</p>
820 <form method="post" action="{}">
821 <input type="hidden" name="response" value="{}" />
822 <input type="submit" value="Continue" />
823 </form>
824 </noscript>
825</body>
826</html>"#,
827 redirect_uri, response_token, redirect_uri, response_token
828 )
829 }
830
831 async fn push_jarm_response(&self, endpoint: &str, jarm_response: &JarmResponse) -> Result<()> {
833 let payload = json!({
834 "response": jarm_response.response_token,
835 "client_id": jarm_response.client_id,
836 "response_id": jarm_response.response_id,
837 "delivered_at": Utc::now(),
838 });
839
840 let response = self
841 .http_client
842 .post_json(endpoint, &payload)
843 .await
844 .map_err(|e| AuthError::internal(format!("Failed to push JARM response: {}", e)))?;
845
846 if !response.status().is_success() {
847 return Err(AuthError::internal(format!(
848 "Push delivery failed with status: {}",
849 response.status()
850 )));
851 }
852
853 Ok(())
854 }
855
856 async fn log_jarm_creation(&self, client_id: &str, delivery_mode: &JarmDeliveryMode) {
858 eprintln!(
860 "AUDIT: JARM response created for client {} with delivery mode {:?}",
861 client_id, delivery_mode
862 );
863 }
864
865 pub fn config(&self) -> &AdvancedJarmConfig {
867 &self.config
868 }
869
870 pub fn revoke_jarm_token(&self, jti: &str) -> Result<()> {
872 self.jwt_validator
873 .revoke_token(jti)
874 .map_err(|e| AuthError::validation(format!("Failed to revoke JARM token: {}", e)))
875 }
876
877 pub fn is_jarm_token_revoked(&self, jti: &str) -> Result<bool> {
879 self.jwt_validator.is_token_revoked(jti).map_err(|e| {
880 AuthError::validation(format!("Failed to check token revocation status: {}", e))
881 })
882 }
883
884 pub fn get_jwt_validator(&self) -> &Arc<SecureJwtValidator> {
886 &self.jwt_validator
887 }
888}
889
890#[derive(Debug, Clone, Serialize, Deserialize)]
892pub struct AuthorizationResponse {
893 pub code: Option<String>,
895 pub access_token: Option<String>,
897 pub id_token: Option<String>,
899 pub state: Option<String>,
901 pub token_type: Option<String>,
903 pub expires_in: Option<u64>,
905 pub scope: Option<String>,
907 pub error: Option<String>,
909 pub error_description: Option<String>,
911}
912
913#[derive(Debug, Clone, Serialize, Deserialize)]
915pub struct JarmResponse {
916 pub response_token: String,
918 pub delivery_mode: JarmDeliveryMode,
920 pub expires_at: DateTime<Utc>,
922 pub client_id: String,
924 pub response_id: String,
926}
927
928#[derive(Debug, Clone)]
930pub struct JarmValidationResult {
931 pub valid: bool,
933 pub claims: HashMap<String, Value>,
935 pub errors: Vec<String>,
937}
938
939#[derive(Debug, Clone)]
941pub enum DeliveryResult {
942 Redirect(String),
944 FormPost(String),
946 Push {
948 success: bool,
950 endpoint: String,
952 },
953}
954
955#[cfg(test)]
956mod tests {
957 use super::*;
958
959 #[tokio::test]
960 async fn test_jarm_response_creation() {
961 let config = AdvancedJarmConfig {
963 supported_algorithms: vec![Algorithm::HS256], enable_response_validation: false, ..Default::default()
966 };
967 let manager = AdvancedJarmManager::new(config);
968
969 let auth_response = AuthorizationResponse {
970 code: Some("auth_code_123".to_string()),
971 state: Some("client_state".to_string()),
972 access_token: None,
973 id_token: None,
974 token_type: None,
975 expires_in: None,
976 scope: None,
977 error: None,
978 error_description: None,
979 };
980
981 let jarm_response = manager
982 .create_jarm_response("test_client", &auth_response, JarmDeliveryMode::Query, None)
983 .await
984 .unwrap();
985
986 assert!(!jarm_response.response_token.is_empty());
987 assert_eq!(jarm_response.delivery_mode, JarmDeliveryMode::Query);
988 assert_eq!(jarm_response.client_id, "test_client");
989 }
990
991 #[tokio::test]
992 async fn test_custom_claims_validation() {
993 let config = AdvancedJarmConfig {
994 max_custom_claims: 2,
995 ..Default::default()
996 };
997 let manager = AdvancedJarmManager::new(config);
998
999 let auth_response = AuthorizationResponse {
1000 code: Some("code123".to_string()),
1001 state: None,
1002 access_token: None,
1003 id_token: None,
1004 token_type: None,
1005 expires_in: None,
1006 scope: None,
1007 error: None,
1008 error_description: None,
1009 };
1010
1011 let mut custom_claims = HashMap::new();
1012 custom_claims.insert("claim1".to_string(), json!("value1"));
1013 custom_claims.insert("claim2".to_string(), json!("value2"));
1014 custom_claims.insert("claim3".to_string(), json!("value3")); let result = manager
1017 .create_jarm_response(
1018 "test_client",
1019 &auth_response,
1020 JarmDeliveryMode::Query,
1021 Some(custom_claims),
1022 )
1023 .await;
1024
1025 assert!(result.is_err());
1026 }
1027
1028 #[test]
1029 fn test_form_post_html_generation() {
1030 let config = AdvancedJarmConfig::default();
1031 let manager = AdvancedJarmManager::new(config);
1032
1033 let html = manager.generate_form_post_html(
1034 "https://client.example.com/callback",
1035 "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
1036 );
1037
1038 assert!(html.contains("https://client.example.com/callback"));
1039 assert!(html.contains("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"));
1040 assert!(html.contains("jarm_form"));
1041 }
1042
1043 #[tokio::test]
1044 async fn test_delivery_mode_validation() {
1045 let config = AdvancedJarmConfig {
1046 supported_delivery_modes: vec![JarmDeliveryMode::Query],
1047 supported_algorithms: vec![Algorithm::HS256], ..Default::default()
1049 };
1050 let manager = AdvancedJarmManager::new(config);
1051
1052 let auth_response = AuthorizationResponse {
1053 code: Some("code123".to_string()),
1054 state: None,
1055 access_token: None,
1056 id_token: None,
1057 token_type: None,
1058 expires_in: None,
1059 scope: None,
1060 error: None,
1061 error_description: None,
1062 };
1063
1064 let result = manager
1066 .create_jarm_response("test_client", &auth_response, JarmDeliveryMode::Query, None)
1067 .await;
1068 assert!(result.is_ok());
1069
1070 let result = manager
1072 .create_jarm_response("test_client", &auth_response, JarmDeliveryMode::Push, None)
1073 .await;
1074 assert!(result.is_err());
1075 }
1076}
1077
1078