use std::{ io::Read, net::{IpAddr, SocketAddr}, }; use axum::{ async_trait, body::HttpBody, extract::{ConnectInfo, FromRequest, FromRequestParts}, http::{request::Parts, Request}, response::Response, BoxError, }; use bytes::Bytes; use moka::future::Cache; use tower_cookies::Cookies; use super::http::text; pub struct Authorized; #[async_trait] impl FromRequestParts for Authorized where S: Send + Sync, { type Rejection = Response; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let Ok(Some(cookies)) = Option::::from_request_parts(parts, state).await else { return Err(text(403, "No cookies provided")) }; let Some(token) = cookies.get("auth") else { return Err(text(403, "No auth token provided")) }; let auth_ip: IpAddr; { let Some(cache) = parts.extensions.get::>() else { return Err(text(500, "Failed to load auth store")) }; let Some(ip) = cache.get(token.value()) else { return Err(text(401, "Unauthorized")) }; auth_ip = ip } let Ok(Some(RequestIp(ip))) = Option::::from_request_parts(parts, state).await else { return Err(text(403, "You have no ip")) }; if auth_ip != ip { return Err(text(403, "Auth token does not match current ip")); } Ok(Self) } } pub struct RequestIp(pub IpAddr); #[async_trait] impl FromRequestParts for RequestIp where S: Send + Sync, { type Rejection = Response; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { let headers = &parts.headers; let forwardedfor = headers .get("x-forwarded-for") .and_then(|h| h.to_str().ok()) .and_then(|h| { h.split(',') .rev() .find_map(|s| s.trim().parse::().ok()) }); if let Some(forwardedfor) = forwardedfor { return Ok(Self(forwardedfor)); } let realip = headers .get("x-real-ip") .and_then(|hv| hv.to_str().ok()) .and_then(|s| s.parse::().ok()); if let Some(realip) = realip { return Ok(Self(realip)); } let realip = headers .get("x-real-ip") .and_then(|hv| hv.to_str().ok()) .and_then(|s| s.parse::().ok()); if let Some(realip) = realip { return Ok(Self(realip)); } let info = parts.extensions.get::>(); if let Some(info) = info { return Ok(Self(info.0.ip())); } Err(text(403, "You have no ip")) } } pub struct Body(pub String); #[async_trait] impl FromRequest for Body where B: HttpBody + Sync + Send + 'static, B::Data: Send, B::Error: Into, S: Send + Sync, { type Rejection = Response; async fn from_request(req: Request, state: &S) -> Result { let Ok(bytes) = Bytes::from_request(req, state).await else { return Err(text(413, "Payload too large")); }; let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else { return Err(text(400, "Invalid utf8 body")) }; Ok(Self(body)) } }