mirror of
https://github.com/NetworkCats/rustyip.git
synced 2026-11-13 16:17:46 +00:00
feat: add host-based routing and IPv4 domain handler with CORS
This commit is contained in:
@@ -5,6 +5,7 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.8.8"
|
||||
tower = { version = "0.5", features = ["util"] }
|
||||
tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "net", "signal", "time", "fs", "io-util"] }
|
||||
askama = "0.15.4"
|
||||
maxminddb = "0.27.3"
|
||||
|
||||
@@ -656,6 +656,45 @@ pub async fn health() -> StatusCode {
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
fn extract_ipv4_client_ip(headers: &HeaderMap, dev_mode: bool) -> Result<IpAddr, AppError> {
|
||||
if let Some(ip) = headers
|
||||
.get("X-IPv4-Client-IP")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|s| s.trim().parse().ok())
|
||||
{
|
||||
return Ok(ip);
|
||||
}
|
||||
|
||||
if dev_mode {
|
||||
return Ok(DEV_FALLBACK_IP);
|
||||
}
|
||||
|
||||
Err(AppError::MissingClientIp)
|
||||
}
|
||||
|
||||
pub async fn ipv4_ip_handler(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Response, AppError> {
|
||||
let ip = extract_ipv4_client_ip(&headers, state.dev_mode)?;
|
||||
let origin = format!("https://{}", state.site_domain);
|
||||
Ok((
|
||||
[
|
||||
(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin),
|
||||
(
|
||||
header::ACCESS_CONTROL_ALLOW_METHODS,
|
||||
"GET".to_owned(),
|
||||
),
|
||||
(
|
||||
header::HeaderName::from_static("access-control-max-age"),
|
||||
"86400".to_owned(),
|
||||
),
|
||||
],
|
||||
format!("{ip}\n"),
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
||||
pub async fn json_handler(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
@@ -915,3 +954,7 @@ pub async fn not_found(State(state): State<AppState>, headers: HeaderMap) -> Res
|
||||
"The page you are looking for does not exist.",
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn ipv4_not_found() -> StatusCode {
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
|
||||
+22
-11
@@ -5,8 +5,29 @@ use axum::response::Response;
|
||||
|
||||
pub async fn security_headers(request: Request, next: Next) -> Response {
|
||||
let mut response = next.run(request).await;
|
||||
let headers = response.headers_mut();
|
||||
apply_common_security_headers(response.headers_mut());
|
||||
response.headers_mut().insert(
|
||||
header::HeaderName::from_static("cross-origin-opener-policy"),
|
||||
HeaderValue::from_static("same-origin"),
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
header::HeaderName::from_static("cross-origin-resource-policy"),
|
||||
HeaderValue::from_static("same-origin"),
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
pub async fn ipv4_domain_security_headers(request: Request, next: Next) -> Response {
|
||||
let mut response = next.run(request).await;
|
||||
apply_common_security_headers(response.headers_mut());
|
||||
response.headers_mut().insert(
|
||||
header::HeaderName::from_static("cross-origin-resource-policy"),
|
||||
HeaderValue::from_static("cross-origin"),
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
fn apply_common_security_headers(headers: &mut axum::http::HeaderMap) {
|
||||
headers.insert(
|
||||
header::X_CONTENT_TYPE_OPTIONS,
|
||||
HeaderValue::from_static("nosniff"),
|
||||
@@ -28,14 +49,4 @@ pub async fn security_headers(request: Request, next: Next) -> Response {
|
||||
header::HeaderName::from_static("permissions-policy"),
|
||||
HeaderValue::from_static("camera=(), microphone=(), geolocation=(), interest-cohort=()"),
|
||||
);
|
||||
headers.insert(
|
||||
header::HeaderName::from_static("cross-origin-opener-policy"),
|
||||
HeaderValue::from_static("same-origin"),
|
||||
);
|
||||
headers.insert(
|
||||
header::HeaderName::from_static("cross-origin-resource-policy"),
|
||||
HeaderValue::from_static("same-origin"),
|
||||
);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
+48
-2
@@ -1,12 +1,16 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::Router;
|
||||
use axum::middleware;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::get;
|
||||
use tower::ServiceExt as _;
|
||||
|
||||
use crate::handlers::{self, AppState};
|
||||
use crate::middleware::security_headers;
|
||||
use crate::middleware::{ipv4_domain_security_headers, security_headers};
|
||||
use crate::static_assets;
|
||||
|
||||
pub fn build_router(state: AppState) -> Router {
|
||||
fn build_main_router(state: AppState) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(handlers::root_redirect))
|
||||
.route("/favicon.ico", get(handlers::favicon))
|
||||
@@ -32,3 +36,45 @@ pub fn build_router(state: AppState) -> Router {
|
||||
.layer(middleware::from_fn(security_headers))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
fn build_ipv4_router(state: AppState) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(handlers::ipv4_ip_handler))
|
||||
.route("/health", get(handlers::health))
|
||||
.fallback(handlers::ipv4_not_found)
|
||||
.layer(middleware::from_fn(ipv4_domain_security_headers))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
pub fn build_router(state: AppState) -> Router {
|
||||
let ipv4_domain: Arc<str> = state.ipv4_domain.clone();
|
||||
let main_router = build_main_router(state.clone());
|
||||
let ipv4_router = build_ipv4_router(state);
|
||||
|
||||
let svc = tower::service_fn(move |req: axum::http::Request<axum::body::Body>| {
|
||||
let ipv4_domain = ipv4_domain.clone();
|
||||
let main_router = main_router.clone();
|
||||
let ipv4_router = ipv4_router.clone();
|
||||
async move {
|
||||
let is_ipv4_domain = !ipv4_domain.is_empty()
|
||||
&& req
|
||||
.headers()
|
||||
.get("host")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.is_some_and(|host| {
|
||||
let host = host.split(':').next().unwrap_or(host);
|
||||
host.eq_ignore_ascii_case(&ipv4_domain)
|
||||
});
|
||||
|
||||
// Router<()> is an infallible service; unwrap is safe.
|
||||
let response = if is_ipv4_domain {
|
||||
ipv4_router.oneshot(req).await.into_response()
|
||||
} else {
|
||||
main_router.oneshot(req).await.into_response()
|
||||
};
|
||||
Ok::<_, std::convert::Infallible>(response)
|
||||
}
|
||||
});
|
||||
|
||||
Router::new().fallback_service(svc)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user