From 1aeda956a8eb2c23c4dfc81072cd814c3fecae55 Mon Sep 17 00:00:00 2001 From: DanGould Date: Sun, 30 Apr 2023 08:58:19 -0400 Subject: [PATCH 1/4] Match req.method() --- payjoin-client/src/app.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/payjoin-client/src/app.rs b/payjoin-client/src/app.rs index 45e3eec9..eb51e0d8 100644 --- a/payjoin-client/src/app.rs +++ b/payjoin-client/src/app.rs @@ -146,21 +146,25 @@ impl App { } fn handle_web_request(&self, req: &Request) -> Response { - self.handle_payjoin_request(req) - .map_err(|e| match e { - Error::BadRequest(e) => { - log::error!("Error handling request: {}", e); - Response::text(e.to_string()).with_status_code(400) - } - e => { - log::error!("Error handling request: {}", e); - Response::text(e.to_string()).with_status_code(500) - } - }) - .unwrap_or_else(|err_resp| err_resp) + match req.method() { + "POST" => self + .handle_payjoin_post(req) + .map_err(|e| match e { + Error::BadRequest(e) => { + log::error!("Error handling request: {}", e); + Response::text(e.to_string()).with_status_code(400) + } + e => { + log::error!("Error handling request: {}", e); + Response::text(e.to_string()).with_status_code(500) + } + }) + .unwrap_or_else(|err_resp| err_resp), + _ => Response::empty_404(), + } } - fn handle_payjoin_request(&self, req: &Request) -> Result { + fn handle_payjoin_post(&self, req: &Request) -> Result { use bitcoin::hashes::hex::ToHex; let headers = Headers(req.headers()); From 3ea3668dfaf21c7246af50c8509b6ac80bb31d47 Mon Sep 17 00:00:00 2001 From: DanGould Date: Sun, 30 Apr 2023 09:34:53 -0400 Subject: [PATCH 2/4] Respond to GET bip21 with PjUri --- Cargo.lock | 1 + payjoin-client/Cargo.toml | 1 + payjoin-client/src/app.rs | 30 ++++++++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ef88392..a908eab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1251,6 +1251,7 @@ version = "0.1.15" dependencies = [ "anyhow", "base64", + "bip21", "bitcoin", "bitcoincore-rpc", "clap", diff --git a/payjoin-client/Cargo.toml b/payjoin-client/Cargo.toml index 2d8af2e3..3b66a3cc 100644 --- a/payjoin-client/Cargo.toml +++ b/payjoin-client/Cargo.toml @@ -12,6 +12,7 @@ native-tls-vendored = ["reqwest/native-tls-vendored"] anyhow = "1.0.70" base64 = "0.13.0" bitcoin = "0.29.2" +bip21 = "0.2.0" bitcoincore-rpc = "0.16.0" clap = "4.1.4" config = "0.13.3" diff --git a/payjoin-client/src/app.rs b/payjoin-client/src/app.rs index eb51e0d8..0ebd6102 100644 --- a/payjoin-client/src/app.rs +++ b/payjoin-client/src/app.rs @@ -146,8 +146,16 @@ impl App { } fn handle_web_request(&self, req: &Request) -> Response { - match req.method() { - "POST" => self + log::debug!("Received request: {:?}", req); + match (req.method(), req.url().as_ref()) { + ("GET", "/bip21") => { + log::debug!("{:?}, {:?}", req.method(), req.raw_query_string()); + let amount = req.get_param("amount").map(|amt| { + Amount::from_btc(amt.parse().expect("Failed to parse amount")).unwrap() + }); + self.handle_get_bip21(amount) + } + ("POST", _) => self .handle_payjoin_post(req) .map_err(|e| match e { Error::BadRequest(e) => { @@ -164,6 +172,24 @@ impl App { } } + fn handle_get_bip21(&self, amount: Option) -> Response { + let address = self.bitcoind.get_new_address(None, None).unwrap(); + let uri_string = if let Some(amount) = amount { + format!( + "{}?amount={}&pj={}", + address.to_qr_uri(), + amount.to_btc(), + self.config.pj_endpoint + ) + } else { + format!("{}?pj={}", address.to_qr_uri(), self.config.pj_endpoint) + }; + // TODO handle errors + let uri = payjoin::Uri::try_from(uri_string.clone()).unwrap(); + let _ = uri.check_pj_supported().unwrap(); + Response::text(uri_string) + } + fn handle_payjoin_post(&self, req: &Request) -> Result { use bitcoin::hashes::hex::ToHex; From 2eee9355e59ff0fe94a7ae8a7881e7b58d8a0cca Mon Sep 17 00:00:00 2001 From: DanGould Date: Wed, 3 May 2023 12:27:49 -0400 Subject: [PATCH 3/4] Handle get_bip21 errors --- payjoin-client/src/app.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/payjoin-client/src/app.rs b/payjoin-client/src/app.rs index 0ebd6102..91be6824 100644 --- a/payjoin-client/src/app.rs +++ b/payjoin-client/src/app.rs @@ -154,6 +154,11 @@ impl App { Amount::from_btc(amt.parse().expect("Failed to parse amount")).unwrap() }); self.handle_get_bip21(amount) + .map_err(|e| { + log::error!("Error handling request: {}", e); + Response::text(e.to_string()).with_status_code(500) + }) + .unwrap_or_else(|err_resp| err_resp) } ("POST", _) => self .handle_payjoin_post(req) @@ -172,8 +177,9 @@ impl App { } } - fn handle_get_bip21(&self, amount: Option) -> Response { - let address = self.bitcoind.get_new_address(None, None).unwrap(); + fn handle_get_bip21(&self, amount: Option) -> Result { + let address = + self.bitcoind.get_new_address(None, None).map_err(|e| Error::Server(e.into()))?; let uri_string = if let Some(amount) = amount { format!( "{}?amount={}&pj={}", @@ -184,10 +190,12 @@ impl App { } else { format!("{}?pj={}", address.to_qr_uri(), self.config.pj_endpoint) }; - // TODO handle errors - let uri = payjoin::Uri::try_from(uri_string.clone()).unwrap(); - let _ = uri.check_pj_supported().unwrap(); - Response::text(uri_string) + let uri = payjoin::Uri::try_from(uri_string.clone()) + .map_err(|_| Error::Server(anyhow!("Could not parse payjoin URI string.").into()))?; + let _ = uri + .check_pj_supported() + .map_err(|_| Error::Server(anyhow!("Created bip21 with invalid &pj=.").into()))?; + Ok(Response::text(uri_string)) } fn handle_payjoin_post(&self, req: &Request) -> Result { From 5657522a7b90fe08741cb309407cfab23103dfd3 Mon Sep 17 00:00:00 2001 From: DanGould Date: Mon, 8 May 2023 11:53:19 -0400 Subject: [PATCH 4/4] Set all defaults pre-config serialization --- payjoin-client/src/app.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/payjoin-client/src/app.rs b/payjoin-client/src/app.rs index 91be6824..73fd2dcd 100644 --- a/payjoin-client/src/app.rs +++ b/payjoin-client/src/app.rs @@ -387,26 +387,27 @@ impl AppConfig { "bitcoind_rpcpass", matches.get_one::("rpcpass").map(|s| s.as_str()), )? + // Subcommand defaults without which file serialization fails. + .set_default("danger_accept_invalid_certs", false)? + .set_default("pj_host", "0.0.0.0:3000")? + .set_default("pj_endpoint", "https://localhost:3010")? + .set_default("sub_only", false)? .add_source(File::new("config.toml", FileFormat::Toml)); let builder = match matches.subcommand() { - Some(("send", matches)) => - builder.set_default("danger_accept_invalid_certs", false)?.set_override_option( - "danger_accept_invalid_certs", - matches.get_one::("DANGER_ACCEPT_INVALID_CERTS").copied(), - )?, + Some(("send", matches)) => builder.set_override_option( + "danger_accept_invalid_certs", + matches.get_one::("DANGER_ACCEPT_INVALID_CERTS").copied(), + )?, Some(("receive", matches)) => builder - .set_default("pj_host", "0.0.0.0:3000")? .set_override_option( "pj_host", matches.get_one::("port").map(|port| format!("0.0.0.0:{}", port)), )? - .set_default("pj_endpoint", "https://localhost:3010")? .set_override_option( "pj_endpoint", matches.get_one::("endpoint").map(|s| s.as_str()), )? - .set_default("sub_only", false)? .set_override_option("sub_only", matches.get_one::("sub_only").copied())?, _ => unreachable!(), // If all subcommands are defined above, anything else is unreachabe!() };