1use std::collections::{BTreeMap, BTreeSet, HashMap};
2use std::panic::AssertUnwindSafe;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use anyhow::anyhow;
7use axum::extract::{Path, Query};
8use axum::{Extension, Json};
9use bitcoin::hashes::sha256;
10use fedimint_core::module::{FEDIMINT_GATEWAY_ALPN, IrohGatewayRequest, IrohGatewayResponse};
11use fedimint_core::net::iroh::build_iroh_endpoint;
12use fedimint_core::task::TaskGroup;
13use fedimint_core::util::FmtCompactAnyhow as _;
14use fedimint_gateway_common::STOP_ENDPOINT;
15use fedimint_logging::LOG_GATEWAY;
16use futures::FutureExt as _;
17use iroh::endpoint::Incoming;
18use reqwest::StatusCode;
19use serde::de::DeserializeOwned;
20use serde_json::json;
21use tracing::{error, info, warn};
22use url::Url;
23
24use crate::Gateway;
25use crate::error::{GatewayError, PublicGatewayError};
26use crate::rpc_server::verify_bolt11_preimage_v2_get;
27
28type GetHandler = Box<
31 dyn Fn(
32 Extension<Arc<Gateway>>,
33 )
34 -> Pin<Box<dyn Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send>>
35 + Send
36 + Sync,
37>;
38
39type PostHandler = Box<
42 dyn Fn(
43 Extension<Arc<Gateway>>,
44 serde_json::Value,
45 )
46 -> Pin<Box<dyn Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send>>
47 + Send
48 + Sync,
49>;
50
51fn make_get_handler<F, Fut>(f: F) -> GetHandler
53where
54 F: Fn(Extension<Arc<Gateway>>) -> Fut + Clone + Send + Sync + 'static,
55 Fut: Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send + 'static,
56{
57 Box::new(move |gateway: Extension<Arc<Gateway>>| {
58 let f = f.clone();
59 Box::pin(async move {
60 let res = f(gateway).await?;
61 Ok(res)
62 })
63 })
64}
65
66fn make_post_handler<P, F, Fut>(f: F) -> PostHandler
68where
69 P: DeserializeOwned + Send + 'static,
70 F: Fn(Extension<Arc<Gateway>>, Json<P>) -> Fut + Clone + Send + Sync + 'static,
71 Fut: Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send + 'static,
72{
73 Box::new(
74 move |gateway: Extension<Arc<Gateway>>, value: serde_json::Value| {
75 let f = f.clone();
76 Box::pin(async move {
77 let payload: P = serde_json::from_value(value)
78 .map_err(|e| PublicGatewayError::Unexpected(anyhow!(e.to_string())))?;
79 let res = f(gateway, Json(payload)).await?;
80 Ok(res)
81 })
82 },
83 )
84}
85
86pub struct Handlers {
92 get_handlers: BTreeMap<String, GetHandler>,
93 post_handlers: BTreeMap<String, PostHandler>,
94 authenticated_routes: BTreeSet<String>,
95}
96
97impl Handlers {
98 pub fn new() -> Self {
99 let mut authenticated_routes = BTreeSet::new();
100 authenticated_routes.insert(STOP_ENDPOINT.to_string());
101 Handlers {
102 get_handlers: BTreeMap::new(),
103 post_handlers: BTreeMap::new(),
104 authenticated_routes,
105 }
106 }
107
108 pub fn add_handler<F, Fut>(&mut self, route: &str, f: F, is_authenticated: bool)
109 where
110 F: Fn(Extension<Arc<Gateway>>) -> Fut + Clone + Send + Sync + 'static,
111 Fut: Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send + 'static,
112 {
113 if is_authenticated {
114 self.authenticated_routes.insert(route.to_string());
115 }
116 self.get_handlers
117 .insert(route.to_string(), make_get_handler(f));
118 }
119
120 pub fn get_handler(&self, route: &str) -> Option<&GetHandler> {
121 self.get_handlers.get(route)
122 }
123
124 pub fn add_handler_with_payload<P, F, Fut>(&mut self, route: &str, f: F, is_authenticated: bool)
125 where
126 P: DeserializeOwned + Send + 'static,
127 F: Fn(Extension<Arc<Gateway>>, Json<P>) -> Fut + Clone + Send + Sync + 'static,
128 Fut: Future<Output = Result<Json<serde_json::Value>, GatewayError>> + Send + 'static,
129 {
130 if is_authenticated {
131 self.authenticated_routes.insert(route.to_string());
132 }
133
134 self.post_handlers
135 .insert(route.to_string(), make_post_handler(f));
136 }
137
138 pub fn get_handler_with_payload(&self, route: &str) -> Option<&PostHandler> {
139 self.post_handlers.get(route)
140 }
141
142 pub fn is_authenticated(&self, route: &str) -> bool {
143 self.authenticated_routes.contains(route)
144 }
145}
146
147pub async fn start_iroh_endpoint(
150 gateway: &Arc<Gateway>,
151 task_group: TaskGroup,
152 handlers: Arc<Handlers>,
153) -> anyhow::Result<()> {
154 if let Some(iroh_listen) = gateway.iroh_listen {
155 info!("Building Iroh Endpoint...");
156 let iroh_endpoint = build_iroh_endpoint(
157 gateway.iroh_sk.clone(),
158 iroh_listen,
159 gateway.iroh_dns.clone(),
160 gateway.iroh_relays.clone(),
161 FEDIMINT_GATEWAY_ALPN,
162 )
163 .await?;
164 let gw_clone = gateway.clone();
165 let tg_clone = task_group.clone();
166 let handlers_clone = handlers.clone();
167 info!("Spawning accept loop...");
168 task_group.spawn("Gateway Iroh", |_| async move {
169 while let Some(incoming) = iroh_endpoint.accept().await {
170 info!("Accepted new connection. Spawning handler...");
171 tg_clone.spawn_cancellable_silent(
172 "handle endpoint accept",
173 handle_incoming_iroh_request(
174 incoming,
175 gw_clone.clone(),
176 handlers_clone.clone(),
177 tg_clone.clone(),
178 ),
179 );
180 }
181 });
182
183 info!(target: LOG_GATEWAY, "Successfully started iroh endpoint");
184 }
185
186 Ok(())
187}
188
189async fn handle_incoming_iroh_request(
192 incoming: Incoming,
193 gateway: Arc<Gateway>,
194 handlers: Arc<Handlers>,
195 task_group: TaskGroup,
196) -> anyhow::Result<()> {
197 let connection = incoming.accept()?.await?;
198 let remote_node_id = &connection.remote_node_id()?;
199 info!(%remote_node_id, "Handler received connection");
200 while let Ok((mut send, mut recv)) = connection.accept_bi().await {
201 let request = recv.read_to_end(100_000).await?;
202 let request = serde_json::from_slice::<IrohGatewayRequest>(&request)?;
203
204 let (status, body) = run_handler(
205 &request.route,
206 handle_request(
207 &request,
208 gateway.clone(),
209 handlers.clone(),
210 task_group.clone(),
211 ),
212 )
213 .await;
214
215 let response = IrohGatewayResponse {
216 status: status.as_u16(),
217 body: body.0,
218 };
219 let response = serde_json::to_vec(&response)?;
220
221 send.write_all(&response).await?;
222 send.finish()?;
223 }
224 Ok(())
225}
226
227async fn run_handler(
241 route: &str,
242 handler: impl Future<Output = anyhow::Result<(StatusCode, Json<serde_json::Value>)>>,
243) -> (StatusCode, Json<serde_json::Value>) {
244 let result = AssertUnwindSafe(handler)
248 .catch_unwind()
249 .await
250 .unwrap_or_else(|_| {
251 error!(
252 target: LOG_GATEWAY,
253 route,
254 "Gateway API handler panicked, DO NOT IGNORE, FIX IT!!!"
255 );
256
257 Ok((StatusCode::INTERNAL_SERVER_ERROR, Json(json!(()))))
258 });
259
260 result.unwrap_or_else(|err| {
261 warn!(
262 target: LOG_GATEWAY,
263 route,
264 err = %err.fmt_compact_anyhow(),
265 "Gateway API handler returned an error"
266 );
267
268 (StatusCode::INTERNAL_SERVER_ERROR, Json(json!(())))
269 })
270}
271
272async fn handle_request(
277 request: &IrohGatewayRequest,
278 gateway: Arc<Gateway>,
279 handlers: Arc<Handlers>,
280 task_group: TaskGroup,
281) -> anyhow::Result<(StatusCode, Json<serde_json::Value>)> {
282 if handlers.is_authenticated(&request.route) && iroh_verify_password(&gateway, request).is_err()
283 {
284 return Ok((StatusCode::UNAUTHORIZED, Json(json!(()))));
285 }
286
287 if request.route == STOP_ENDPOINT {
290 let body = crate::rpc_server::stop(Extension(task_group), Extension(gateway)).await?;
291 return Ok((StatusCode::OK, body));
292 }
293
294 if let Some(verify_route) = parse_verify_route(&request.route) {
298 let Ok((payment_hash, query_map)) = verify_route else {
299 return Ok((StatusCode::BAD_REQUEST, Json(json!(()))));
303 };
304
305 let body =
306 verify_bolt11_preimage_v2_get(Extension(gateway), Path(payment_hash), Query(query_map))
307 .await?;
308
309 return Ok((StatusCode::OK, body));
310 }
311
312 let (status, body) = if let Some(params) = &request.params {
313 let Some(handler) = handlers.get_handler_with_payload(&request.route) else {
314 return Ok(unknown_route(&request.route));
315 };
316
317 (
318 StatusCode::OK,
319 handler(Extension(gateway), params.clone()).await?,
320 )
321 } else {
322 let Some(handler) = handlers.get_handler(&request.route) else {
323 return Ok(unknown_route(&request.route));
324 };
325
326 (StatusCode::OK, handler(Extension(gateway)).await?)
327 };
328
329 Ok((status, body))
330}
331
332fn unknown_route(route: &str) -> (StatusCode, Json<serde_json::Value>) {
334 warn!(
335 target: LOG_GATEWAY,
336 route,
337 "Iroh handler received request with unknown route"
338 );
339
340 (StatusCode::NOT_FOUND, Json(json!(())))
341}
342
343fn parse_verify_route(
353 route: &str,
354) -> Option<anyhow::Result<(sha256::Hash, HashMap<String, String>)>> {
355 if !route.starts_with("/verify/") {
359 return None;
360 }
361
362 let url = Url::parse(&format!("http://localhost{route}")).ok()?;
364
365 let mut segments = url.path_segments()?;
370
371 if segments.next() != Some("verify") {
372 return None;
373 }
374
375 let hash_str = segments.next()?;
376
377 if segments.next().is_some() {
378 return None;
379 }
380
381 let payment_hash = match hash_str.parse::<sha256::Hash>() {
382 Ok(payment_hash) => payment_hash,
383 Err(err) => return Some(Err(anyhow!(err).context("Invalid payment hash"))),
384 };
385
386 let query_map: HashMap<String, String> = url.query_pairs().into_owned().collect();
388
389 Some(Ok((payment_hash, query_map)))
390}
391
392fn iroh_verify_password(
395 gateway: &Arc<Gateway>,
396 request: &IrohGatewayRequest,
397) -> anyhow::Result<()> {
398 if let Some(password) = request.password.as_ref()
399 && bcrypt::verify(password, &gateway.bcrypt_password_hash)?
400 {
401 return Ok(());
402 }
403
404 Err(anyhow!("Invalid password"))
405}
406
407#[cfg(test)]
408mod tests {
409 use bitcoin::hashes::Hash as _;
410
411 use super::*;
412
413 #[tokio::test]
414 async fn panicking_handler_returns_an_error_instead_of_unwinding() {
415 let (status, _body) = run_handler("/pay_invoice", async { panic!("handler panic") }).await;
416
417 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
418 }
419
420 #[tokio::test]
421 async fn failing_handler_is_answered_instead_of_ending_the_connection() {
422 let (status, _body) = run_handler("/verify/nonsense", async {
423 Err(anyhow!("Verify route does not contain a payment hash"))
424 })
425 .await;
426
427 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
428 }
429
430 #[test]
431 fn verify_route_parses_the_payment_hash_not_the_route_prefix() {
432 let payment_hash = sha256::Hash::hash(b"payment hash");
433
434 let (parsed_hash, _query) = parse_verify_route(&format!("/verify/{payment_hash}"))
435 .expect("the route is the verify route")
436 .expect("the payment hash is the second path segment, not the first");
437
438 assert_eq!(parsed_hash, payment_hash);
439 }
440
441 #[test]
442 fn verify_route_parses_the_wait_query_parameter() {
443 let payment_hash = sha256::Hash::hash(b"payment hash");
444
445 let (parsed_hash, query) = parse_verify_route(&format!("/verify/{payment_hash}?wait"))
446 .expect("the route is the verify route")
447 .expect("query parameters do not affect path parsing");
448
449 assert_eq!(parsed_hash, payment_hash);
450 assert!(query.contains_key("wait"));
451 }
452
453 #[test]
454 fn verify_route_with_a_malformed_payment_hash_is_rejected() {
455 parse_verify_route("/verify/")
456 .expect("the route is shaped like the verify route")
457 .expect_err("a route with an empty payment hash has nothing to verify");
458 parse_verify_route("/verify/not-a-payment-hash")
459 .expect("the route is shaped like the verify route")
460 .expect_err("a payment hash that is not a sha256 hash is rejected");
461 }
462
463 #[test]
464 fn only_the_exact_verify_route_reaches_the_verify_endpoint() {
465 let payment_hash = sha256::Hash::hash(b"payment hash");
466
467 for route in [
472 "/verify".to_string(),
473 format!("/verifyfoo/{payment_hash}"),
474 format!("/verify_something/{payment_hash}?wait"),
475 format!("/verify/{payment_hash}/extra"),
476 format!("/../verify/{payment_hash}"),
479 format!("/verify/{payment_hash}/../../stop"),
480 "/verify/../stop".to_string(),
481 ] {
482 assert!(
483 parse_verify_route(&route).is_none(),
484 "{route} must not reach the unauthenticated verify handler"
485 );
486 }
487 }
488}