diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/create_account_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/create_account_handler.rs index e53afd2f78..402b7d3bd8 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/create_account_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/create_account_handler.rs @@ -7,7 +7,6 @@ use std::collections::HashMap; use std::fmt; use std::fmt::Formatter; -use actix_helpers::extractors::get_request_origin_uri::get_request_origin_uri; use actix_web::error::ResponseError; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse}; @@ -23,7 +22,7 @@ use sqlx::MySqlPool; use tokens::tokens::user_sessions::UserSessionToken; use user_input_common::check_for_slurs::contains_slurs; use utoipa::ToSchema; - +use crate::http_server::requests::get_request_signup_source::get_request_signup_source; use crate::http_server::session::http::http_user_session_manager::HttpUserSessionManager; use crate::http_server::validations::is_reserved_username::is_reserved_username; use crate::http_server::validations::validate_passwords::validate_passwords; @@ -185,26 +184,7 @@ pub async fn create_account_handler( let ip_address = get_request_ip(&http_request); - let maybe_origin = get_request_origin_uri(&http_request); - - let mut maybe_source = None; - - match maybe_origin { - Ok(Some(uri)) => { - if let Some(host) = uri.host() { - if host.contains("storyteller") { - maybe_source = Some("storyteller"); - } else if host.contains("fakeyou") { - maybe_source = Some("fakeyou"); - } - } - } - // Fail open for now. - Ok(None) => {} - Err(err) => { - warn!("Origin header error: {:?}", err); - } - } + let maybe_source = get_request_signup_source(&http_request); let create_account_result = create_account_from_email_and_password( &mysql_pool, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_creating_user_account.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_creating_user_account.rs index ec9eb1fbc1..d646985b0f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_creating_user_account.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_creating_user_account.rs @@ -15,6 +15,7 @@ use mysql_queries::queries::users::user::lookup_user_for_login_result::UserRecor use mysql_queries::utils::transactor::Transactor; use sqlx::pool::PoolConnection; use sqlx::{Acquire, MySql}; +use crate::http_server::requests::get_request_signup_source::get_request_signup_source; pub struct CreateArgs<'a> { pub http_request: &'a HttpRequest, @@ -42,6 +43,8 @@ pub async fn handle_creating_user_account( let ip_address = get_request_ip(&args.http_request); let user_email_gravatar_hash = email_to_gravatar(&args.user_email_address); + let mut maybe_source = get_request_signup_source(&args.http_request); + let mut maybe_user_token = None; let mut maybe_user_display_name = None; @@ -61,7 +64,7 @@ pub async fn handle_creating_user_account( email_confirmed_by_google: args.claims.email_verified(), maybe_feature_flags: user_feature_flags.as_deref(), ip_address: &ip_address, - maybe_source: None, // TODO: Add source + maybe_source, }, Transactor::for_transaction(&mut transaction), ).await; diff --git a/crates/service/web/storyteller_web/src/http_server/requests/get_request_domain_branding.rs b/crates/service/web/storyteller_web/src/http_server/requests/get_request_domain_branding.rs index a5a24b3405..622d57f914 100644 --- a/crates/service/web/storyteller_web/src/http_server/requests/get_request_domain_branding.rs +++ b/crates/service/web/storyteller_web/src/http_server/requests/get_request_domain_branding.rs @@ -1,6 +1,7 @@ +use actix_helpers::extractors::get_request_origin_uri::get_request_origin_uri; use actix_web::http::header::HOST; use actix_web::HttpRequest; -use log::info; +use log::{info, warn}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum DomainBranding { @@ -9,7 +10,36 @@ pub enum DomainBranding { } pub fn get_request_domain_branding(http_request: &HttpRequest) -> Option { - // NB: http_request.uri() does not include the hostname - it only includes the path (!) + // NB: "Origin" vs "Referrer" + // + // Basically: + // - "In order to preserve privacy, any browser request can decide to omit the Referer header." + // - "The Origin header is similar to the Referer header, but does not disclose the path, and may be null." + // - "Origin" is sent on cross-origin, same-origin (except GET and HEAD requests - typically). + // + // Reading: + // - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin + // - https://stackoverflow.com/a/71040145 + // + let maybe_origin = get_request_origin_uri(&http_request); + + match maybe_origin { + Ok(Some(uri)) => { + if let Some(branding) = match_possible_hostname(uri.host()) { + info!("Origin header: {:?} Branding for hostname: {:?}", uri, branding); + return Some(branding); + } + } + // Fail open for now. + Ok(None) => {} + Err(err) => { + warn!("Origin header error: {:?}", err); + } + } + + // NB: The "HOST" header contains the hostname. The `http_request.uri()` method we tried + // before does not include the hostname at all - it only includes the path (!), which is + // why we use "HOST" header instead. let maybe_host_header = http_request.headers() .get(HOST) .map(|header| header.to_str().ok()) @@ -20,14 +50,6 @@ pub fn get_request_domain_branding(http_request: &HttpRequest) -> Option) -> Option Option<&'static str> { + // NB: "Origin" vs "Referrer" + // + // Basically: + // - "In order to preserve privacy, any browser request can decide to omit the Referer header." + // - "The Origin header is similar to the Referer header, but does not disclose the path, and may be null." + // - "Origin" is sent on cross-origin, same-origin (except GET and HEAD requests - typically). + // + // Reading: + // - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin + // - https://stackoverflow.com/a/71040145 + // let maybe_origin = get_request_origin_uri(&http_request); match maybe_origin { @@ -28,6 +40,10 @@ pub fn get_request_signup_source(http_request: &HttpRequest) -> Option<&'static } } + // NB: We don't want to check the "Host" header because we might have misconfigured + // a future frontend to talk to some other API gateway and might be improperly and + // silently misattributing signup statistics. + None } @@ -42,7 +58,7 @@ mod tests { #[test] fn fakeyou_dot_com() { let request = TestRequest::get() - .insert_header(("origin", "fakeyou.com")) + .insert_header(("origin", "https://fakeyou.com")) .to_http_request(); assert_eq!(get_request_signup_source(&request), Some("fakeyou")); } @@ -50,7 +66,7 @@ mod tests { #[test] fn api_dot_fakeyou_dot_com() { let request = TestRequest::get() - .insert_header(("origin", "api.fakeyou.com")) + .insert_header(("origin", "https://api.fakeyou.com")) .to_http_request(); assert_eq!(get_request_signup_source(&request), Some("fakeyou")); } @@ -58,7 +74,7 @@ mod tests { #[test] fn storyteller_dot_ai() { let request = TestRequest::get() - .insert_header(("origin", "storyteller.ai")) + .insert_header(("origin", "https://storyteller.ai")) .to_http_request(); assert_eq!(get_request_signup_source(&request), Some("storyteller")); } @@ -66,80 +82,7 @@ mod tests { #[test] fn api_dot_storyteller_dot_ai() { let request = TestRequest::get() - .insert_header(("origin", "api.storyteller.ai")) - .to_http_request(); - assert_eq!(get_request_signup_source(&request), Some("storyteller")); - } - } - - - mod host_header { - use super::*; - - #[test] - fn fakeyou_dot_com() { - let request = TestRequest::get() - .insert_header(("host", "fakeyou.com")) - .to_http_request(); - assert_eq!(get_request_signup_source(&request), Some("fakeyou")); - } - - #[test] - fn api_dot_fakeyou_dot_com() { - let request = TestRequest::get() - .insert_header(("host", "api.fakeyou.com")) - .to_http_request(); - assert_eq!(get_request_signup_source(&request), Some("fakeyou")); - } - - #[test] - fn storyteller_dot_ai() { - let request = TestRequest::get() - .insert_header(("host", "storyteller.ai")) - .to_http_request(); - assert_eq!(get_request_signup_source(&request), Some("storyteller")); - } - - #[test] - fn api_dot_storyteller_dot_ai() { - let request = TestRequest::get() - .insert_header(("host", "api.storyteller.ai")) - .to_http_request(); - assert_eq!(get_request_signup_source(&request), Some("storyteller")); - } - } - - mod uri { - use super::*; - - #[test] - fn fakeyou_dot_com() { - let request = TestRequest::get() - .uri("https://fakeyou.com") - .to_http_request(); - assert_eq!(get_request_signup_source(&request), Some("fakeyou")); - } - - #[test] - fn api_dot_fakeyou_dot_com() { - let request = TestRequest::get() - .uri("https://api.fakeyou.com") - .to_http_request(); - assert_eq!(get_request_signup_source(&request), Some("fakeyou")); - } - - #[test] - fn storyteller_dot_ai() { - let request = TestRequest::get() - .uri("https://storyteller.ai") - .to_http_request(); - assert_eq!(get_request_signup_source(&request), Some("storyteller")); - } - - #[test] - fn api_dot_storyteller_dot_ai() { - let request = TestRequest::get() - .uri("https://api.storyteller.ai") + .insert_header(("origin", "https://api.storyteller.ai")) .to_http_request(); assert_eq!(get_request_signup_source(&request), Some("storyteller")); }