diff --git a/crates/lib/http_server_common/src/lib.rs b/crates/lib/http_server_common/src/lib.rs index 44ed722aef..2f7981c30d 100644 --- a/crates/lib/http_server_common/src/lib.rs +++ b/crates/lib/http_server_common/src/lib.rs @@ -12,4 +12,5 @@ pub mod cors; pub mod endpoints; +pub mod request; pub mod response; diff --git a/crates/lib/http_server_common/src/request/get_request_ip.rs b/crates/lib/http_server_common/src/request/get_request_ip.rs new file mode 100644 index 0000000000..c1b097410e --- /dev/null +++ b/crates/lib/http_server_common/src/request/get_request_ip.rs @@ -0,0 +1,100 @@ +use actix_web::HttpRequest; +use actix_web::dev::ServiceRequest; +use log::{info}; +use actix_web::http::{HeaderMap, HeaderName}; +use std::str::FromStr; + +pub fn get_request_ip(request: &HttpRequest) -> String { + let headers = request.headers(); + let maybe_x_forwarded = get_ip_from_header(headers, "x-forwarded-for"); + let maybe_forwarded = get_ip_from_header(headers, "forwarded"); + + info!("(1) x-forwarded-for: {:?}, forwarded: {:?}", maybe_x_forwarded, maybe_forwarded); + + let maybe_ip = maybe_x_forwarded.or(maybe_forwarded); + + match maybe_ip { + Some(ip_address) => ip_address, + None => { + // If we're running without the upstream Rust proxy, we can grab 'x-forarded-for', which is + // populated by the DigitalOcean load balancer. + let ip_address_and_port = request.connection_info() + .remote_addr() + .unwrap_or("") + .to_string(); + let ip_address = ip_address_and_port.split(":") + .collect::>() + .get(0) + .copied() + .unwrap_or("") + .to_string(); + info!("Forwarded IP address (1): {}", &ip_address); + ip_address + }, + } +} + +// TODO: De-duplicate +pub fn get_service_request_ip(request: &ServiceRequest) -> String { + let headers = request.headers(); + let maybe_x_forwarded = get_ip_from_header(headers, "x-forwarded-for"); + let maybe_forwarded = get_ip_from_header(headers, "forwarded"); + + info!("(2) x-forwarded-for: {:?}, forwarded: {:?}", maybe_x_forwarded, maybe_forwarded); + + let maybe_ip = maybe_x_forwarded.or(maybe_forwarded); + + match maybe_ip { + Some(ip_address) => ip_address, + None => { + // If we're running without the upstream Rust proxy, we can grab 'x-forarded-for', which is + // populated by the DigitalOcean load balancer. + let ip_address_and_port = request.connection_info() + .remote_addr() + .unwrap_or("") + .to_string(); + let ip_address = ip_address_and_port.split(":") + .collect::>() + .get(0) + .copied() + .unwrap_or("") + .to_string(); + info!("(2) Forwarded IP address: {}", &ip_address); + ip_address + }, + } +} + +fn get_ip_from_header(headers: &HeaderMap, header_name: &str) -> Option { + if let Ok(header_name) = HeaderName::from_str(header_name) { + headers.get(&header_name) + .and_then(|value| value.to_str().ok()) + .and_then(|value| first_ip_from_list(value)) + } else { + None + } +} + +// NB: GKE packs in multiple IP addresses into the header: +// Example of header: x-forwarded-for: Some("136.55.189.34, 34.117.9.171") +fn first_ip_from_list(ip_list: &str) -> Option { + ip_list.split(",") + .into_iter() + .map(|ip| ip.trim()) + .find(|ip| !ip.is_empty()) + .map(|ip| ip.to_string()) +} + +#[cfg(test)] +mod tests { + use crate::http_server::web_utils::ip_address::first_ip_from_list; + + #[test] + fn test_first_ip() { + assert_eq!(first_ip_from_list("1.2.3.4"), Some("1.2.3.4".to_string())); + assert_eq!(first_ip_from_list("1.2.3.4, 34.117.9.171"), Some("1.2.3.4".to_string())); + assert_eq!(first_ip_from_list(", 1.2.3.4"), Some("1.2.3.4".to_string())); + assert_eq!(first_ip_from_list("1.2.3.4, "), Some("1.2.3.4".to_string())); + assert_eq!(first_ip_from_list(""), None); + } +} \ No newline at end of file diff --git a/crates/lib/http_server_common/src/request/mod.rs b/crates/lib/http_server_common/src/request/mod.rs new file mode 100644 index 0000000000..e4ff0429af --- /dev/null +++ b/crates/lib/http_server_common/src/request/mod.rs @@ -0,0 +1 @@ +pub mod get_request_ip; \ No newline at end of file diff --git a/crates/service/obs_gateway/src/endpoints/oauth_end.rs b/crates/service/obs_gateway/src/endpoints/oauth_end.rs index c2791676c5..1bef800fe5 100644 --- a/crates/service/obs_gateway/src/endpoints/oauth_end.rs +++ b/crates/service/obs_gateway/src/endpoints/oauth_end.rs @@ -11,6 +11,7 @@ use std::fmt; use std::sync::Arc; use twitch_oauth2::tokens::BearerTokenType::UserToken; use twitch_oauth2::{CsrfToken, TwitchToken}; +use http_server_common::request::get_request_ip::get_request_ip; #[derive(Deserialize)] pub struct QueryParams { @@ -139,13 +140,19 @@ pub async fn oauth_end_enroll_from_redirect( info!("Never expiring: {:?}", never_expiring); info!("Expires in (don't store): {:?}", expires_in); // Should be ~4 hours + let ip_address = get_request_ip(&http_request); + let expires_seconds = expires_in.as_secs() as u32; // NB: Silent overflow let mut insert_builder = TwitchOauthTokenInsertBuilder::new(&user_id, &auth_token) .set_expires_in_seconds(Some(expires_seconds)) - .set_twitch_username(Some(&twitch_username)); - //.set_has_bits_read(true) + .set_twitch_username(Some(&twitch_username)) + .set_refresh_token(refresh_token.as_deref()) + .set_ip_address_creation(Some(&ip_address)); + //.set_user_token() + //.set_token_type() + //.set_has_bits_read(true) ... let result = insert_builder.insert(&server_state.backends.mysql_pool) .await