cruiser/src/guards.rs

55 lines
1.8 KiB
Rust

use std::net::IpAddr;
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome, Request};
#[derive(Debug)]
pub struct ClientIP(pub String);
#[derive(Debug)]
pub struct TrustedProxies(pub Vec<IpAddr>);
#[rocket::async_trait]
impl<'r> FromRequest<'r> for ClientIP {
type Error = ();
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
// fetch configured trusted proxies list (if any)
let trusted_proxies = &req.rocket().state::<TrustedProxies>().unwrap().0;
// fetch the actual remote IP address
let remote_ip = match req.remote() {
Some(socket_addr) => socket_addr.ip(),
None => {
return Outcome::Failure((Status::InternalServerError, ()));
}
};
// if the request does not come from one trusted proxy, don't trust any HTTP header
if !trusted_proxies.contains(&remote_ip) {
return Outcome::Success(ClientIP(remote_ip.to_string()));
}
// fetch valid IP addresses from X-FORWARDED-FOR header(s)
let xff_ip_addresses: Vec<IpAddr> = req
.headers()
.get("x-forwarded-for")
.flat_map(|h| h.split(", "))
.flat_map(str::parse)
.collect::<Vec<_>>();
// fetch Rocket's "client IP", which first inspects "X-REAL-IP" header
let client_ip = req.client_ip().unwrap();
// find first (last) X-FORWARDED-FOR address that does not match one of the trusted proxies
Outcome::Success(ClientIP(
xff_ip_addresses
.iter()
.rev()
.find(|xff_ip_address| !trusted_proxies.contains(xff_ip_address))
.unwrap_or_else(|| xff_ip_addresses.first().unwrap_or(&client_ip))
.to_string(),
))
}
}