use std::{convert::Infallible, future::Ready, net::SocketAddr};
use ::warp::{
filters::path::FullPath,
http::{HeaderMap, StatusCode},
reject::Reject,
Filter, Rejection, Reply,
};
use crate::{
middleware::{
AuthorizationOutcome, AuthorizedRequest, DeniedResponse, Middleware, ReqKeyFailure,
RequestContext, ResponseContext,
},
VerificationResult,
};
#[derive(Clone)]
pub struct ReqKeyFilter {
middleware: Middleware,
}
impl ReqKeyFilter {
pub const fn new(middleware: Middleware) -> Self {
Self { middleware }
}
pub fn filter(
&self,
) -> impl Filter<Extract = (WarpRequest,), Error = Rejection> + Clone + Send + Sync + 'static
{
let middleware = self.middleware.clone();
let query = ::warp::query::raw()
.or(::warp::any().map(String::new))
.unify();
::warp::method()
.and(::warp::path::full())
.and(query)
.and(::warp::header::headers_cloned())
.and(::warp::addr::remote())
.and_then(
move |method: http::Method,
path: FullPath,
query: String,
headers: HeaderMap,
remote: Option<SocketAddr>| {
let middleware = middleware.clone();
async move {
let mut context =
RequestContext::new(method, path.as_str()).with_headers(headers);
if !query.is_empty() {
context = context.with_query(query);
}
if let Some(remote) = remote {
context = context.with_client_ip(remote.ip().to_string());
}
match middleware.authorize(context).await {
AuthorizationOutcome::Bypass => Ok(WarpRequest {
middleware,
authorized: None,
}),
AuthorizationOutcome::Authorized(authorized) => Ok(WarpRequest {
middleware,
authorized: Some(authorized),
}),
AuthorizationOutcome::Denied(denial) => {
Err(::warp::reject::custom(ReqKeyRejection(denial)))
}
}
}
},
)
}
}
pub struct WarpRequest {
middleware: Middleware,
authorized: Option<AuthorizedRequest>,
}
impl WarpRequest {
pub fn decision(&self) -> Option<&VerificationResult> {
self.authorized
.as_ref()
.and_then(|authorized| authorized.decision.as_ref())
}
pub fn failure(&self) -> Option<&ReqKeyFailure> {
self.authorized
.as_ref()
.and_then(|authorized| authorized.failure.as_ref())
}
pub async fn record(self, reply: impl Reply) -> ::warp::reply::Response {
let mut response = reply.into_response();
let Some(authorized) = self.authorized else {
return response;
};
let original_headers = response.headers().clone();
response.headers_mut().extend(authorized.response_headers());
self.middleware
.record(
authorized.clone(),
ResponseContext::new(response.status().as_u16())
.with_headers(original_headers)
.with_latency_ms(authorized.elapsed_ms()),
)
.await;
response
}
}
#[derive(Debug)]
pub struct ReqKeyRejection(pub DeniedResponse);
impl Reject for ReqKeyRejection {}
pub fn recover(rejection: Rejection) -> Ready<Result<::warp::reply::Response, Rejection>> {
std::future::ready(recover_inner(rejection))
}
fn recover_inner(rejection: Rejection) -> Result<::warp::reply::Response, Rejection> {
let Some(rejection) = rejection.find::<ReqKeyRejection>() else {
return Err(rejection);
};
let denial = &rejection.0;
let status =
StatusCode::from_u16(denial.status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let mut response = ::warp::reply::with_status(
::warp::reply::json(&serde_json::json!({
"error": denial.error.as_str(),
"message": denial.message,
})),
status,
)
.into_response();
response.headers_mut().extend(denial.headers());
Ok(response)
}
pub type InfallibleReply<T> = Result<T, Infallible>;