|
3 | 3 | /// signature/audience/issuer verification rather than a placeholder. |
4 | 4 | use axum::{ |
5 | 5 | body::Body, |
| 6 | + extract::Extension, |
6 | 7 | http::{Request, StatusCode}, |
| 8 | + routing::get, |
7 | 9 | Router, |
8 | 10 | }; |
9 | 11 | use chrono::Utc; |
@@ -201,7 +203,24 @@ fn create_test_app(pool: PgPool) -> Router { |
201 | 203 | privy: Arc::new(PrivyJwksClient::new(mock_jwks_url().to_string())), |
202 | 204 | privy_app_id: TEST_APP_ID.to_string(), |
203 | 205 | }; |
204 | | - Router::new().nest("/api/auth", zaps_backend::api::auth_routes_with_state(state)) |
| 206 | + Router::new().nest( |
| 207 | + "/api/auth", |
| 208 | + zaps_backend::api::auth_routes_with_state(state), |
| 209 | + ) |
| 210 | +} |
| 211 | + |
| 212 | +async fn protected_identity( |
| 213 | + Extension(user): Extension<zaps_backend::api::AuthenticatedUser>, |
| 214 | +) -> String { |
| 215 | + user.address |
| 216 | +} |
| 217 | + |
| 218 | +fn create_protected_test_app(pool: PgPool) -> Router { |
| 219 | + zaps_backend::api::protected_routes( |
| 220 | + Router::new().route("/protected", get(protected_identity)), |
| 221 | + pool, |
| 222 | + zaps_backend::api::AuthTokenCache::new(), |
| 223 | + ) |
205 | 224 | } |
206 | 225 |
|
207 | 226 | #[cfg(test)] |
@@ -246,7 +265,10 @@ mod privy_auth_integration_tests { |
246 | 265 | let body = response.into_body().collect().await.unwrap().to_bytes(); |
247 | 266 | let json: Value = serde_json::from_slice(&body).unwrap(); |
248 | 267 |
|
249 | | - assert!(json["token"].is_string(), "Response should contain JWT token"); |
| 268 | + assert!( |
| 269 | + json["token"].is_string(), |
| 270 | + "Response should contain JWT token" |
| 271 | + ); |
250 | 272 | assert_eq!(json["username"].as_str().unwrap(), "u_GBPK7THXDEPNBQB5K"); |
251 | 273 | assert_eq!(json["privy_did"].as_str().unwrap(), privy_did); |
252 | 274 |
|
@@ -739,4 +761,85 @@ mod privy_auth_integration_tests { |
739 | 761 | "Token issued for a different Privy app must be rejected" |
740 | 762 | ); |
741 | 763 | } |
| 764 | + |
| 765 | + /// Test 12 - A valid Privy token produces a session token that passes the |
| 766 | + /// protected-route authentication middleware. |
| 767 | + #[tokio::test] |
| 768 | + async fn test_valid_privy_token_passes_auth_middleware() { |
| 769 | + let pool = setup_test_pool().await; |
| 770 | + let stellar_addr = "GBPK7THXDEPNBQB5K3EMQL5FZAQLHJ4XPBWJFNV3EPJN7CVPQGJZ6PBN"; |
| 771 | + let privy_did = format!("did:privy:test_{}", Uuid::new_v4()); |
| 772 | + cleanup_test_user(&pool, stellar_addr).await; |
| 773 | + |
| 774 | + let request = Request::builder() |
| 775 | + .method("POST") |
| 776 | + .uri("/api/auth/privy") |
| 777 | + .header("content-type", "application/json") |
| 778 | + .body(Body::from( |
| 779 | + json!({ |
| 780 | + "privy_token": create_mock_privy_token(&privy_did, Some(stellar_addr), false), |
| 781 | + "privy_did": privy_did, |
| 782 | + "stellar_address": stellar_addr |
| 783 | + }) |
| 784 | + .to_string(), |
| 785 | + )) |
| 786 | + .unwrap(); |
| 787 | + |
| 788 | + let response = create_test_app(pool.clone()) |
| 789 | + .oneshot(request) |
| 790 | + .await |
| 791 | + .unwrap(); |
| 792 | + assert_eq!(response.status(), StatusCode::CREATED); |
| 793 | + let body = response.into_body().collect().await.unwrap().to_bytes(); |
| 794 | + let auth_response: Value = serde_json::from_slice(&body).unwrap(); |
| 795 | + let session_token = auth_response["token"].as_str().unwrap(); |
| 796 | + |
| 797 | + let protected_request = Request::builder() |
| 798 | + .uri("/protected") |
| 799 | + .header("authorization", format!("Bearer {session_token}")) |
| 800 | + .body(Body::empty()) |
| 801 | + .unwrap(); |
| 802 | + let protected_response = create_protected_test_app(pool.clone()) |
| 803 | + .oneshot(protected_request) |
| 804 | + .await |
| 805 | + .unwrap(); |
| 806 | + |
| 807 | + assert_eq!(protected_response.status(), StatusCode::OK); |
| 808 | + let body = protected_response |
| 809 | + .into_body() |
| 810 | + .collect() |
| 811 | + .await |
| 812 | + .unwrap() |
| 813 | + .to_bytes(); |
| 814 | + assert_eq!(body, stellar_addr); |
| 815 | + |
| 816 | + cleanup_test_user(&pool, stellar_addr).await; |
| 817 | + } |
| 818 | + |
| 819 | + /// Test 13 - An invalid Privy verification response must not produce a |
| 820 | + /// session token and is returned as 401 Unauthorized. |
| 821 | + #[tokio::test] |
| 822 | + async fn test_invalid_privy_token_returns_unauthorized() { |
| 823 | + let pool = setup_test_pool().await; |
| 824 | + let stellar_addr = "GBPK7THXDEPNBQB5K3EMQL5FZAQLHJ4XPBWJFNV3EPJN7CVPQGJZ6PBN"; |
| 825 | + let privy_did = format!("did:privy:test_{}", Uuid::new_v4()); |
| 826 | + |
| 827 | + let request = Request::builder() |
| 828 | + .method("POST") |
| 829 | + .uri("/api/auth/privy") |
| 830 | + .header("content-type", "application/json") |
| 831 | + .body(Body::from( |
| 832 | + json!({ |
| 833 | + "privy_token": "not-a-valid-privy-token", |
| 834 | + "privy_did": privy_did, |
| 835 | + "stellar_address": stellar_addr |
| 836 | + }) |
| 837 | + .to_string(), |
| 838 | + )) |
| 839 | + .unwrap(); |
| 840 | + |
| 841 | + let response = create_test_app(pool).oneshot(request).await.unwrap(); |
| 842 | + |
| 843 | + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); |
| 844 | + } |
742 | 845 | } |
0 commit comments