@@ -6,6 +6,7 @@ use crate::models::{ApiToken, User};
66use crate :: util:: errors:: {
77 forbidden, internal, AppError , AppResult , ChainError , InsecurelyGeneratedTokenRevoked ,
88} ;
9+ use conduit:: Host ;
910
1011#[ derive( Debug ) ]
1112pub 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+
3172impl < ' 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