diff --git a/src/idnot.rs b/src/idnot.rs new file mode 100644 index 0000000..c70607a --- /dev/null +++ b/src/idnot.rs @@ -0,0 +1,79 @@ +use reqwest::{Client, Url}; +use jwt_simple::prelude::*; +use serde::Deserialize; +use std::error::Error; + +use crate::Config; + +#[derive(Debug, Clone)] +pub struct IdNotVariables { + pub idnot_client_id: String, + pub idnot_client_secret: String, + pub idnot_redirect_uri: String, + pub idnot_base_url: String, + pub idnot_connexion_url: String, +} + +#[derive(Debug, Deserialize)] +pub struct IdNotToken { + pub access_token: String, + pub expires_in: String, + pub id_token: String, + pub token_type: String, +} + +#[derive(Debug, Deserialize)] +pub struct IdNotJwtPayload { + pub sub: String, + pub profile_idn: String, + pub entity_idn: String, +} + +pub async fn get_id_not_token( + code: &str, + vars: &IdNotVariables, +// ) -> Result> { +) -> Result<(), anyhow::Error> { + // Build the full URL + let url = Url::parse(&format!("{}{}", vars.idnot_base_url, vars.idnot_connexion_url))?; + + let params = [ + ("client_id", vars.idnot_client_id.as_str()), + ("client_secret", vars.idnot_client_secret.as_str()), + ("redirect_uri", vars.idnot_redirect_uri.as_str()), + ("code", &code), + ("grant_type", "authorization_code"), + ]; + + let client = Client::new(); + let req = client + .post(url.as_str()) + .query(¶ms) + .build()?; + + log::debug!("{:#?}", req); + + // let resp = client.execute(req).await?; + + // log::debug!("{:#?}", resp); + + // If status != 200, print the body as error and bail + // if !resp.status().is_success() { + // let err_text = resp.text().await.unwrap_or_else(|_| "".into()); + // log::error!("Error response from {}: {}", url, err_text); + // return Err(anyhow::Error::msg("Non-200 response")); + // } + + // Parse JSON into our token struct + // let decoded_token: IdNotToken = resp.json().await?; + + // log::debug!("{:?}", decoded_token); + + // let token_data: IdNotJwtPayload = // &decoded_token.id_token) + // .map_err(|e| { + // eprintln!("JWT decode error: {}", e); + // "Failed to decode id_token" + // })?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index f46a425..dadc55e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ use bitcoincore_rpc::{ }; use commit::{lock_members, MEMBERLIST}; use futures_util::{future, pin_mut, stream::TryStreamExt, FutureExt, StreamExt}; +use idnot::{get_id_not_token, IdNotVariables}; use log::{debug, error, warn}; use message::{broadcast_message, process_message, BroadcastType, MessageCache, MESSAGECACHE}; use scan::{check_transaction_alone, compute_partial_tweak_to_transaction}; @@ -46,8 +47,8 @@ use sdk_common::{ MutexExt, }; -use serde_json::Value; -use tokio::net::{TcpListener, TcpStream}; +use serde_json::{json, Value}; +use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::{TcpListener, TcpStream}}; use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_tungstenite::tungstenite::Message; @@ -62,6 +63,8 @@ mod electrumclient; mod faucet; mod message; mod scan; +mod commit; +mod idnot; use crate::config::Config; use crate::{ @@ -165,11 +168,79 @@ fn handle_new_tx_request(new_tx_msg: &NewTxMessage) -> Result<()> { Ok(()) } -async fn handle_connection( - raw_stream: TcpStream, - addr: SocketAddr, - our_sp_address: SilentPaymentAddress, -) { +async fn handle_http_connection(mut raw_stream: TcpStream, addr: SocketAddr, vars: IdNotVariables) { + debug!("Incoming HTTP connection from: {}", addr); + + // 1) Read up to 4KiB for the request headers + let mut buf = vec![0u8; 4096]; + let n = match raw_stream.read(&mut buf).await { + Ok(0) => return, // client closed connection + Ok(n) => n, + Err(err) => { + debug!("Failed to read from socket: {}", err); + return; + } + }; + + // 2) Convert to &str + let req = match std::str::from_utf8(&buf[..n]) { + Ok(s) => s, + Err(err) => { + debug!("Invalid UTF-8 request: {}", err); + return; + } + }; + + // 3) Parse request-line: "POST /api/v1/idnot/user?code=XYZ HTTP/1.1" + let mut lines = req.split("\r\n"); + let request_line = match lines.next() { + Some(l) => l, + None => return, + }; + let mut parts = request_line.split_whitespace(); + let method = parts.next().unwrap_or(""); + let uri = parts.next().unwrap_or(""); + + // 4) Only handle POST /api/v1/idnot/user + if method == "POST" && uri.starts_with("/api/v1/idnot/user/") { + let code_opt = uri + .split('/') + .last() + .filter(|&code| !code.is_empty()); + + if let Some(code) = code_opt { + if let Ok(()) = get_id_not_token(code, &vars).await { + let response = + "HTTP/1.1 200 OK\r\n\ + Content-Type: application/json\r\n\ + Content-Length: 0\r\n\r\n"; + let _ = raw_stream.write_all(response.as_bytes()).await; + } else { + log::error!("Failed to get token"); + } + return; + } else { + // Missing code → 400 Bad Request + let body = json!({ "error": "code is required" }).to_string(); + let response = format!( + "HTTP/1.1 400 Bad Request\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {}\r\n\r\n\ + {}", + body.len(), + body + ); + let _ = raw_stream.write_all(response.as_bytes()).await; + return; + } + } + + // 5) Fallback: 404 Not Found + let response = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; + let _ = raw_stream.write_all(response.as_bytes()).await; +} + +async fn handle_ws_connection(raw_stream: TcpStream, addr: SocketAddr, our_sp_address: SilentPaymentAddress) { debug!("Incoming TCP connection from: {}", addr); let peers = PEERMAP.get().expect("Peer Map not initialized"); @@ -474,15 +545,39 @@ async fn main() -> Result<()> { // Subscribe to Bitcoin Core tokio::spawn(handle_zmq(config.zmq_url, config.electrum_url)); + // Create the TcpListener for http request + let try_socket = TcpListener::bind(config.http_url).await; + let http_listener = try_socket.expect("Failed to bind"); + + if config.idnot_client_id.is_some() && config.idnot_client_secret.is_some() && config.idnot_redirect_uri.is_some() { + let id_not_vars = IdNotVariables { + idnot_base_url: "https://qual-connexion.idnot.fr".to_owned(), + idnot_client_id: config.idnot_client_id.unwrap(), + idnot_client_secret: config.idnot_client_secret.unwrap(), + idnot_connexion_url: "/IdPOAuth2/token/idnot_idp_v1".to_owned(), + idnot_redirect_uri: config.idnot_redirect_uri.unwrap(), + }; + + + log::debug!("{:?}", id_not_vars); + + // Spawn a task to handle HTTP connections + tokio::spawn(async move { + while let Ok((stream, addr)) = http_listener.accept().await { + tokio::spawn(handle_http_connection(stream, addr, id_not_vars.clone())); + } + }); + } + // Create the event loop and TCP listener we'll accept connections on. let try_socket = TcpListener::bind(config.ws_url).await; - let listener = try_socket.expect("Failed to bind"); + let ws_listener = try_socket.expect("Failed to bind"); tokio::spawn(MessageCache::clean_up()); // Let's spawn the handling of each connection in a separate task. - while let Ok((stream, addr)) = listener.accept().await { - tokio::spawn(handle_connection(stream, addr, our_sp_address)); + while let Ok((stream, addr)) = ws_listener.accept().await { + tokio::spawn(handle_ws_connection(stream, addr, our_sp_address)); } Ok(())