@@ -6,6 +6,7 @@ use crate::models::{ApiToken, User};
6
6
use crate :: util:: errors:: {
7
7
forbidden, internal, AppError , AppResult , ChainError , InsecurelyGeneratedTokenRevoked ,
8
8
} ;
9
+ use conduit:: Host ;
9
10
10
11
#[ derive( Debug ) ]
11
12
pub struct AuthenticatedUser {
@@ -28,33 +29,50 @@ impl AuthenticatedUser {
28
29
}
29
30
}
30
31
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
+
31
72
impl < ' a > UserAuthenticationExt for dyn RequestExt + ' a {
32
73
/// Obtain `AuthenticatedUser` for the request or return an `Forbidden` error
33
74
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 ) ?;
58
76
if let Some ( id) = self . extensions ( ) . find :: < TrustedUserId > ( ) . map ( |x| x. 0 ) {
59
77
log_request:: add_custom_metadata ( self , "uid" , id) ;
60
78
Ok ( AuthenticatedUser {
0 commit comments