Files
nym/common/http-api-common/src/middleware/bearer_auth.rs
T
Jędrzej Stuczyński feefde9022 Bugfix/credential proxy sequencing (#5187)
* using common middleware for all http servers

* improved span handling in credential-proxy

* ensure increase in sequence number upon making deposit

* added explicit connect options for the db

* fixed further instances of incorrect span instrumentation

* batch deposit requests together to improve concurrency

* ignore cancelled requests

* updated credential proxy version to 0.1.4

* adjusted Dockerfile with new binary location

* log binary version on startup

* reduce default log level

* guard against unavaiable commit sha

* apply review comments: dont exit(0), instead just shutdown normally

* add skip_webhook parameter to obtain-async

* removing dead code
2024-12-02 14:52:35 +00:00

116 lines
3.3 KiB
Rust

// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use axum::http::{header, HeaderValue, StatusCode};
use axum::response::IntoResponse;
use axum::{extract::Request, response::Response};
use futures::future::BoxFuture;
use std::sync::Arc;
use std::task::{Context, Poll};
use tower::{Layer, Service};
use tracing::{debug, instrument, trace};
use zeroize::Zeroizing;
#[derive(Debug, Clone)]
pub struct AuthLayer {
bearer_token: Arc<Zeroizing<String>>,
}
impl AuthLayer {
pub fn new(bearer_token: Arc<Zeroizing<String>>) -> Self {
AuthLayer { bearer_token }
}
}
impl<S> Layer<S> for AuthLayer {
type Service = RequireAuth<S>;
fn layer(&self, inner: S) -> Self::Service {
RequireAuth::new(inner, self.bearer_token.clone())
}
}
#[derive(Debug, Clone)]
pub struct RequireAuth<S> {
inner: S,
bearer_token: Arc<Zeroizing<String>>,
}
impl<S> RequireAuth<S> {
pub fn new(inner: S, bearer_token: Arc<Zeroizing<String>>) -> Self {
RequireAuth {
inner,
bearer_token,
}
}
fn check_auth_header(&self, header: Option<&HeaderValue>) -> Result<(), &'static str> {
let Some(token) = header else {
trace!("missing header");
return Err("`Authorization` header is missing");
};
let Ok(authorization) = token.to_str() else {
trace!("invalid header");
return Err("`Authorization` header contains invalid characters");
};
debug!("header value: '{authorization}'");
let split = authorization.split_once(' ');
let bearer_token = match split {
// Found proper bearer
Some(("Bearer", contents)) => contents,
// Found empty bearer;
_ if authorization == "Bearer" => "",
// Found nothing
_ => return Err("`Authorization` header must be a bearer token"),
};
debug!("parsed token: '{bearer_token}'");
if self.bearer_token.is_empty() && bearer_token.is_empty() {
return Ok(());
}
if bearer_token.is_empty() {
return Err("`Authorization` header must contain non-empty `Bearer` token");
}
if self.bearer_token.as_str() != bearer_token {
return Err("`Authorization` header does not contain the correct `Bearer` token");
}
Ok(())
}
}
impl<S> Service<Request> for RequireAuth<S>
where
S: Service<Request, Response = Response> + Send + 'static,
S: Send + Sync + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
#[instrument(skip_all, fields(uri = %req.uri()))]
fn call(&mut self, req: Request) -> Self::Future {
debug!("checking the auth");
let auth_header = req.headers().get(header::AUTHORIZATION);
match self.check_auth_header(auth_header) {
Ok(_authorised) => Box::pin(self.inner.call(req)),
Err(err) => {
Box::pin(async move { Ok((StatusCode::UNAUTHORIZED, err).into_response()) })
}
}
}
}