feat: boxygit add .

This commit is contained in:
hexlocation 2025-07-29 11:47:02 +02:00
parent ba8d1b9453
commit a8bc61f40c
15 changed files with 4700 additions and 0 deletions

92
src/services/api.rs Normal file
View file

@ -0,0 +1,92 @@
use std::{hash, net::{IpAddr, SocketAddr}, pin::Pin, sync::Arc};
use base64::{Engine, prelude::BASE64_STANDARD};
use bcrypt::DEFAULT_COST;
use http_body_util::{BodyExt, Full};
use hyper::{
body::{Bytes, Incoming}, service::Service, Method, Request, Response, StatusCode, Uri
};
use log::{info, trace};
use tokio::{net::TcpStream, sync::Mutex};
use crate::{
config::{Client, Config}, db::{BoxyDatabase, Endpoint}, types::{GeneralBody, GeneralResponse, TcpIntercept}
};
#[derive(Debug, Clone)]
pub struct ApiService {
pub database: Arc<Mutex<&'static mut BoxyDatabase>>,
pub config: Config,
pub _address: Option<IpAddr>,
}
async fn default_response() -> Response<http_body_util::Either<Incoming, Full<Bytes>>> {
Response::builder()
.status(404)
.body(GeneralBody::Right(Full::from(Bytes::from(
"That route doesn't exist.",
))))
.unwrap()
}
async fn custom_resp(e: StatusCode, m: String) -> Response<http_body_util::Either<Incoming, Full<Bytes>>> {
Response::builder()
.status(e)
.body(GeneralBody::Right(Full::from(Bytes::from(m))))
.unwrap()
}
impl TcpIntercept for ApiService {
fn handle(&mut self, stream: &TcpStream) {
self._address = Some(stream.peer_addr().unwrap().ip());
}
}
impl Service<Request<Incoming>> for ApiService {
type Response = GeneralResponse;
type Error = hyper::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn call(&self, req: Request<Incoming>) -> Self::Future {
let database = self.database.clone();
let config = self.config.clone();
let address = self._address.clone().unwrap();
Box::pin(async move {
match *req.method() {
Method::POST => match req.uri().path() {
"/register" => {
let encoded_header = req.headers().get(hyper::header::AUTHORIZATION).unwrap().to_str().unwrap();
let auth_string = String::from_utf8(BASE64_STANDARD.decode(&encoded_header[6..]).unwrap()).unwrap();
let auth_string_split: Vec<&str> = auth_string.split(':').collect();
let name = auth_string_split.first().unwrap();
let secret = auth_string_split.get(1).unwrap();
let matched_clients: Vec<&Client> = config.clients.iter().filter(|x| x.name.eq(name)).collect();
let client = matched_clients.first().unwrap();
if !bcrypt::verify(secret, client.secret.as_str()).unwrap() {
return Ok(custom_resp(StatusCode::UNAUTHORIZED, "Invalid credentials.".to_string()).await);
}
let body = String::from_utf8(req.collect().await.unwrap().to_bytes().iter().cloned().collect::<Vec<u8>>()).unwrap();
let json = json::parse(body.as_str()).unwrap();
info!("body: {}", body);
let mut endpoint = Endpoint::new(None, address, json["port"].as_u16().unwrap(), json["callback"].as_str().unwrap_or("/").to_string()).await;
endpoint.register(*database.lock().await, json["hostname"].as_str().unwrap().to_string()).await.unwrap();
Ok(custom_resp(StatusCode::OK, "yay".to_string()).await)
}
_ => Ok(default_response().await),
},
_ => Ok(default_response().await),
}
})
}
}

View file

@ -0,0 +1,60 @@
use std::{pin::Pin, sync::Arc};
use http_body_util::Full;
use hyper::{
Request, Response,
body::{Bytes, Incoming},
service::Service,
};
use log::error;
use tokio::sync::Mutex;
use crate::{
config::{self, Client, Config, Host},
db::{BoxyDatabase, Endpoint},
types::{GeneralBody, GeneralResponse, TcpIntercept},
};
use super::proxy::ProxyService;
#[derive(Debug, Clone)]
pub struct ControllerService {
pub database: Arc<Mutex<&'static mut BoxyDatabase>>,
}
impl TcpIntercept for ControllerService {
fn handle(&mut self, stream: &tokio::net::TcpStream) {
}
}
impl Service<Request<Incoming>> for ControllerService {
type Response = GeneralResponse;
type Error = hyper::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn call(&self, req: Request<Incoming>) -> Self::Future {
let database = self.database.clone();
Box::pin(async move {
let hostname = req
.headers()
.get(hyper::header::HOST)
.unwrap()
.to_str()
.unwrap()
.to_string();
let endpoints = Endpoint::get_by_hostname(*database.lock().await, hostname.clone())
.await
.unwrap();
let endpoint = endpoints.first().unwrap();
let proxy = ProxyService {
address: format!("{}:{}", endpoint.address.clone(), endpoint.port),
hostname,
};
proxy.call(req).await
})
}
}

50
src/services/proxy.rs Normal file
View file

@ -0,0 +1,50 @@
use std::pin::Pin;
use hyper::{Request, body::Incoming, service::Service};
use hyper_util::rt::TokioIo;
use log::error;
use tokio::net::TcpStream;
use crate::types::{GeneralResponse, to_general_response};
#[derive(Debug, Clone)]
pub struct ProxyService {
pub address: String,
pub hostname: String,
}
impl Service<Request<Incoming>> for ProxyService {
type Response = GeneralResponse;
type Error = hyper::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn call(&self, incoming: Request<Incoming>) -> Self::Future {
let address = self.address.clone();
let hostname = self.hostname.clone();
Box::pin(async move {
let stream = TcpStream::connect(address).await.unwrap();
let io = TokioIo::new(stream);
let (mut sender, conn) = hyper::client::conn::http1::Builder::new()
.handshake(io)
.await
.unwrap();
tokio::spawn(async move {
if let Err(err) = conn.await {
error!("Could not open connection to backend: {err}");
}
});
let (mut parts, body_stream) = incoming.into_parts();
parts
.headers
.insert(hyper::header::HOST, hostname.parse().unwrap());
let req = Request::from_parts(parts, body_stream);
Ok(to_general_response(sender.send_request(req).await.unwrap()))
})
}
}