handlers_integration.rs (61961B)
1 use anyhow::Result; 2 use axum::{ 3 body::{Body, to_bytes}, 4 http::{Request, StatusCode}, 5 routing::{get, post}, 6 Router, 7 }; 8 use kych_oauth2_gateway_lib::{ 9 config::{ClientConfig, Config, CryptoConfig, DatabaseConfig, ServerConfig, VcConfig}, 10 db::{authorization_codes, clients, sessions}, 11 handlers, 12 models::{ 13 Constraint, Field, Filter, InputDescriptor, PresentationDefinition, SwiyuManagementResponse, 14 SwiyuVerificationStatus, TokenResponse, 15 }, 16 state::AppState, 17 }; 18 use std::collections::HashSet; 19 use mockito::Server; 20 use serde_json::Value; 21 use sqlx::{PgPool, postgres::PgPoolOptions}; 22 use tower::util::ServiceExt; 23 use uuid::Uuid; 24 25 async fn get_pool() -> Option<PgPool> { 26 let url = match std::env::var("DATABASE_URL") { 27 Ok(url) if !url.trim().is_empty() => url, 28 _ => { 29 eprintln!("DATABASE_URL not set; skipping handler integration tests."); 30 return None; 31 } 32 }; 33 34 match PgPoolOptions::new().max_connections(5).connect(&url).await { 35 Ok(pool) => Some(pool), 36 Err(err) => { 37 eprintln!("Failed to connect to DATABASE_URL; skipping tests: {}", err); 38 None 39 } 40 } 41 } 42 43 fn test_config(database_url: &str) -> Config { 44 Config { 45 server: ServerConfig { 46 host: Some("127.0.0.1".to_string()), 47 port: Some(8080), 48 socket_path: None, 49 socket_mode: 0o666, 50 }, 51 database: DatabaseConfig { 52 url: database_url.to_string(), 53 }, 54 crypto: CryptoConfig { 55 nonce_bytes: 32, 56 token_bytes: 32, 57 authorization_code_bytes: 32, 58 authorization_code_ttl_minutes: 10, 59 }, 60 vc: VcConfig { 61 vc_type: "betaid-sdjwt".to_string(), 62 vc_format: "vc+sd-jwt".to_string(), 63 vc_algorithms: vec!["ES256".to_string()], 64 vc_claims: [ 65 "first_name", 66 "last_name", 67 "family_name", 68 "given_name", 69 "birth_date", 70 "age_over_18", 71 ] 72 .iter() 73 .map(|s| s.to_string()) 74 .collect::<HashSet<String>>(), 75 }, 76 allowed_scopes: None, 77 clients: Vec::<ClientConfig>::new(), 78 } 79 } 80 81 fn build_app(state: AppState) -> Router { 82 Router::new() 83 .route("/config", get(handlers::config)) 84 .route("/setup/{client_id}", post(handlers::setup)) 85 .route("/authorize/{nonce}", get(handlers::authorize)) 86 .route("/token", post(handlers::token)) 87 .route("/info", get(handlers::info)) 88 .route("/notification", post(handlers::notification_webhook)) 89 .route("/status/{verification_id}", get(handlers::status)) 90 .route("/finalize/{verification_id}", get(handlers::finalize)) 91 .with_state(state) 92 } 93 94 struct TestClient { 95 client: clients::Client, 96 secret: String, 97 } 98 99 async fn create_test_client(pool: &PgPool) -> Result<TestClient> { 100 let suffix = Uuid::new_v4().to_string(); 101 let client_id = format!("test-client-{}", suffix); 102 let secret = format!("secret-{}", suffix); 103 let verifier_url = "https://verifier.example"; 104 let redirect_uri = "https://example.com/callback"; 105 let accepted_issuer_dids = Some("did:example:issuer1,did:example:issuer2"); 106 107 let client = clients::register_client( 108 pool, 109 &client_id, 110 &secret, 111 verifier_url, 112 None, 113 redirect_uri, 114 accepted_issuer_dids, 115 ) 116 .await?; 117 118 Ok(TestClient { client, secret }) 119 } 120 121 async fn create_test_client_with_verifier( 122 pool: &PgPool, 123 verifier_url: &str, 124 ) -> Result<TestClient> { 125 let suffix = Uuid::new_v4().to_string(); 126 let client_id = format!("test-client-{}", suffix); 127 let secret = format!("secret-{}", suffix); 128 let redirect_uri = "https://example.com/callback"; 129 let accepted_issuer_dids = Some("did:example:issuer1,did:example:issuer2"); 130 131 let client = clients::register_client( 132 pool, 133 &client_id, 134 &secret, 135 verifier_url, 136 None, 137 redirect_uri, 138 accepted_issuer_dids, 139 ) 140 .await?; 141 142 Ok(TestClient { client, secret }) 143 } 144 145 async fn create_second_client(pool: &PgPool) -> Result<TestClient> { 146 let suffix = Uuid::new_v4().to_string(); 147 let client_id = format!("test-client-b-{}", suffix); 148 let secret = format!("secret-b-{}", suffix); 149 let verifier_url = "https://verifier.example"; 150 let redirect_uri = "https://example.com/callback"; 151 let accepted_issuer_dids = Some("did:example:issuer1,did:example:issuer2"); 152 153 let client = clients::register_client( 154 pool, 155 &client_id, 156 &secret, 157 verifier_url, 158 None, 159 redirect_uri, 160 accepted_issuer_dids, 161 ) 162 .await?; 163 164 Ok(TestClient { client, secret }) 165 } 166 167 fn sample_presentation_definition() -> PresentationDefinition { 168 PresentationDefinition { 169 id: "pd-1".to_string(), 170 name: None, 171 purpose: None, 172 format: None, 173 input_descriptors: vec![InputDescriptor { 174 id: "descriptor-1".to_string(), 175 name: None, 176 purpose: None, 177 format: None, 178 constraints: Constraint { 179 fields: vec![Field { 180 path: vec!["$.vct".to_string()], 181 id: None, 182 name: None, 183 purpose: None, 184 filter: Some(Filter { 185 filter_type: "string".to_string(), 186 const_value: Some("betaid-sdjwt".to_string()), 187 }), 188 }], 189 }, 190 }], 191 } 192 } 193 194 async fn get_session_status(pool: &PgPool, session_id: Uuid) -> Result<sessions::SessionStatus> { 195 let status = sqlx::query_scalar::<_, sessions::SessionStatus>( 196 r#" 197 SELECT status 198 FROM oauth2gw.verification_sessions 199 WHERE id = $1 200 "#, 201 ) 202 .bind(session_id) 203 .fetch_one(pool) 204 .await?; 205 206 Ok(status) 207 } 208 209 struct SessionData { 210 client: clients::Client, 211 secret: String, 212 request_id: String, 213 redirect_uri: String, 214 state: String, 215 authorization_code: String, 216 } 217 218 async fn setup_session_with_status( 219 pool: &PgPool, 220 status: sessions::SessionStatus, 221 ) -> Result<SessionData> { 222 let test_client = create_test_client(pool).await?; 223 let client = &test_client.client; 224 225 let nonce = format!("nonce-{}", Uuid::new_v4()); 226 let session = sessions::create_session(pool, &client.client_id, &nonce, 5) 227 .await? 228 .expect("client should exist"); 229 230 let redirect_uri = "https://example.com/callback".to_string(); 231 let state = "state-123".to_string(); 232 let authorize = sessions::get_session_for_authorize( 233 pool, 234 &nonce, 235 &client.client_id, 236 "first_name", 237 &redirect_uri, 238 &state, 239 ) 240 .await? 241 .expect("session should exist"); 242 243 let request_id = Uuid::new_v4().to_string(); 244 let _ = sessions::update_session_authorized( 245 pool, 246 authorize.session_id, 247 "https://verifier.example/verify/1", 248 None, 249 &request_id, 250 None, 251 ) 252 .await?; 253 254 let authorization_code = format!("code-{}", Uuid::new_v4()); 255 let issued = sessions::verify_session_and_issue_code( 256 pool, 257 session.id, 258 status, 259 &authorization_code, 260 10, 261 client.id, 262 "", 263 Some(&serde_json::json!({"vc": "data"})), 264 ) 265 .await?; 266 assert_eq!(issued, authorization_code); 267 268 Ok(SessionData { 269 client: client.clone(), 270 secret: test_client.secret, 271 request_id, 272 redirect_uri, 273 state, 274 authorization_code, 275 }) 276 } 277 278 async fn setup_authorized_session( 279 pool: &PgPool, 280 verifier_url: &str, 281 ) -> Result<(TestClient, Uuid, Uuid)> { 282 let test_client = create_test_client_with_verifier(pool, verifier_url).await?; 283 let nonce = format!("nonce-{}", Uuid::new_v4()); 284 let session = sessions::create_session(pool, &test_client.client.client_id, &nonce, 5) 285 .await? 286 .expect("client should exist"); 287 288 let verification_id = Uuid::new_v4(); 289 let _ = sessions::update_session_authorized( 290 pool, 291 session.id, 292 "https://verifier.example/verify/1", 293 None, 294 &verification_id.to_string(), 295 None, 296 ) 297 .await?; 298 299 Ok((test_client, session.id, verification_id)) 300 } 301 302 fn form_body(pairs: &[(&str, &str)]) -> String { 303 pairs 304 .iter() 305 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v))) 306 .collect::<Vec<_>>() 307 .join("&") 308 } 309 310 async fn assert_error_response( 311 response: axum::response::Response, 312 status: StatusCode, 313 expected_error: &str, 314 ) -> Result<()> { 315 assert_eq!(response.status(), status); 316 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 317 let json: Value = serde_json::from_slice(&bytes)?; 318 let error = json.get("error").and_then(|v| v.as_str()).unwrap_or(""); 319 assert_eq!(error, expected_error); 320 Ok(()) 321 } 322 323 #[tokio::test] 324 async fn test_config_endpoint() -> Result<()> { 325 let Some(pool) = get_pool().await else { return Ok(()); }; 326 let config = test_config(&std::env::var("DATABASE_URL")?); 327 let app = build_app(AppState::new(config, pool)); 328 329 let response = app 330 .oneshot(Request::builder().uri("/config").body(Body::empty())?) 331 .await?; 332 333 assert_eq!(response.status(), StatusCode::OK); 334 335 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 336 let json: Value = serde_json::from_slice(&bytes)?; 337 338 assert_eq!(json.get("name").and_then(|v| v.as_str()), Some("kych-oauth2-gateway")); 339 assert!(json.get("version").and_then(|v| v.as_str()).is_some()); 340 assert_eq!(json.get("status").and_then(|v| v.as_str()), Some("healthy")); 341 assert_eq!(json.get("vc_type").and_then(|v| v.as_str()), Some("betaid-sdjwt")); 342 assert_eq!(json.get("vc_format").and_then(|v| v.as_str()), Some("vc+sd-jwt")); 343 assert!(json.get("vc_algorithms").and_then(|v| v.as_array()).is_some()); 344 assert!(json.get("vc_claims").and_then(|v| v.as_array()).is_some()); 345 346 Ok(()) 347 } 348 349 #[tokio::test] 350 async fn test_setup_unauthorized() -> Result<()> { 351 let Some(pool) = get_pool().await else { return Ok(()); }; 352 let config = test_config(&std::env::var("DATABASE_URL")?); 353 let app = build_app(AppState::new(config, pool)); 354 355 let response = app 356 .oneshot( 357 Request::builder() 358 .method("POST") 359 .uri("/setup/unknown-client") 360 .body(Body::empty())?, 361 ) 362 .await?; 363 364 assert_error_response(response, StatusCode::UNAUTHORIZED, "unauthorized").await?; 365 Ok(()) 366 } 367 368 #[tokio::test] 369 async fn test_setup_success() -> Result<()> { 370 let Some(pool) = get_pool().await else { return Ok(()); }; 371 let config = test_config(&std::env::var("DATABASE_URL")?); 372 let app = build_app(AppState::new(config, pool.clone())); 373 374 let test_client = create_test_client(&pool).await?; 375 376 let response = app 377 .oneshot( 378 Request::builder() 379 .method("POST") 380 .uri(format!("/setup/{}", test_client.client.client_id)) 381 .header("authorization", format!("Bearer {}", test_client.secret)) 382 .body(Body::empty())?, 383 ) 384 .await?; 385 386 assert_eq!(response.status(), StatusCode::OK); 387 388 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 389 let json: Value = serde_json::from_slice(&bytes)?; 390 let nonce = json 391 .get("nonce") 392 .and_then(|v| v.as_str()) 393 .unwrap_or(""); 394 assert!(!nonce.is_empty()); 395 assert!(nonce.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_')); 396 397 let _ = clients::delete_client(&pool, test_client.client.id).await?; 398 Ok(()) 399 } 400 401 #[tokio::test] 402 async fn test_token_success_and_info() -> Result<()> { 403 let Some(pool) = get_pool().await else { return Ok(()); }; 404 let config = test_config(&std::env::var("DATABASE_URL")?); 405 let app = build_app(AppState::new(config, pool.clone())); 406 407 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 408 409 let form = form_body(&[ 410 ("grant_type", "authorization_code"), 411 ("code", &session.authorization_code), 412 ("client_id", &session.client.client_id), 413 ("client_secret", &session.secret), 414 ("redirect_uri", &session.redirect_uri), 415 ]); 416 417 let response = app 418 .clone() 419 .oneshot( 420 Request::builder() 421 .method("POST") 422 .uri("/token") 423 .header("content-type", "application/x-www-form-urlencoded") 424 .body(Body::from(form))?, 425 ) 426 .await?; 427 428 assert_eq!(response.status(), StatusCode::OK); 429 430 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 431 let token: TokenResponse = serde_json::from_slice(&bytes)?; 432 assert!(!token.access_token.is_empty()); 433 assert_eq!(token.token_type, "Bearer"); 434 435 let response = app 436 .oneshot( 437 Request::builder() 438 .method("GET") 439 .uri("/info") 440 .header("authorization", format!("Bearer {}", token.access_token)) 441 .body(Body::empty())?, 442 ) 443 .await?; 444 445 assert_eq!(response.status(), StatusCode::OK); 446 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 447 let json: Value = serde_json::from_slice(&bytes)?; 448 assert_eq!(json.get("vc").and_then(|v| v.as_str()), Some("data")); 449 450 let _ = clients::delete_client(&pool, session.client.id).await?; 451 Ok(()) 452 } 453 454 #[tokio::test] 455 async fn test_token_redirect_uri_mismatch() -> Result<()> { 456 let Some(pool) = get_pool().await else { return Ok(()); }; 457 let config = test_config(&std::env::var("DATABASE_URL")?); 458 let app = build_app(AppState::new(config, pool.clone())); 459 460 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 461 462 let form = form_body(&[ 463 ("grant_type", "authorization_code"), 464 ("code", &session.authorization_code), 465 ("client_id", &session.client.client_id), 466 ("client_secret", &session.secret), 467 ("redirect_uri", "https://example.com/wrong"), 468 ]); 469 470 let response = app 471 .oneshot( 472 Request::builder() 473 .method("POST") 474 .uri("/token") 475 .header("content-type", "application/x-www-form-urlencoded") 476 .body(Body::from(form))?, 477 ) 478 .await?; 479 480 assert_error_response(response, StatusCode::BAD_REQUEST, "invalid_grant").await?; 481 482 let _ = clients::delete_client(&pool, session.client.id).await?; 483 Ok(()) 484 } 485 486 #[tokio::test] 487 async fn test_status_and_finalize() -> Result<()> { 488 let Some(pool) = get_pool().await else { return Ok(()); }; 489 let config = test_config(&std::env::var("DATABASE_URL")?); 490 let app = build_app(AppState::new(config, pool.clone())); 491 492 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 493 494 let response = app 495 .clone() 496 .oneshot( 497 Request::builder() 498 .method("GET") 499 .uri(format!( 500 "/status/{}?state={}", 501 session.request_id, session.state 502 )) 503 .body(Body::empty())?, 504 ) 505 .await?; 506 507 assert_eq!(response.status(), StatusCode::OK); 508 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 509 let json: Value = serde_json::from_slice(&bytes)?; 510 assert_eq!(json.get("status").and_then(|v| v.as_str()), Some("verified")); 511 512 let response = app 513 .oneshot( 514 Request::builder() 515 .method("GET") 516 .uri(format!( 517 "/finalize/{}?state={}", 518 session.request_id, session.state 519 )) 520 .body(Body::empty())?, 521 ) 522 .await?; 523 524 assert_eq!(response.status(), StatusCode::FOUND); 525 let location = response 526 .headers() 527 .get("location") 528 .and_then(|v| v.to_str().ok()) 529 .unwrap_or(""); 530 assert!(location.contains("code=")); 531 assert!(location.contains("state=")); 532 533 let _ = clients::delete_client(&pool, session.client.id).await?; 534 Ok(()) 535 } 536 537 #[tokio::test] 538 async fn test_status_invalid_state() -> Result<()> { 539 let Some(pool) = get_pool().await else { return Ok(()); }; 540 let config = test_config(&std::env::var("DATABASE_URL")?); 541 let app = build_app(AppState::new(config, pool.clone())); 542 543 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 544 545 let response = app 546 .oneshot( 547 Request::builder() 548 .method("GET") 549 .uri(format!( 550 "/status/{}?state={}", 551 session.request_id, "wrong-state" 552 )) 553 .body(Body::empty())?, 554 ) 555 .await?; 556 557 assert_error_response(response, StatusCode::FORBIDDEN, "invalid_state").await?; 558 559 let _ = clients::delete_client(&pool, session.client.id).await?; 560 Ok(()) 561 } 562 563 #[tokio::test] 564 async fn test_status_not_found() -> Result<()> { 565 let Some(pool) = get_pool().await else { return Ok(()); }; 566 let config = test_config(&std::env::var("DATABASE_URL")?); 567 let app = build_app(AppState::new(config, pool)); 568 569 let response = app 570 .oneshot( 571 Request::builder() 572 .method("GET") 573 .uri(format!("/status/{}?state=state", Uuid::new_v4())) 574 .body(Body::empty())?, 575 ) 576 .await?; 577 578 assert_error_response(response, StatusCode::NOT_FOUND, "session_not_found").await?; 579 Ok(()) 580 } 581 582 #[tokio::test] 583 async fn test_finalize_invalid_state() -> Result<()> { 584 let Some(pool) = get_pool().await else { return Ok(()); }; 585 let config = test_config(&std::env::var("DATABASE_URL")?); 586 let app = build_app(AppState::new(config, pool.clone())); 587 588 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 589 590 let response = app 591 .oneshot( 592 Request::builder() 593 .method("GET") 594 .uri(format!( 595 "/finalize/{}?state={}", 596 session.request_id, "wrong-state" 597 )) 598 .body(Body::empty())?, 599 ) 600 .await?; 601 602 assert_error_response(response, StatusCode::FORBIDDEN, "invalid_state").await?; 603 604 let _ = clients::delete_client(&pool, session.client.id).await?; 605 Ok(()) 606 } 607 608 #[tokio::test] 609 async fn test_finalize_not_verified() -> Result<()> { 610 let Some(pool) = get_pool().await else { return Ok(()); }; 611 let config = test_config(&std::env::var("DATABASE_URL")?); 612 let app = build_app(AppState::new(config, pool.clone())); 613 614 let session = setup_session_with_status(&pool, sessions::SessionStatus::Failed).await?; 615 616 let response = app 617 .oneshot( 618 Request::builder() 619 .method("GET") 620 .uri(format!( 621 "/finalize/{}?state={}", 622 session.request_id, session.state 623 )) 624 .body(Body::empty())?, 625 ) 626 .await?; 627 628 assert_error_response(response, StatusCode::BAD_REQUEST, "not_verified").await?; 629 630 let _ = clients::delete_client(&pool, session.client.id).await?; 631 Ok(()) 632 } 633 634 #[tokio::test] 635 async fn test_finalize_not_found() -> Result<()> { 636 let Some(pool) = get_pool().await else { return Ok(()); }; 637 let config = test_config(&std::env::var("DATABASE_URL")?); 638 let app = build_app(AppState::new(config, pool)); 639 640 let response = app 641 .oneshot( 642 Request::builder() 643 .method("GET") 644 .uri(format!("/finalize/{}?state=state", Uuid::new_v4())) 645 .body(Body::empty())?, 646 ) 647 .await?; 648 649 assert_error_response(response, StatusCode::NOT_FOUND, "session_not_found").await?; 650 Ok(()) 651 } 652 653 #[tokio::test] 654 async fn test_token_invalid_grant_type() -> Result<()> { 655 let Some(pool) = get_pool().await else { return Ok(()); }; 656 let config = test_config(&std::env::var("DATABASE_URL")?); 657 let app = build_app(AppState::new(config, pool.clone())); 658 659 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 660 661 let form = form_body(&[ 662 ("grant_type", "client_credentials"), 663 ("code", &session.authorization_code), 664 ("client_id", &session.client.client_id), 665 ("client_secret", &session.secret), 666 ("redirect_uri", &session.redirect_uri), 667 ]); 668 669 let response = app 670 .oneshot( 671 Request::builder() 672 .method("POST") 673 .uri("/token") 674 .header("content-type", "application/x-www-form-urlencoded") 675 .body(Body::from(form))?, 676 ) 677 .await?; 678 679 assert_error_response(response, StatusCode::BAD_REQUEST, "unsupported_grant_type").await?; 680 681 let _ = clients::delete_client(&pool, session.client.id).await?; 682 Ok(()) 683 } 684 685 #[tokio::test] 686 async fn test_token_invalid_client() -> Result<()> { 687 let Some(pool) = get_pool().await else { return Ok(()); }; 688 let config = test_config(&std::env::var("DATABASE_URL")?); 689 let app = build_app(AppState::new(config, pool.clone())); 690 691 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 692 693 let form = form_body(&[ 694 ("grant_type", "authorization_code"), 695 ("code", &session.authorization_code), 696 ("client_id", &session.client.client_id), 697 ("client_secret", "wrong-secret"), 698 ("redirect_uri", &session.redirect_uri), 699 ]); 700 701 let response = app 702 .oneshot( 703 Request::builder() 704 .method("POST") 705 .uri("/token") 706 .header("content-type", "application/x-www-form-urlencoded") 707 .body(Body::from(form))?, 708 ) 709 .await?; 710 711 assert_error_response(response, StatusCode::UNAUTHORIZED, "invalid_client").await?; 712 713 let _ = clients::delete_client(&pool, session.client.id).await?; 714 Ok(()) 715 } 716 717 #[tokio::test] 718 async fn test_token_used_code_rejected() -> Result<()> { 719 let Some(pool) = get_pool().await else { return Ok(()); }; 720 let config = test_config(&std::env::var("DATABASE_URL")?); 721 let app = build_app(AppState::new(config, pool.clone())); 722 723 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 724 725 let _ = authorization_codes::get_code_for_token_exchange(&pool, &session.authorization_code) 726 .await? 727 .expect("code should exist"); 728 729 let form = form_body(&[ 730 ("grant_type", "authorization_code"), 731 ("code", &session.authorization_code), 732 ("client_id", &session.client.client_id), 733 ("client_secret", &session.secret), 734 ("redirect_uri", &session.redirect_uri), 735 ]); 736 737 let response = app 738 .oneshot( 739 Request::builder() 740 .method("POST") 741 .uri("/token") 742 .header("content-type", "application/x-www-form-urlencoded") 743 .body(Body::from(form))?, 744 ) 745 .await?; 746 747 assert_error_response(response, StatusCode::BAD_REQUEST, "invalid_grant").await?; 748 749 let _ = clients::delete_client(&pool, session.client.id).await?; 750 Ok(()) 751 } 752 753 #[tokio::test] 754 async fn test_token_wrong_session_status() -> Result<()> { 755 let Some(pool) = get_pool().await else { return Ok(()); }; 756 let config = test_config(&std::env::var("DATABASE_URL")?); 757 let app = build_app(AppState::new(config, pool.clone())); 758 759 let session = setup_session_with_status(&pool, sessions::SessionStatus::Failed).await?; 760 761 let form = form_body(&[ 762 ("grant_type", "authorization_code"), 763 ("code", &session.authorization_code), 764 ("client_id", &session.client.client_id), 765 ("client_secret", &session.secret), 766 ("redirect_uri", &session.redirect_uri), 767 ]); 768 769 let response = app 770 .oneshot( 771 Request::builder() 772 .method("POST") 773 .uri("/token") 774 .header("content-type", "application/x-www-form-urlencoded") 775 .body(Body::from(form))?, 776 ) 777 .await?; 778 779 assert_error_response(response, StatusCode::BAD_REQUEST, "invalid_grant").await?; 780 781 let _ = clients::delete_client(&pool, session.client.id).await?; 782 Ok(()) 783 } 784 785 #[tokio::test] 786 async fn test_info_missing_authorization() -> Result<()> { 787 let Some(pool) = get_pool().await else { return Ok(()); }; 788 let config = test_config(&std::env::var("DATABASE_URL")?); 789 let app = build_app(AppState::new(config, pool)); 790 791 let response = app 792 .oneshot( 793 Request::builder() 794 .method("GET") 795 .uri("/info") 796 .body(Body::empty())?, 797 ) 798 .await?; 799 800 assert_error_response(response, StatusCode::UNAUTHORIZED, "invalid_token").await?; 801 Ok(()) 802 } 803 804 #[tokio::test] 805 async fn test_token_code_for_different_client() -> Result<()> { 806 let Some(pool) = get_pool().await else { return Ok(()); }; 807 let config = test_config(&std::env::var("DATABASE_URL")?); 808 let app = build_app(AppState::new(config, pool.clone())); 809 810 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 811 let other_client = create_second_client(&pool).await?; 812 813 let form = form_body(&[ 814 ("grant_type", "authorization_code"), 815 ("code", &session.authorization_code), 816 ("client_id", &other_client.client.client_id), 817 ("client_secret", &other_client.secret), 818 ("redirect_uri", &session.redirect_uri), 819 ]); 820 821 let response = app 822 .oneshot( 823 Request::builder() 824 .method("POST") 825 .uri("/token") 826 .header("content-type", "application/x-www-form-urlencoded") 827 .body(Body::from(form))?, 828 ) 829 .await?; 830 831 assert_error_response(response, StatusCode::BAD_REQUEST, "invalid_grant").await?; 832 833 let _ = clients::delete_client(&pool, session.client.id).await?; 834 let _ = clients::delete_client(&pool, other_client.client.id).await?; 835 Ok(()) 836 } 837 838 #[tokio::test] 839 async fn test_token_expired_code() -> Result<()> { 840 let Some(pool) = get_pool().await else { return Ok(()); }; 841 let config = test_config(&std::env::var("DATABASE_URL")?); 842 let app = build_app(AppState::new(config, pool.clone())); 843 844 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 845 846 sqlx::query( 847 r#" 848 UPDATE oauth2gw.authorization_codes 849 SET expires_at = NOW() - INTERVAL '1 minute' 850 WHERE code = $1 851 "#, 852 ) 853 .bind(&session.authorization_code) 854 .execute(&pool) 855 .await?; 856 857 let form = form_body(&[ 858 ("grant_type", "authorization_code"), 859 ("code", &session.authorization_code), 860 ("client_id", &session.client.client_id), 861 ("client_secret", &session.secret), 862 ("redirect_uri", &session.redirect_uri), 863 ]); 864 865 let response = app 866 .oneshot( 867 Request::builder() 868 .method("POST") 869 .uri("/token") 870 .header("content-type", "application/x-www-form-urlencoded") 871 .body(Body::from(form))?, 872 ) 873 .await?; 874 875 assert_error_response(response, StatusCode::BAD_REQUEST, "invalid_grant").await?; 876 877 let _ = clients::delete_client(&pool, session.client.id).await?; 878 Ok(()) 879 } 880 881 #[tokio::test] 882 async fn test_info_revoked_token() -> Result<()> { 883 let Some(pool) = get_pool().await else { return Ok(()); }; 884 let config = test_config(&std::env::var("DATABASE_URL")?); 885 let app = build_app(AppState::new(config, pool.clone())); 886 887 let session = setup_session_with_status(&pool, sessions::SessionStatus::Verified).await?; 888 889 let form = form_body(&[ 890 ("grant_type", "authorization_code"), 891 ("code", &session.authorization_code), 892 ("client_id", &session.client.client_id), 893 ("client_secret", &session.secret), 894 ("redirect_uri", &session.redirect_uri), 895 ]); 896 897 let response = app 898 .clone() 899 .oneshot( 900 Request::builder() 901 .method("POST") 902 .uri("/token") 903 .header("content-type", "application/x-www-form-urlencoded") 904 .body(Body::from(form))?, 905 ) 906 .await?; 907 908 assert_eq!(response.status(), StatusCode::OK); 909 910 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 911 let token: TokenResponse = serde_json::from_slice(&bytes)?; 912 913 sqlx::query( 914 r#" 915 UPDATE oauth2gw.access_tokens 916 SET revoked = TRUE, revoked_at = NOW() 917 WHERE token = $1 918 "#, 919 ) 920 .bind(&token.access_token) 921 .execute(&pool) 922 .await?; 923 924 let response = app 925 .oneshot( 926 Request::builder() 927 .method("GET") 928 .uri("/info") 929 .header("authorization", format!("Bearer {}", token.access_token)) 930 .body(Body::empty())?, 931 ) 932 .await?; 933 934 assert_error_response(response, StatusCode::UNAUTHORIZED, "invalid_token").await?; 935 936 let _ = clients::delete_client(&pool, session.client.id).await?; 937 Ok(()) 938 } 939 940 #[tokio::test] 941 async fn test_authorize_success() -> Result<()> { 942 let Some(pool) = get_pool().await else { return Ok(()); }; 943 944 let mut server = Server::new_async().await; 945 let verifier_url = server.url(); 946 let config = test_config(&std::env::var("DATABASE_URL")?); 947 let app = build_app(AppState::new(config, pool.clone())); 948 949 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 950 951 let nonce = format!("nonce-{}", Uuid::new_v4()); 952 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 953 .await? 954 .expect("client should exist"); 955 956 let verification_id = Uuid::new_v4(); 957 let response_body = SwiyuManagementResponse { 958 id: verification_id, 959 request_nonce: Some("req-nonce".to_string()), 960 state: SwiyuVerificationStatus::Pending, 961 verification_url: "https://verifier.example/verify/1".to_string(), 962 verification_deeplink: Some("swiyu-verify://verify/1".to_string()), 963 presentation_definition: sample_presentation_definition(), 964 dcql_query: None, 965 wallet_response: None, 966 }; 967 let response_json = serde_json::to_string(&response_body)?; 968 969 let _mock = server 970 .mock("POST", "/management/api/verifications") 971 .with_status(200) 972 .with_header("content-type", "application/json") 973 .with_body(response_json) 974 .create_async() 975 .await; 976 977 let uri = format!( 978 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 979 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 980 ); 981 982 let response = app 983 .oneshot( 984 Request::builder() 985 .method("GET") 986 .uri(uri) 987 .body(Body::empty())?, 988 ) 989 .await?; 990 991 assert_eq!(response.status(), StatusCode::OK); 992 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 993 let json: Value = serde_json::from_slice(&bytes)?; 994 let verification_id_str = json 995 .get("verificationId") 996 .and_then(|v| v.as_str()) 997 .unwrap_or(""); 998 assert_eq!(verification_id_str, verification_id.to_string()); 999 1000 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1001 Ok(()) 1002 } 1003 1004 #[tokio::test] 1005 async fn test_authorize_html_response() -> Result<()> { 1006 let Some(pool) = get_pool().await else { return Ok(()); }; 1007 1008 let mut server = Server::new_async().await; 1009 let verifier_url = server.url(); 1010 let config = test_config(&std::env::var("DATABASE_URL")?); 1011 let app = build_app(AppState::new(config, pool.clone())); 1012 1013 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1014 1015 let nonce = format!("nonce-{}", Uuid::new_v4()); 1016 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1017 .await? 1018 .expect("client should exist"); 1019 1020 let verification_id = Uuid::new_v4(); 1021 let response_body = SwiyuManagementResponse { 1022 id: verification_id, 1023 request_nonce: Some("req-nonce".to_string()), 1024 state: SwiyuVerificationStatus::Pending, 1025 verification_url: "https://verifier.example/verify/1".to_string(), 1026 verification_deeplink: Some("swiyu-verify://verify/1".to_string()), 1027 presentation_definition: sample_presentation_definition(), 1028 dcql_query: None, 1029 wallet_response: None, 1030 }; 1031 let response_json = serde_json::to_string(&response_body)?; 1032 1033 let _mock = server 1034 .mock("POST", "/management/api/verifications") 1035 .with_status(200) 1036 .with_header("content-type", "application/json") 1037 .with_body(response_json) 1038 .create_async() 1039 .await; 1040 1041 let uri = format!( 1042 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1043 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1044 ); 1045 1046 let response = app 1047 .oneshot( 1048 Request::builder() 1049 .method("GET") 1050 .uri(uri) 1051 .header("accept", "text/html") 1052 .body(Body::empty())?, 1053 ) 1054 .await?; 1055 1056 assert_eq!(response.status(), StatusCode::OK); 1057 let content_type = response 1058 .headers() 1059 .get("content-type") 1060 .and_then(|v| v.to_str().ok()) 1061 .unwrap_or(""); 1062 assert!(content_type.contains("text/html")); 1063 let csp = response 1064 .headers() 1065 .get("content-security-policy") 1066 .and_then(|v| v.to_str().ok()) 1067 .unwrap_or(""); 1068 assert!(csp.contains("default-src 'self'")); 1069 assert!(csp.contains("script-src 'self' 'unsafe-inline'")); 1070 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 1071 let body = String::from_utf8(bytes.to_vec())?; 1072 assert!(body.contains("Identity Verification")); 1073 assert!(body.contains("https://verifier.example/verify/1")); 1074 1075 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1076 Ok(()) 1077 } 1078 1079 #[tokio::test] 1080 async fn test_authorize_invalid_verification_url() -> Result<()> { 1081 let Some(pool) = get_pool().await else { return Ok(()); }; 1082 1083 let mut server = Server::new_async().await; 1084 let verifier_url = server.url(); 1085 let config = test_config(&std::env::var("DATABASE_URL")?); 1086 let app = build_app(AppState::new(config, pool.clone())); 1087 1088 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1089 1090 let nonce = format!("nonce-{}", Uuid::new_v4()); 1091 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1092 .await? 1093 .expect("client should exist"); 1094 1095 let response_body = SwiyuManagementResponse { 1096 id: Uuid::new_v4(), 1097 request_nonce: Some("req-nonce".to_string()), 1098 state: SwiyuVerificationStatus::Pending, 1099 verification_url: "http://verifier.example/verify/1".to_string(), 1100 verification_deeplink: Some("swiyu-verify://verify/1".to_string()), 1101 presentation_definition: sample_presentation_definition(), 1102 dcql_query: None, 1103 wallet_response: None, 1104 }; 1105 let response_json = serde_json::to_string(&response_body)?; 1106 1107 let _mock = server 1108 .mock("POST", "/management/api/verifications") 1109 .with_status(200) 1110 .with_header("content-type", "application/json") 1111 .with_body(response_json) 1112 .create_async() 1113 .await; 1114 1115 let uri = format!( 1116 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1117 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1118 ); 1119 1120 let response = app 1121 .oneshot( 1122 Request::builder() 1123 .method("GET") 1124 .uri(uri) 1125 .body(Body::empty())?, 1126 ) 1127 .await?; 1128 1129 assert_error_response(response, StatusCode::BAD_GATEWAY, "invalid_verification_url").await?; 1130 1131 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1132 Ok(()) 1133 } 1134 1135 #[tokio::test] 1136 async fn test_authorize_invalid_verification_deeplink() -> Result<()> { 1137 let Some(pool) = get_pool().await else { return Ok(()); }; 1138 1139 let mut server = Server::new_async().await; 1140 let verifier_url = server.url(); 1141 let config = test_config(&std::env::var("DATABASE_URL")?); 1142 let app = build_app(AppState::new(config, pool.clone())); 1143 1144 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1145 1146 let nonce = format!("nonce-{}", Uuid::new_v4()); 1147 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1148 .await? 1149 .expect("client should exist"); 1150 1151 let response_body = SwiyuManagementResponse { 1152 id: Uuid::new_v4(), 1153 request_nonce: Some("req-nonce".to_string()), 1154 state: SwiyuVerificationStatus::Pending, 1155 verification_url: "https://verifier.example/verify/1".to_string(), 1156 verification_deeplink: Some("ftp://bad.example".to_string()), 1157 presentation_definition: sample_presentation_definition(), 1158 dcql_query: None, 1159 wallet_response: None, 1160 }; 1161 let response_json = serde_json::to_string(&response_body)?; 1162 1163 let _mock = server 1164 .mock("POST", "/management/api/verifications") 1165 .with_status(200) 1166 .with_header("content-type", "application/json") 1167 .with_body(response_json) 1168 .create_async() 1169 .await; 1170 1171 let uri = format!( 1172 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1173 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1174 ); 1175 1176 let response = app 1177 .oneshot( 1178 Request::builder() 1179 .method("GET") 1180 .uri(uri) 1181 .body(Body::empty())?, 1182 ) 1183 .await?; 1184 1185 assert_error_response(response, StatusCode::BAD_GATEWAY, "invalid_verification_deeplink").await?; 1186 1187 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1188 Ok(()) 1189 } 1190 1191 #[tokio::test] 1192 async fn test_authorize_cached_html_response() -> Result<()> { 1193 let Some(pool) = get_pool().await else { return Ok(()); }; 1194 1195 let verifier_url = "https://verifier.example".to_string(); 1196 let config = test_config(&std::env::var("DATABASE_URL")?); 1197 let app = build_app(AppState::new(config, pool.clone())); 1198 1199 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1200 1201 let nonce = format!("nonce-{}", Uuid::new_v4()); 1202 let session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1203 .await? 1204 .expect("client should exist"); 1205 1206 let verification_id = Uuid::new_v4().to_string(); 1207 let _ = sessions::update_session_authorized( 1208 &pool, 1209 session.id, 1210 "https://verifier.example/verify/1", 1211 Some("swiyu-verify://verify/1"), 1212 &verification_id, 1213 None, 1214 ) 1215 .await?; 1216 1217 let uri = format!( 1218 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1219 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1220 ); 1221 1222 let response = app 1223 .oneshot( 1224 Request::builder() 1225 .method("GET") 1226 .uri(uri) 1227 .header("accept", "text/html") 1228 .body(Body::empty())?, 1229 ) 1230 .await?; 1231 1232 assert_eq!(response.status(), StatusCode::OK); 1233 let content_type = response 1234 .headers() 1235 .get("content-type") 1236 .and_then(|v| v.to_str().ok()) 1237 .unwrap_or(""); 1238 assert!(content_type.contains("text/html")); 1239 let csp = response 1240 .headers() 1241 .get("content-security-policy") 1242 .and_then(|v| v.to_str().ok()) 1243 .unwrap_or(""); 1244 assert!(csp.contains("default-src 'self'")); 1245 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 1246 let body = String::from_utf8(bytes.to_vec())?; 1247 assert!(body.contains("Identity Verification")); 1248 assert!(body.contains("https://verifier.example/verify/1")); 1249 1250 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1251 Ok(()) 1252 } 1253 1254 #[tokio::test] 1255 async fn test_authorize_invalid_redirect_uri() -> Result<()> { 1256 let Some(pool) = get_pool().await else { return Ok(()); }; 1257 1258 let verifier_url = "https://verifier.example".to_string(); 1259 let config = test_config(&std::env::var("DATABASE_URL")?); 1260 let app = build_app(AppState::new(config, pool.clone())); 1261 1262 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1263 1264 let nonce = format!("nonce-{}", Uuid::new_v4()); 1265 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1266 .await? 1267 .expect("client should exist"); 1268 1269 let uri = format!( 1270 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1271 nonce, test_client.client.client_id, "https://example.com/wrong", "state-123" 1272 ); 1273 1274 let response = app 1275 .oneshot( 1276 Request::builder() 1277 .method("GET") 1278 .uri(uri) 1279 .body(Body::empty())?, 1280 ) 1281 .await?; 1282 1283 assert_error_response(response, StatusCode::BAD_REQUEST, "invalid_redirect_uri").await?; 1284 1285 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1286 Ok(()) 1287 } 1288 1289 #[tokio::test] 1290 async fn test_authorize_session_expired() -> Result<()> { 1291 let Some(pool) = get_pool().await else { return Ok(()); }; 1292 1293 let verifier_url = "https://verifier.example".to_string(); 1294 let config = test_config(&std::env::var("DATABASE_URL")?); 1295 let app = build_app(AppState::new(config, pool.clone())); 1296 1297 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1298 1299 let nonce = format!("nonce-{}", Uuid::new_v4()); 1300 let session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1301 .await? 1302 .expect("client should exist"); 1303 1304 sqlx::query( 1305 r#" 1306 UPDATE oauth2gw.verification_sessions 1307 SET expires_at = NOW() - INTERVAL '1 minute' 1308 WHERE id = $1 1309 "#, 1310 ) 1311 .bind(session.id) 1312 .execute(&pool) 1313 .await?; 1314 1315 let uri = format!( 1316 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1317 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1318 ); 1319 1320 let response = app 1321 .oneshot( 1322 Request::builder() 1323 .method("GET") 1324 .uri(uri) 1325 .body(Body::empty())?, 1326 ) 1327 .await?; 1328 1329 assert_error_response(response, StatusCode::GONE, "session_expired").await?; 1330 1331 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1332 Ok(()) 1333 } 1334 1335 #[tokio::test] 1336 async fn test_authorize_invalid_response_type() -> Result<()> { 1337 let Some(pool) = get_pool().await else { return Ok(()); }; 1338 1339 let verifier_url = "https://verifier.example".to_string(); 1340 let config = test_config(&std::env::var("DATABASE_URL")?); 1341 let app = build_app(AppState::new(config, pool.clone())); 1342 1343 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1344 1345 let nonce = format!("nonce-{}", Uuid::new_v4()); 1346 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1347 .await? 1348 .expect("client should exist"); 1349 1350 let uri = format!( 1351 "/authorize/{}?response_type=token&client_id={}&redirect_uri={}&state={}&scope=first_name", 1352 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1353 ); 1354 1355 let response = app 1356 .oneshot( 1357 Request::builder() 1358 .method("GET") 1359 .uri(uri) 1360 .body(Body::empty())?, 1361 ) 1362 .await?; 1363 1364 assert_error_response(response, StatusCode::BAD_REQUEST, "invalid_request").await?; 1365 1366 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1367 Ok(()) 1368 } 1369 1370 #[tokio::test] 1371 async fn test_authorize_invalid_scope() -> Result<()> { 1372 let Some(pool) = get_pool().await else { return Ok(()); }; 1373 1374 let verifier_url = "https://verifier.example".to_string(); 1375 1376 let mut config = test_config(&std::env::var("DATABASE_URL")?); 1377 config.allowed_scopes = Some(vec!["first_name".to_string(), "last_name".to_string()]); 1378 1379 let app = build_app(AppState::new(config, pool.clone())); 1380 1381 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1382 1383 let nonce = format!("nonce-{}", Uuid::new_v4()); 1384 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1385 .await? 1386 .expect("client should exist"); 1387 1388 let uri = format!( 1389 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=invalid_scope", 1390 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1391 ); 1392 1393 let response = app 1394 .oneshot( 1395 Request::builder() 1396 .method("GET") 1397 .uri(uri) 1398 .body(Body::empty())?, 1399 ) 1400 .await?; 1401 1402 assert_error_response(response, StatusCode::BAD_REQUEST, "invalid_scope").await?; 1403 1404 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1405 Ok(()) 1406 } 1407 1408 #[tokio::test] 1409 async fn test_authorize_session_status_conflict() -> Result<()> { 1410 let Some(pool) = get_pool().await else { return Ok(()); }; 1411 1412 let verifier_url = "https://verifier.example".to_string(); 1413 let config = test_config(&std::env::var("DATABASE_URL")?); 1414 let app = build_app(AppState::new(config, pool.clone())); 1415 1416 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1417 1418 let nonce = format!("nonce-{}", Uuid::new_v4()); 1419 let session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1420 .await? 1421 .expect("client should exist"); 1422 1423 sqlx::query( 1424 r#" 1425 UPDATE oauth2gw.verification_sessions 1426 SET status = 'completed' 1427 WHERE id = $1 1428 "#, 1429 ) 1430 .bind(session.id) 1431 .execute(&pool) 1432 .await?; 1433 1434 let uri = format!( 1435 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1436 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1437 ); 1438 1439 let response = app 1440 .oneshot( 1441 Request::builder() 1442 .method("GET") 1443 .uri(uri) 1444 .body(Body::empty())?, 1445 ) 1446 .await?; 1447 1448 assert_error_response(response, StatusCode::CONFLICT, "invalid_session_status").await?; 1449 1450 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1451 Ok(()) 1452 } 1453 1454 #[tokio::test] 1455 async fn test_authorize_verifier_error() -> Result<()> { 1456 let Some(pool) = get_pool().await else { return Ok(()); }; 1457 1458 let mut server = Server::new_async().await; 1459 let verifier_url = server.url(); 1460 let config = test_config(&std::env::var("DATABASE_URL")?); 1461 let app = build_app(AppState::new(config, pool.clone())); 1462 1463 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1464 1465 let nonce = format!("nonce-{}", Uuid::new_v4()); 1466 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1467 .await? 1468 .expect("client should exist"); 1469 1470 let _mock = server 1471 .mock("POST", "/management/api/verifications") 1472 .with_status(500) 1473 .with_header("content-type", "application/json") 1474 .with_body("{\"error\":\"boom\"}") 1475 .create_async() 1476 .await; 1477 1478 let uri = format!( 1479 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1480 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1481 ); 1482 1483 let response = app 1484 .oneshot( 1485 Request::builder() 1486 .method("GET") 1487 .uri(uri) 1488 .body(Body::empty())?, 1489 ) 1490 .await?; 1491 1492 assert_error_response(response, StatusCode::BAD_GATEWAY, "verifier_error").await?; 1493 1494 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1495 Ok(()) 1496 } 1497 1498 #[tokio::test] 1499 async fn test_authorize_verifier_invalid_json() -> Result<()> { 1500 let Some(pool) = get_pool().await else { return Ok(()); }; 1501 1502 let mut server = Server::new_async().await; 1503 let verifier_url = server.url(); 1504 let config = test_config(&std::env::var("DATABASE_URL")?); 1505 let app = build_app(AppState::new(config, pool.clone())); 1506 1507 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1508 1509 let nonce = format!("nonce-{}", Uuid::new_v4()); 1510 let _session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1511 .await? 1512 .expect("client should exist"); 1513 1514 let _mock = server 1515 .mock("POST", "/management/api/verifications") 1516 .with_status(200) 1517 .with_header("content-type", "application/json") 1518 .with_body("not-json") 1519 .create_async() 1520 .await; 1521 1522 let uri = format!( 1523 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1524 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1525 ); 1526 1527 let response = app 1528 .oneshot( 1529 Request::builder() 1530 .method("GET") 1531 .uri(uri) 1532 .body(Body::empty())?, 1533 ) 1534 .await?; 1535 1536 assert_error_response(response, StatusCode::BAD_GATEWAY, "verifier_invalid_response").await?; 1537 1538 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1539 Ok(()) 1540 } 1541 1542 #[tokio::test] 1543 async fn test_authorize_idempotent_cached_response() -> Result<()> { 1544 let Some(pool) = get_pool().await else { return Ok(()); }; 1545 1546 let verifier_url = "https://verifier.example".to_string(); 1547 let config = test_config(&std::env::var("DATABASE_URL")?); 1548 let app = build_app(AppState::new(config, pool.clone())); 1549 1550 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1551 1552 let nonce = format!("nonce-{}", Uuid::new_v4()); 1553 let session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1554 .await? 1555 .expect("client should exist"); 1556 1557 let verification_id = Uuid::new_v4().to_string(); 1558 let _ = sessions::update_session_authorized( 1559 &pool, 1560 session.id, 1561 "https://verifier.example/verify/1", 1562 Some("swiyu-verify://verify/1"), 1563 &verification_id, 1564 None, 1565 ) 1566 .await?; 1567 1568 let uri = format!( 1569 "/authorize/{}?response_type=code&client_id={}&redirect_uri={}&state={}&scope=first_name", 1570 nonce, test_client.client.client_id, test_client.client.redirect_uri, "state-123" 1571 ); 1572 1573 let response = app 1574 .oneshot( 1575 Request::builder() 1576 .method("GET") 1577 .uri(uri) 1578 .body(Body::empty())?, 1579 ) 1580 .await?; 1581 1582 assert_eq!(response.status(), StatusCode::OK); 1583 let bytes = to_bytes(response.into_body(), usize::MAX).await?; 1584 let json: Value = serde_json::from_slice(&bytes)?; 1585 let verification_id_str = json 1586 .get("verificationId") 1587 .and_then(|v| v.as_str()) 1588 .unwrap_or(""); 1589 assert_eq!(verification_id_str, verification_id); 1590 let verification_url = json 1591 .get("verification_url") 1592 .and_then(|v| v.as_str()) 1593 .unwrap_or(""); 1594 assert_eq!(verification_url, "https://verifier.example/verify/1"); 1595 1596 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1597 Ok(()) 1598 } 1599 1600 #[tokio::test] 1601 async fn test_notification_success() -> Result<()> { 1602 let Some(pool) = get_pool().await else { return Ok(()); }; 1603 1604 let mut server = Server::new_async().await; 1605 let verifier_url = server.url(); 1606 let config = test_config(&std::env::var("DATABASE_URL")?); 1607 let app = build_app(AppState::new(config, pool.clone())); 1608 1609 let test_client = create_test_client_with_verifier(&pool, &verifier_url).await?; 1610 1611 let nonce = format!("nonce-{}", Uuid::new_v4()); 1612 let session = sessions::create_session(&pool, &test_client.client.client_id, &nonce, 5) 1613 .await? 1614 .expect("client should exist"); 1615 1616 let verification_id = Uuid::new_v4(); 1617 let _ = sessions::update_session_authorized( 1618 &pool, 1619 session.id, 1620 "https://verifier.example/verify/1", 1621 None, 1622 &verification_id.to_string(), 1623 None, 1624 ) 1625 .await?; 1626 1627 let response_body = SwiyuManagementResponse { 1628 id: verification_id, 1629 request_nonce: Some("req-nonce".to_string()), 1630 state: SwiyuVerificationStatus::Success, 1631 verification_url: "https://verifier.example/verify/1".to_string(), 1632 verification_deeplink: Some("swiyu-verify://verify/1".to_string()), 1633 presentation_definition: sample_presentation_definition(), 1634 dcql_query: None, 1635 wallet_response: Some(serde_json::json!({"vc": "data"})), 1636 }; 1637 let response_json = serde_json::to_string(&response_body)?; 1638 1639 let path = format!("/management/api/verifications/{}", verification_id); 1640 let _mock = server 1641 .mock("GET", path.as_str()) 1642 .with_status(200) 1643 .with_header("content-type", "application/json") 1644 .with_body(response_json) 1645 .create_async() 1646 .await; 1647 1648 let webhook = serde_json::json!({ 1649 "verification_id": verification_id, 1650 "timestamp": "2025-01-01T00:00:00Z", 1651 }); 1652 1653 let response = app 1654 .oneshot( 1655 Request::builder() 1656 .method("POST") 1657 .uri("/notification") 1658 .header("content-type", "application/json") 1659 .body(Body::from(webhook.to_string()))?, 1660 ) 1661 .await?; 1662 1663 assert_eq!(response.status(), StatusCode::OK); 1664 1665 let status = get_session_status(&pool, session.id).await?; 1666 assert_eq!(status, sessions::SessionStatus::Verified); 1667 1668 let code = authorization_codes::get_code_by_session(&pool, session.id) 1669 .await? 1670 .expect("authorization code should exist"); 1671 assert!(!code.code.is_empty()); 1672 1673 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1674 Ok(()) 1675 } 1676 1677 #[tokio::test] 1678 async fn test_notification_pending_ignored() -> Result<()> { 1679 let Some(pool) = get_pool().await else { return Ok(()); }; 1680 1681 let mut server = Server::new_async().await; 1682 let verifier_url = server.url(); 1683 let config = test_config(&std::env::var("DATABASE_URL")?); 1684 let app = build_app(AppState::new(config, pool.clone())); 1685 1686 let (test_client, session_id, verification_id) = 1687 setup_authorized_session(&pool, &verifier_url).await?; 1688 1689 let response_body = SwiyuManagementResponse { 1690 id: verification_id, 1691 request_nonce: Some("req-nonce".to_string()), 1692 state: SwiyuVerificationStatus::Pending, 1693 verification_url: "https://verifier.example/verify/1".to_string(), 1694 verification_deeplink: Some("swiyu-verify://verify/1".to_string()), 1695 presentation_definition: sample_presentation_definition(), 1696 dcql_query: None, 1697 wallet_response: None, 1698 }; 1699 let response_json = serde_json::to_string(&response_body)?; 1700 1701 let path = format!("/management/api/verifications/{}", verification_id); 1702 let _mock = server 1703 .mock("GET", path.as_str()) 1704 .with_status(200) 1705 .with_header("content-type", "application/json") 1706 .with_body(response_json) 1707 .create_async() 1708 .await; 1709 1710 let webhook = serde_json::json!({ 1711 "verification_id": verification_id, 1712 "timestamp": "2025-01-01T00:00:00Z", 1713 }); 1714 1715 let response = app 1716 .oneshot( 1717 Request::builder() 1718 .method("POST") 1719 .uri("/notification") 1720 .header("content-type", "application/json") 1721 .body(Body::from(webhook.to_string()))?, 1722 ) 1723 .await?; 1724 1725 assert_eq!(response.status(), StatusCode::OK); 1726 1727 let status = get_session_status(&pool, session_id).await?; 1728 assert_eq!(status, sessions::SessionStatus::Authorized); 1729 let code = authorization_codes::get_code_by_session(&pool, session_id).await?; 1730 assert!(code.is_none()); 1731 1732 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1733 Ok(()) 1734 } 1735 1736 #[tokio::test] 1737 async fn test_notification_verifier_error() -> Result<()> { 1738 let Some(pool) = get_pool().await else { return Ok(()); }; 1739 1740 let mut server = Server::new_async().await; 1741 let verifier_url = server.url(); 1742 let config = test_config(&std::env::var("DATABASE_URL")?); 1743 let app = build_app(AppState::new(config, pool.clone())); 1744 1745 let (test_client, session_id, verification_id) = 1746 setup_authorized_session(&pool, &verifier_url).await?; 1747 1748 let path = format!("/management/api/verifications/{}", verification_id); 1749 let _mock = server 1750 .mock("GET", path.as_str()) 1751 .with_status(500) 1752 .with_header("content-type", "application/json") 1753 .with_body("{\"error\":\"boom\"}") 1754 .create_async() 1755 .await; 1756 1757 let webhook = serde_json::json!({ 1758 "verification_id": verification_id, 1759 "timestamp": "2025-01-01T00:00:00Z", 1760 }); 1761 1762 let response = app 1763 .oneshot( 1764 Request::builder() 1765 .method("POST") 1766 .uri("/notification") 1767 .header("content-type", "application/json") 1768 .body(Body::from(webhook.to_string()))?, 1769 ) 1770 .await?; 1771 1772 assert_eq!(response.status(), StatusCode::OK); 1773 1774 let status = get_session_status(&pool, session_id).await?; 1775 assert_eq!(status, sessions::SessionStatus::Authorized); 1776 let code = authorization_codes::get_code_by_session(&pool, session_id).await?; 1777 assert!(code.is_none()); 1778 1779 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1780 Ok(()) 1781 } 1782 1783 #[tokio::test] 1784 async fn test_notification_verifier_invalid_json() -> Result<()> { 1785 let Some(pool) = get_pool().await else { return Ok(()); }; 1786 1787 let mut server = Server::new_async().await; 1788 let verifier_url = server.url(); 1789 let config = test_config(&std::env::var("DATABASE_URL")?); 1790 let app = build_app(AppState::new(config, pool.clone())); 1791 1792 let (test_client, session_id, verification_id) = 1793 setup_authorized_session(&pool, &verifier_url).await?; 1794 1795 let path = format!("/management/api/verifications/{}", verification_id); 1796 let _mock = server 1797 .mock("GET", path.as_str()) 1798 .with_status(200) 1799 .with_header("content-type", "application/json") 1800 .with_body("not-json") 1801 .create_async() 1802 .await; 1803 1804 let webhook = serde_json::json!({ 1805 "verification_id": verification_id, 1806 "timestamp": "2025-01-01T00:00:00Z", 1807 }); 1808 1809 let response = app 1810 .oneshot( 1811 Request::builder() 1812 .method("POST") 1813 .uri("/notification") 1814 .header("content-type", "application/json") 1815 .body(Body::from(webhook.to_string()))?, 1816 ) 1817 .await?; 1818 1819 assert_eq!(response.status(), StatusCode::OK); 1820 1821 let status = get_session_status(&pool, session_id).await?; 1822 assert_eq!(status, sessions::SessionStatus::Authorized); 1823 let code = authorization_codes::get_code_by_session(&pool, session_id).await?; 1824 assert!(code.is_none()); 1825 1826 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1827 Ok(()) 1828 } 1829 1830 #[tokio::test] 1831 async fn test_notification_failed_verification() -> Result<()> { 1832 let Some(pool) = get_pool().await else { return Ok(()); }; 1833 1834 let mut server = Server::new_async().await; 1835 let verifier_url = server.url(); 1836 let config = test_config(&std::env::var("DATABASE_URL")?); 1837 let app = build_app(AppState::new(config, pool.clone())); 1838 1839 let (test_client, session_id, verification_id) = 1840 setup_authorized_session(&pool, &verifier_url).await?; 1841 1842 let response_body = SwiyuManagementResponse { 1843 id: verification_id, 1844 request_nonce: Some("req-nonce".to_string()), 1845 state: SwiyuVerificationStatus::Failed, 1846 verification_url: "https://verifier.example/verify/1".to_string(), 1847 verification_deeplink: Some("swiyu-verify://verify/1".to_string()), 1848 presentation_definition: sample_presentation_definition(), 1849 dcql_query: None, 1850 wallet_response: None, 1851 }; 1852 let response_json = serde_json::to_string(&response_body)?; 1853 1854 let path = format!("/management/api/verifications/{}", verification_id); 1855 let _mock = server 1856 .mock("GET", path.as_str()) 1857 .with_status(200) 1858 .with_header("content-type", "application/json") 1859 .with_body(response_json) 1860 .create_async() 1861 .await; 1862 1863 let webhook = serde_json::json!({ 1864 "verification_id": verification_id, 1865 "timestamp": "2025-01-01T00:00:00Z", 1866 }); 1867 1868 let response = app 1869 .oneshot( 1870 Request::builder() 1871 .method("POST") 1872 .uri("/notification") 1873 .header("content-type", "application/json") 1874 .body(Body::from(webhook.to_string()))?, 1875 ) 1876 .await?; 1877 1878 assert_eq!(response.status(), StatusCode::OK); 1879 1880 let status = get_session_status(&pool, session_id).await?; 1881 assert_eq!(status, sessions::SessionStatus::Failed); 1882 1883 let _ = clients::delete_client(&pool, test_client.client.id).await?; 1884 Ok(()) 1885 }