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