This commit is contained in:
Brandon Thomas committed 2024-09-25 13:12:10 -04:00
1 parent c0627eee28
commit 6bd31c7075
4 files changed
+130 -146

No files matched your search

@@ -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,
@@ -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;
@@ -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<DomainBranding> {
// 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<DomainB
return Some(branding);
}
// NB: This may not actually work against real requests. It fails in local dev. See above comment.
let maybe_hostname = http_request.uri().host();
if let Some(branding) = match_possible_hostname(maybe_hostname) {
info!("URI hostname: {:?} Branding for hostname: {:?}", maybe_hostname, branding);
return Some(branding);
}
None
}
@@ -47,7 +69,43 @@ fn match_possible_hostname(maybe_hostname: Option<&str>) -> Option<DomainBrandin
mod tests {
use actix_web::test::TestRequest;
use crate::http_server::requests::get_request_domain_branding::{DomainBranding, get_request_domain_branding};
use crate::http_server::requests::get_request_domain_branding::{get_request_domain_branding, DomainBranding};
mod origin_header {
use super::*;
#[test]
fn fakeyou_dot_com() {
let request = TestRequest::get()
.insert_header(("origin", "https://fakeyou.com"))
.to_http_request();
assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::FakeYou));
}
#[test]
fn api_dot_fakeyou_dot_com() {
let request = TestRequest::get()
.insert_header(("origin", "https://api.fakeyou.com"))
.to_http_request();
assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::FakeYou));
}
#[test]
fn storyteller_dot_ai() {
let request = TestRequest::get()
.insert_header(("origin", "https://storyteller.ai"))
.to_http_request();
assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::Storyteller));
}
#[test]
fn api_dot_storyteller_dot_ai() {
let request = TestRequest::get()
.insert_header(("origin", "https://api.storyteller.ai"))
.to_http_request();
assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::Storyteller));
}
}
mod host_header {
use super::*;
@@ -85,39 +143,39 @@ mod tests {
}
}
mod uri {
use super::*;
#[test]
fn fakeyou_dot_com() {
let request = TestRequest::get()
.uri("https://fakeyou.com")
.to_http_request();
assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::FakeYou));
}
#[test]
fn api_dot_fakeyou_dot_com() {
let request = TestRequest::get()
.uri("https://api.fakeyou.com")
.to_http_request();
assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::FakeYou));
}
#[test]
fn storyteller_dot_ai() {
let request = TestRequest::get()
.uri("https://storyteller.ai")
.to_http_request();
assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::Storyteller));
}
#[test]
fn api_dot_storyteller_dot_ai() {
let request = TestRequest::get()
.uri("https://api.storyteller.ai")
.to_http_request();
assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::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_domain_branding(&request), Some(DomainBranding::FakeYou));
// }
//
// #[test]
// fn api_dot_fakeyou_dot_com() {
// let request = TestRequest::get()
// .uri("https://api.fakeyou.com")
// .to_http_request();
// assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::FakeYou));
// }
//
// #[test]
// fn storyteller_dot_ai() {
// let request = TestRequest::get()
// .uri("https://storyteller.ai")
// .to_http_request();
// assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::Storyteller));
// }
//
// #[test]
// fn api_dot_storyteller_dot_ai() {
// let request = TestRequest::get()
// .uri("https://api.storyteller.ai")
// .to_http_request();
// assert_eq!(get_request_domain_branding(&request), Some(DomainBranding::Storyteller));
// }
// }
}
@@ -1,3 +1,4 @@
use actix_http::header::HOST;
use actix_helpers::extractors::get_request_origin_uri::get_request_origin_uri;
use actix_web::HttpRequest;
use log::warn;
@@ -9,6 +10,17 @@ const STORYTELLER : &str = "storyteller";
/// While we could back this with an enum, there may be a motivation to use this VARCHAR(255) field
/// for more robust payloads and user tracking in the future.
pub fn get_request_signup_source(http_request: &HttpRequest) -> 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"));
}