Add idnot logic

This commit is contained in:
NicolasCantu 2025-06-03 18:38:27 +02:00
parent e7a685056c
commit febd8c963d
2 changed files with 184 additions and 10 deletions

79
src/idnot.rs Normal file
View File

@ -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<IdNotJwtPayload, Box<dyn Error>> {
) -> 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(&params)
.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(|_| "<no body>".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(())
}

View File

@ -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(())