Skip to content

Commit 3ebafa0

Browse files
committed
feat(iroh-relay): allow to authenticate nodes via a HTTP POST request
1 parent cf3e650 commit 3ebafa0

File tree

1 file changed

+58
-1
lines changed

1 file changed

+58
-1
lines changed

iroh-relay/src/main.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use iroh_relay::{
2222
use n0_future::FutureExt;
2323
use serde::{Deserialize, Serialize};
2424
use tokio_rustls_acme::{caches::DirCache, AcmeConfig};
25-
use tracing::debug;
25+
use tracing::{debug, warn};
2626
use tracing_subscriber::{prelude::*, EnvFilter};
2727

2828
/// The default `http_bind_port` when using `--dev`.
@@ -181,6 +181,14 @@ enum AccessConfig {
181181
Allowlist(Vec<NodeId>),
182182
/// Allows everyone, except these nodes.
183183
Denylist(Vec<NodeId>),
184+
/// Performs a HTTP POST request to determine access for each node that connects to the relay.
185+
///
186+
/// The string value is used as the URL template, where `{node_id}` will be replaced
187+
/// with the hex-encoded node id that is connecting.
188+
///
189+
/// To grant access, the HTTP endpoint must return a `200` response with `true` as the response text.
190+
/// In all other cases, the node will be denied accesss.
191+
Http(String),
184192
}
185193

186194
impl From<AccessConfig> for iroh_relay::server::AccessConfig {
@@ -215,10 +223,59 @@ impl From<AccessConfig> for iroh_relay::server::AccessConfig {
215223
.boxed()
216224
}))
217225
}
226+
AccessConfig::Http(url_template) => {
227+
let client = reqwest::Client::default();
228+
let url_template = Arc::new(url_template);
229+
iroh_relay::server::AccessConfig::Restricted(Box::new(move |node_id| {
230+
let client = client.clone();
231+
let url_template = url_template.clone();
232+
async move { http_access_check(&client, &url_template, node_id).await }.boxed()
233+
}))
234+
}
218235
}
219236
}
220237
}
221238

239+
#[tracing::instrument("http-access-check", skip_all, fields(node_id=%node_id.fmt_short()))]
240+
async fn http_access_check(
241+
client: &reqwest::Client,
242+
url_template: &str,
243+
node_id: NodeId,
244+
) -> iroh_relay::server::Access {
245+
use iroh_relay::server::Access;
246+
let url = url_template.replace("{node_id}", &node_id.to_string());
247+
debug!(%url, "check relay access via HTTP POST");
248+
let res = match client.post(&url).send().await {
249+
Ok(t) => t,
250+
Err(err) => {
251+
warn!("request failed: {err}");
252+
return Access::Deny;
253+
}
254+
};
255+
if res.status().is_success() {
256+
match res.text().await {
257+
Err(err) => {
258+
warn!("failed to read response: {err}");
259+
Access::Deny
260+
}
261+
Ok(text) if text == "true" => {
262+
debug!("request succesfull: grant access");
263+
Access::Allow
264+
}
265+
Ok(_) => {
266+
warn!("request succesfull but response text is not `true`: deny access");
267+
Access::Deny
268+
}
269+
}
270+
} else {
271+
debug!(
272+
"request returned non-success code {}: deny access",
273+
res.status()
274+
);
275+
Access::Deny
276+
}
277+
}
278+
222279
impl Config {
223280
fn http_bind_addr(&self) -> SocketAddr {
224281
self.http_bind_addr

0 commit comments

Comments
 (0)