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