diff --git a/.env.sample b/.env.sample index c6017cd0182..97fdee89991 100644 --- a/.env.sample +++ b/.env.sample @@ -3,6 +3,10 @@ # `postgres://postgres@localhost/cargo_registry`. export DATABASE_URL= +# Allowed origins - any origins for which you want to allow browser +# access to authenticated endpoints. +export WEB_ALLOWED_ORIGINS=http://localhost:8888,http://localhost:4200 + # If you are running a mirror of crates.io, uncomment this line. # export MIRROR=1 diff --git a/src/config.rs b/src/config.rs index 4ae82e2fca7..d6fc655a12d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,6 +17,7 @@ pub struct Config { pub publish_rate_limit: PublishRateLimit, pub blocked_traffic: Vec<(String, Vec)>, pub domain_name: String, + pub allowed_origins: Vec, } impl Default for Config { @@ -121,6 +122,10 @@ impl Default for Config { } } }; + let allowed_origins = env("WEB_ALLOWED_ORIGINS") + .split(',') + .map(ToString::to_string) + .collect(); Config { uploader, session_key: env("SESSION_KEY"), @@ -136,6 +141,7 @@ impl Default for Config { publish_rate_limit: Default::default(), blocked_traffic: blocked_traffic(), domain_name: domain_name(), + allowed_origins, } } } diff --git a/src/controllers/util.rs b/src/controllers/util.rs index b782b2bef9a..ee73f02956b 100644 --- a/src/controllers/util.rs +++ b/src/controllers/util.rs @@ -6,7 +6,6 @@ use crate::models::{ApiToken, User}; use crate::util::errors::{ forbidden, internal, AppError, AppResult, ChainError, InsecurelyGeneratedTokenRevoked, }; -use conduit::Host; #[derive(Debug)] pub struct AuthenticatedUser { @@ -36,32 +35,22 @@ impl AuthenticatedUser { // be: https://crates.io in production, or http://localhost:port/ in development. fn verify_origin(req: &dyn RequestExt) -> AppResult<()> { let headers = req.headers(); - // If x-forwarded-host and -proto are present, trust those to tell us what the proto and host - // are; otherwise (in local dev) trust the Host header and the scheme. - let forwarded_host = headers.get("x-forwarded-host"); - let forwarded_proto = headers.get("x-forwarded-proto"); - let expected_origin = match (forwarded_host, forwarded_proto) { - (Some(host), Some(proto)) => format!( - "{}://{}", - proto.to_str().unwrap_or_default(), - host.to_str().unwrap_or_default() - ), - // For the default case we assume HTTP, because we know we're not serving behind a reverse - // proxy, and we also know that crates by itself doesn't serve HTTPS. - _ => match req.host() { - Host::Name(a) => format!("http://{}", a), - Host::Socket(a) => format!("http://{}", a.to_string()), - }, - }; + let allowed_origins = req + .app() + .config + .allowed_origins + .iter() + .map(|s| &**s) + .collect::>(); let bad_origin = headers .get_all(header::ORIGIN) .iter() - .find(|h| h.to_str().unwrap_or_default() != expected_origin); + .find(|h| !allowed_origins.contains(&h.to_str().unwrap_or_default())); if let Some(bad_origin) = bad_origin { let error_message = format!( - "only same-origin requests can be authenticated. expected {}, got {:?}", - expected_origin, bad_origin + "only same-origin requests can be authenticated. got {:?}", + bad_origin ); return Err(internal(&error_message)) .chain_error(|| Box::new(forbidden()) as Box); diff --git a/src/tests/all.rs b/src/tests/all.rs index 7ee498f2daf..be5ac468180 100644 --- a/src/tests/all.rs +++ b/src/tests/all.rs @@ -145,6 +145,7 @@ fn simple_config() -> Config { publish_rate_limit: Default::default(), blocked_traffic: Default::default(), domain_name: "crates.io".into(), + allowed_origins: Vec::new(), } }