this repo has no description
1mod common;
2use axum::{Router, extract::Request, http::StatusCode, routing::any};
3use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
4use reqwest::Client;
5use std::sync::Arc;
6use tokio::net::TcpListener;
7
8async fn spawn_mock_upstream() -> (
9 String,
10 tokio::sync::mpsc::Receiver<(String, String, Option<String>)>,
11) {
12 let (tx, rx) = tokio::sync::mpsc::channel(10);
13 let tx = Arc::new(tx);
14 let app = Router::new().fallback(any(move |req: Request| {
15 let tx = tx.clone();
16 async move {
17 let method = req.method().to_string();
18 let uri = req.uri().to_string();
19 let auth = req
20 .headers()
21 .get("Authorization")
22 .and_then(|h| h.to_str().ok())
23 .map(|s| s.to_string());
24 let _ = tx.send((method, uri, auth)).await;
25 (StatusCode::OK, "Mock Response")
26 }
27 }));
28 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
29 let addr = listener.local_addr().unwrap();
30 tokio::spawn(async move {
31 axum::serve(listener, app).await.unwrap();
32 });
33 (format!("http://{}", addr), rx)
34}
35
36#[tokio::test]
37async fn test_proxy_via_header() {
38 let app_url = common::base_url().await;
39 let (upstream_url, mut rx) = spawn_mock_upstream().await;
40 let client = Client::new();
41 let res = client
42 .get(format!("{}/xrpc/com.example.test", app_url))
43 .header("atproto-proxy", &upstream_url)
44 .header("Authorization", "Bearer test-token")
45 .send()
46 .await
47 .unwrap();
48 assert_eq!(res.status(), StatusCode::OK);
49 let (method, uri, auth) = rx.recv().await.expect("Upstream should receive request");
50 assert_eq!(method, "GET");
51 assert_eq!(uri, "/xrpc/com.example.test");
52 assert_eq!(auth, Some("Bearer test-token".to_string()));
53}
54
55#[tokio::test]
56async fn test_proxy_auth_signing() {
57 let app_url = common::base_url().await;
58 let (upstream_url, mut rx) = spawn_mock_upstream().await;
59 let client = Client::new();
60 let (access_jwt, did) = common::create_account_and_login(&client).await;
61 let res = client
62 .get(format!("{}/xrpc/com.example.signed", app_url))
63 .header("atproto-proxy", &upstream_url)
64 .header("Authorization", format!("Bearer {}", access_jwt))
65 .send()
66 .await
67 .unwrap();
68 assert_eq!(res.status(), StatusCode::OK);
69 let (method, uri, auth) = rx.recv().await.expect("Upstream receive");
70 assert_eq!(method, "GET");
71 assert_eq!(uri, "/xrpc/com.example.signed");
72 let received_token = auth.expect("No auth header").replace("Bearer ", "");
73 assert_ne!(received_token, access_jwt, "Token should be replaced");
74 let parts: Vec<&str> = received_token.split('.').collect();
75 assert_eq!(parts.len(), 3);
76 let payload_bytes = URL_SAFE_NO_PAD.decode(parts[1]).expect("payload b64");
77 let claims: serde_json::Value = serde_json::from_slice(&payload_bytes).expect("payload json");
78 assert_eq!(claims["iss"], did);
79 assert_eq!(claims["sub"], did);
80 assert_eq!(claims["aud"], upstream_url);
81 assert_eq!(claims["lxm"], "com.example.signed");
82}
83
84#[tokio::test]
85async fn test_proxy_post_with_body() {
86 let app_url = common::base_url().await;
87 let (upstream_url, mut rx) = spawn_mock_upstream().await;
88 let client = Client::new();
89 let payload = serde_json::json!({
90 "text": "Hello from proxy test",
91 "createdAt": "2024-01-01T00:00:00Z"
92 });
93 let res = client
94 .post(format!("{}/xrpc/com.example.postMethod", app_url))
95 .header("atproto-proxy", &upstream_url)
96 .header("Authorization", "Bearer test-token")
97 .json(&payload)
98 .send()
99 .await
100 .unwrap();
101 assert_eq!(res.status(), StatusCode::OK);
102 let (method, uri, auth) = rx.recv().await.expect("Upstream should receive request");
103 assert_eq!(method, "POST");
104 assert_eq!(uri, "/xrpc/com.example.postMethod");
105 assert_eq!(auth, Some("Bearer test-token".to_string()));
106}
107
108#[tokio::test]
109async fn test_proxy_with_query_params() {
110 let app_url = common::base_url().await;
111 let (upstream_url, mut rx) = spawn_mock_upstream().await;
112 let client = Client::new();
113 let res = client
114 .get(format!(
115 "{}/xrpc/com.example.query?repo=did:plc:test&collection=app.bsky.feed.post&limit=50",
116 app_url
117 ))
118 .header("atproto-proxy", &upstream_url)
119 .header("Authorization", "Bearer test-token")
120 .send()
121 .await
122 .unwrap();
123 assert_eq!(res.status(), StatusCode::OK);
124 let (method, uri, _auth) = rx.recv().await.expect("Upstream should receive request");
125 assert_eq!(method, "GET");
126 assert!(
127 uri.contains("repo=did") || uri.contains("repo=did%3Aplc%3Atest"),
128 "URI should contain repo param, got: {}",
129 uri
130 );
131 assert!(
132 uri.contains("collection=app.bsky.feed.post") || uri.contains("collection=app.bsky"),
133 "URI should contain collection param, got: {}",
134 uri
135 );
136 assert!(
137 uri.contains("limit=50"),
138 "URI should contain limit param, got: {}",
139 uri
140 );
141}