Skip to content

Commit 1540521

Browse files
committed
Factor out verify_origin and handle no-proxy case.
1 parent 8523b79 commit 1540521

File tree

1 file changed

+42
-24
lines changed

1 file changed

+42
-24
lines changed

src/controllers/util.rs

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::models::{ApiToken, User};
66
use crate::util::errors::{
77
forbidden, internal, AppError, AppResult, ChainError, InsecurelyGeneratedTokenRevoked,
88
};
9+
use conduit::Host;
910

1011
#[derive(Debug)]
1112
pub struct AuthenticatedUser {
@@ -28,33 +29,50 @@ impl AuthenticatedUser {
2829
}
2930
}
3031

32+
// The Origin header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin)
33+
// is sent with CORS requests and POST requests, and indicates where the request comes from.
34+
// We don't want to accept authenticated requests that originated from other sites, so this
35+
// function returns an error if the Origin header doesn't match what we expect "this site" to
36+
// be: https://crates.io in production, or http://localhost:port/ in development.
37+
fn verify_origin(req: &dyn RequestExt) -> AppResult<()> {
38+
let headers = req.headers();
39+
// If x-forwarded-host and -proto are present, trust those to tell us what the proto and host
40+
// are; otherwise (in local dev) trust the Host header and the scheme.
41+
let forwarded_host = headers.get("x-forwarded-host");
42+
let forwarded_proto = headers.get("x-forwarded-proto");
43+
let expected_origin = match (forwarded_host, forwarded_proto) {
44+
(Some(host), Some(proto)) => format!(
45+
"{}://{}",
46+
proto.to_str().unwrap_or_default(),
47+
host.to_str().unwrap_or_default()
48+
),
49+
// For the default case we assume HTTP, because we know we're not serving behind a reverse
50+
// proxy, and we also know that crates by itself doesn't serve HTTPS.
51+
_ => match req.host() {
52+
Host::Name(a) => format!("http://{}", a),
53+
Host::Socket(a) => format!("http://{}", a.to_string()),
54+
},
55+
};
56+
57+
let bad_origin = headers
58+
.get_all(header::ORIGIN)
59+
.iter()
60+
.find(|h| h.to_str().unwrap_or_default() != expected_origin);
61+
if let Some(bad_origin) = bad_origin {
62+
let error_message = format!(
63+
"only same-origin requests can be authenticated. expected {}, got {:?}",
64+
expected_origin, bad_origin
65+
);
66+
return Err(internal(&error_message))
67+
.chain_error(|| Box::new(forbidden()) as Box<dyn AppError>);
68+
}
69+
Ok(())
70+
}
71+
3172
impl<'a> UserAuthenticationExt for dyn RequestExt + 'a {
3273
/// Obtain `AuthenticatedUser` for the request or return an `Forbidden` error
3374
fn authenticate(&mut self) -> AppResult<AuthenticatedUser> {
34-
let forwarded_host = self.headers().get("x-forwarded-host");
35-
let forwarded_proto = self.headers().get("x-forwarded-proto");
36-
let expected_origin = match (forwarded_host, forwarded_proto) {
37-
(Some(host), Some(proto)) => format!(
38-
"{}://{}",
39-
proto.to_str().unwrap_or_default(),
40-
host.to_str().unwrap_or_default()
41-
),
42-
_ => "".to_string(),
43-
};
44-
45-
let bad_origin = self
46-
.headers()
47-
.get_all(header::ORIGIN)
48-
.iter()
49-
.find(|h| h.to_str().unwrap_or_default() != expected_origin);
50-
if let Some(bad_origin) = bad_origin {
51-
let error_message = format!(
52-
"only same-origin requests can be authenticated. expected {}, got {:?}",
53-
expected_origin, bad_origin
54-
);
55-
return Err(internal(&error_message))
56-
.chain_error(|| Box::new(forbidden()) as Box<dyn AppError>);
57-
}
75+
verify_origin(self)?;
5876
if let Some(id) = self.extensions().find::<TrustedUserId>().map(|x| x.0) {
5977
log_request::add_custom_metadata(self, "uid", id);
6078
Ok(AuthenticatedUser {

0 commit comments

Comments
 (0)