fedimint_server/net/api/
http_auth.rs1use std::error::Error as StdError;
2use std::pin::Pin;
3use std::sync::Arc;
4use std::task::{Context, Poll};
5
6use anyhow::bail;
7use base64::Engine;
8use base64::engine::general_purpose::STANDARD;
9use fedimint_logging::LOG_NET_AUTH;
10use futures::{Future, FutureExt as _, TryFutureExt as _};
11use http::HeaderValue;
12use hyper::body::Body;
13use hyper::{Request, Response, http};
14use subtle::ConstantTimeEq as _;
15use tower::Service;
16use tracing::{debug, info};
17
18#[derive(Clone, Debug)]
19pub struct HttpAuthLayer {
20 auth_base64: Arc<Vec<String>>,
23}
24
25impl HttpAuthLayer {
26 pub fn new(secrets: &[String]) -> Self {
27 if secrets.is_empty() {
28 info!(target: LOG_NET_AUTH, "Api available for public access");
29 } else {
30 info!(target: LOG_NET_AUTH, num_secrets = secrets.len(), "Api available for private access");
31 }
32 Self {
33 auth_base64: secrets
34 .iter()
35 .map(|p| STANDARD.encode(format!("fedimint:{p}")))
36 .collect::<Vec<_>>()
37 .into(),
38 }
39 }
40}
41
42impl<S> tower::Layer<S> for HttpAuthLayer {
43 type Service = HttpAuthService<S>;
44
45 fn layer(&self, service: S) -> Self::Service {
46 HttpAuthService {
47 inner: service,
48 auth_base64: self.auth_base64.clone(),
49 }
50 }
51}
52
53#[derive(Clone)]
54pub struct HttpAuthService<S> {
55 inner: S,
56 auth_base64: Arc<Vec<String>>,
57}
58
59impl<S> HttpAuthService<S> {
60 fn needs_auth(&self) -> bool {
61 !self.auth_base64.is_empty()
62 }
63
64 fn check_auth(&self, base64_auth: &str) -> bool {
65 self.auth_base64
66 .iter()
67 .any(|p| p.as_bytes().ct_eq(base64_auth.as_bytes()).into())
68 }
69
70 fn check_auth_header_value(&self, auth_header: &HeaderValue) -> anyhow::Result<bool> {
71 let mut split = auth_header.to_str()?.split_ascii_whitespace();
72
73 let Some(auth_method) = split.next() else {
74 bail!("Invalid Request: empty value");
75 };
76
77 if auth_method != "Basic" {
78 bail!("Invalid Request: Wrong auth method");
79 }
80 let Some(auth) = split.next() else {
81 bail!("Invalid Request: no auth string");
82 };
83
84 if split.next().is_some() {
85 bail!("Invalid Request: too many things");
86 }
87
88 Ok(self.check_auth(auth))
89 }
90}
91
92impl<S, B: Body + 'static> Service<Request<B>> for HttpAuthService<S>
93where
94 S: Service<Request<B>, Response = jsonrpsee::core::http_helpers::Response>,
95 S::Response: 'static,
96 S::Error: Into<Box<dyn StdError + Send + Sync>> + 'static,
97 S::Future: Send + 'static,
98{
99 type Response = S::Response;
100 type Error = Box<dyn StdError + Send + Sync + 'static>;
101 type Future =
102 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
103
104 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
105 self.inner.poll_ready(cx).map_err(Into::into)
106 }
107
108 fn call(&mut self, req: Request<B>) -> Self::Future {
109 let needs_auth = self.needs_auth();
110
111 if !needs_auth {
112 return Box::pin(self.inner.call(req).map_err(Into::into));
113 }
114
115 if let Some(auth_header) = req.headers().get(hyper::http::header::AUTHORIZATION) {
116 let auth_ok = self.check_auth_header_value(auth_header).unwrap_or(false);
117
118 if auth_ok {
119 return Box::pin(self.inner.call(req).map_err(Into::into));
120 }
121 }
122
123 debug!(target: LOG_NET_AUTH, "Access denied to incoming api connection");
124 let mut response = Response::new(jsonrpsee::core::http_helpers::Body::new(
125 "Unauthorized".to_string(),
126 ));
127 *response.status_mut() = http::StatusCode::UNAUTHORIZED;
128 response.headers_mut().insert(
129 http::header::WWW_AUTHENTICATE,
130 HeaderValue::from_static("Basic realm=\"Authentication needed\""),
131 );
132 async { Ok(response) }.boxed()
133 }
134}