feat: new matcher system instead of manually 'match'ing in servcie
This commit is contained in:
parent
f305cb5a85
commit
638b0376d8
15 changed files with 646 additions and 225 deletions
180
src/matchers/api.rs
Normal file
180
src/matchers/api.rs
Normal file
|
@ -0,0 +1,180 @@
|
|||
use std::{net::IpAddr, pin::Pin, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use base64::{Engine, prelude::BASE64_STANDARD};
|
||||
use http::request::Parts;
|
||||
use http_body_util::BodyExt;
|
||||
use hyper::{
|
||||
Method, Request, StatusCode,
|
||||
body::{self, Incoming},
|
||||
service::Service,
|
||||
};
|
||||
use json::JsonValue;
|
||||
use log::{debug, error, warn};
|
||||
use tokio::{net::TcpStream, sync::Mutex};
|
||||
|
||||
use crate::{
|
||||
config::{Client, Config},
|
||||
db::{BoxyDatabase, Endpoint},
|
||||
routes::api::{AddHost, RegisterEndpoint, RemoveHost},
|
||||
server::{GeneralResponse, TcpIntercept, custom_resp, default_response, json_to_vec},
|
||||
services::matcher::{Matcher, MatcherService},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApiMatcher {
|
||||
pub database: Arc<Mutex<&'static mut BoxyDatabase>>,
|
||||
pub config: Config,
|
||||
pub _address: Option<IpAddr>,
|
||||
pub body: Option<JsonValue>,
|
||||
}
|
||||
|
||||
impl ApiMatcher {
|
||||
pub async fn new(database: Arc<Mutex<&'static mut BoxyDatabase>>, config: Config) -> Self {
|
||||
Self {
|
||||
database,
|
||||
config,
|
||||
_address: None,
|
||||
body: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Matcher for ApiMatcher {
|
||||
async fn unimatch(
|
||||
&mut self,
|
||||
req: &Request<Incoming>,
|
||||
) -> (bool, Option<Result<GeneralResponse, hyper::Error>>) {
|
||||
let address = self._address.unwrap();
|
||||
|
||||
let encoded_header = match req.headers().get(hyper::header::AUTHORIZATION) {
|
||||
None => {
|
||||
error!("Authorization header not given for request from {address}",);
|
||||
|
||||
return (
|
||||
false,
|
||||
Some(Ok(custom_resp(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Invalid credentials.".to_string(),
|
||||
)
|
||||
.await)),
|
||||
);
|
||||
}
|
||||
Some(x) => x,
|
||||
}
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
||||
debug!("authorization header: {}", encoded_header);
|
||||
|
||||
let auth_bytes = match BASE64_STANDARD.decode(&encoded_header[6..]) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error while decoding authorization header from {address}: {e}",);
|
||||
|
||||
return (
|
||||
false,
|
||||
Some(Ok(custom_resp(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Invalid base64 string given.".to_string(),
|
||||
)
|
||||
.await)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let auth_string = match String::from_utf8(auth_bytes) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error while decoding authorization header from {address}: {e}",);
|
||||
|
||||
return (
|
||||
false,
|
||||
Some(Ok(custom_resp(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Invalid UTF-8 in body.".to_string(),
|
||||
)
|
||||
.await)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
debug!("decoded auth string: {}", auth_string);
|
||||
|
||||
if !Client::verify(auth_string.clone(), self.config.clone()).await {
|
||||
warn!(
|
||||
"Authentication for string {} from {} failed.",
|
||||
auth_string, address
|
||||
);
|
||||
|
||||
return (
|
||||
false,
|
||||
Some(Ok(custom_resp(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Invalid credentials.".to_string(),
|
||||
)
|
||||
.await)),
|
||||
);
|
||||
}
|
||||
|
||||
return (true, None);
|
||||
}
|
||||
|
||||
fn retrieve(&self) -> Vec<Arc<dyn crate::services::matcher::Route<Self> + Sync + Send>> {
|
||||
vec![
|
||||
Arc::new(RegisterEndpoint {}),
|
||||
Arc::new(AddHost {}),
|
||||
Arc::new(RemoveHost {}),
|
||||
]
|
||||
}
|
||||
|
||||
fn stream(&mut self, stream: &TcpStream) {
|
||||
self._address = Some(stream.peer_addr().unwrap().ip());
|
||||
}
|
||||
|
||||
async fn body(&mut self, body: Incoming) -> Option<Result<GeneralResponse, hyper::Error>> {
|
||||
let address = self._address.unwrap();
|
||||
|
||||
let body_string = match String::from_utf8(
|
||||
body.collect()
|
||||
.await
|
||||
.unwrap()
|
||||
.to_bytes()
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<u8>>()
|
||||
.clone(),
|
||||
) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error while inferring UTF-8 string from {address}'s request body: {e}",);
|
||||
|
||||
return Some(Ok(custom_resp(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Invalid UTF-8 in body.".to_string(),
|
||||
)
|
||||
.await));
|
||||
}
|
||||
};
|
||||
|
||||
debug!("body: {}", body_string);
|
||||
|
||||
let json = match json::parse(body_string.as_str()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error while parsing JSON body from {address}: {e}",);
|
||||
|
||||
return Some(Ok(custom_resp(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"Invalid JSON in body.".to_string(),
|
||||
)
|
||||
.await));
|
||||
}
|
||||
};
|
||||
|
||||
self.body = Some(json);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue