diff --git a/Cargo.lock b/Cargo.lock index 024bc445df..5b90bd4982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -330,6 +330,19 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "actix_artcraft" +version = "0.0.1" +dependencies = [ + "actix-http", + "actix-service", + "actix-web", + "actix_helpers", + "enums", + "log", + "workspace-build-acceleration", +] + [[package]] name = "actix_cors_configs" version = "0.0.1" @@ -1351,6 +1364,7 @@ version = "0.0.1" dependencies = [ "actix-http", "actix-web", + "actix_artcraft", "anyhow", "artcraft_api_defs", "async-stripe 1.0.0-alpha.2", @@ -10719,6 +10733,7 @@ dependencies = [ "actix-web", "actix-web-actors", "actix-web-lab", + "actix_artcraft", "actix_cors_configs", "actix_helpers", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index fe120bd453..3084e833e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "crates/desktop/artcraft", # Shared libraries + "crates/lib/actix_artcraft", "crates/lib/actix_cors_configs", "crates/lib/actix_helpers", "crates/lib/bootstrap", @@ -186,6 +187,8 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" } # Internal libs # Setting paths here ensures we don't need to do so in every crate's Cargo.toml # It makes refactoring much easier. +actix_artcraft = { path = "crates/lib/actix_artcraft" } +actix_helpers = { path = "crates/lib/actix_helpers" } artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" } build_metadata = { path = "crates/lib/build_metadata" } cloud_storage = { path = "crates/api_clients/cloud_storage" } diff --git a/crates/lib/actix_artcraft/Cargo.toml b/crates/lib/actix_artcraft/Cargo.toml new file mode 100644 index 0000000000..24bc78a601 --- /dev/null +++ b/crates/lib/actix_artcraft/Cargo.toml @@ -0,0 +1,34 @@ +# For things that are too application-logic specific to live in generic actix helpers. +[package] +name = "actix_artcraft" +edition = "2024" +version = "0.0.1" +authors = [ + "Brandon Thomas ", + "Brandon Thomas ", +] +publish = false + +[package.metadata.cargo-udeps.ignore] +normal = ["workspace-build-acceleration"] + +[lib] +name = "actix_artcraft" +path = "src/lib.rs" + +[dependencies] +# Workspace hack (faster compile times) +workspace-build-acceleration.workspace = true + +# Internal +actix_helpers.workspace = true +enums.workspace = true + +# External +actix-http.workspace = true +actix-service.workspace = true +actix-web.workspace = true +log.workspace = true + +[dev-dependencies] +# (None yet) diff --git a/crates/lib/actix_artcraft/src/lib.rs b/crates/lib/actix_artcraft/src/lib.rs new file mode 100644 index 0000000000..110ce8b0cc --- /dev/null +++ b/crates/lib/actix_artcraft/src/lib.rs @@ -0,0 +1,20 @@ +//! actix_artcraft - for actix helpers that need to be shared between crates, +//! but are too application-specific to be in general actix helper crates +//! + +// Never allow these +#![forbid(private_bounds)] +#![forbid(private_interfaces)] +#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things + +// Okay to toggle +#![forbid(unreachable_patterns)] +#![forbid(unused_imports)] +#![forbid(unused_mut)] +#![forbid(unused_variables)] + +// Always allow +#![allow(dead_code)] +#![allow(non_snake_case)] + +pub mod requests; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/requests/get_request_signup_source.rs b/crates/lib/actix_artcraft/src/requests/get_request_signup_source.rs similarity index 59% rename from crates/service/web/storyteller_web/src/http_server/requests/get_request_signup_source.rs rename to crates/lib/actix_artcraft/src/requests/get_request_signup_source.rs index ee9d3b36e3..247fb19ecb 100644 --- a/crates/service/web/storyteller_web/src/http_server/requests/get_request_signup_source.rs +++ b/crates/lib/actix_artcraft/src/requests/get_request_signup_source.rs @@ -1,6 +1,5 @@ use actix_helpers::extractors::get_request_origin_uri::get_request_origin_uri; use actix_web::HttpRequest; -use enums::by_table::users::user_signup_source::UserSignupSource; use log::warn; const ARTCRAFT : &str = "artcraft"; @@ -50,10 +49,6 @@ pub fn get_request_signup_source(http_request: &HttpRequest) -> Option<&'static None } -pub fn get_request_signup_source_enum(http_request: &HttpRequest) -> Option { - get_request_signup_source(http_request) - .and_then(|source_str| UserSignupSource::from_str(source_str).ok()) -} #[cfg(test)] mod tests { @@ -61,7 +56,7 @@ mod tests { mod origin_header_str { use super::*; - use crate::http_server::requests::get_request_signup_source::get_request_signup_source; + use crate::requests::get_request_signup_source::get_request_signup_source; #[test] fn artcraft_dot_ai() { @@ -119,66 +114,4 @@ mod tests { assert_eq!(get_request_signup_source(&request), Some("storyteller")); } } - - mod origin_header_enum { - use super::*; - use crate::http_server::requests::get_request_signup_source::get_request_signup_source_enum; - use enums::by_table::users::user_signup_source::UserSignupSource; - - #[test] - fn artcraft_dot_ai() { - let request = TestRequest::get() - .insert_header(("origin", "https://artcraft.ai")) - .to_http_request(); - assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft)); - } - - #[test] - fn get_artcraft_dot_com() { - let request = TestRequest::get() - .insert_header(("origin", "https://getartcraft.com")) - .to_http_request(); - assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft)); - } - - #[test] - fn api_dot_get_artcraft_dot_com() { - let request = TestRequest::get() - .insert_header(("origin", "https://api.getartcraft.com")) - .to_http_request(); - assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft)); - } - - #[test] - fn fakeyou_dot_com() { - let request = TestRequest::get() - .insert_header(("origin", "https://fakeyou.com")) - .to_http_request(); - assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::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_signup_source_enum(&request), Some(UserSignupSource::FakeYou)); - } - - #[test] - fn storyteller_dot_ai() { - let request = TestRequest::get() - .insert_header(("origin", "https://storyteller.ai")) - .to_http_request(); - assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::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_signup_source_enum(&request), Some(UserSignupSource::Storyteller)); - } - } } diff --git a/crates/lib/actix_artcraft/src/requests/get_request_signup_source_enum.rs b/crates/lib/actix_artcraft/src/requests/get_request_signup_source_enum.rs new file mode 100644 index 0000000000..2d1bfaadf8 --- /dev/null +++ b/crates/lib/actix_artcraft/src/requests/get_request_signup_source_enum.rs @@ -0,0 +1,75 @@ +use crate::requests::get_request_signup_source::get_request_signup_source; +use actix_web::HttpRequest; +use enums::by_table::users::user_signup_source::UserSignupSource; + +pub fn get_request_signup_source_enum(http_request: &HttpRequest) -> Option { + get_request_signup_source(http_request) + .and_then(|source_str| UserSignupSource::from_str(source_str).ok()) +} + +#[cfg(test)] +mod tests { + use actix_web::test::TestRequest; + + mod origin_header_enum { + use super::*; + use crate::requests::get_request_signup_source_enum::get_request_signup_source_enum; + use enums::by_table::users::user_signup_source::UserSignupSource; + + #[test] + fn artcraft_dot_ai() { + let request = TestRequest::get() + .insert_header(("origin", "https://artcraft.ai")) + .to_http_request(); + assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft)); + } + + #[test] + fn get_artcraft_dot_com() { + let request = TestRequest::get() + .insert_header(("origin", "https://getartcraft.com")) + .to_http_request(); + assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft)); + } + + #[test] + fn api_dot_get_artcraft_dot_com() { + let request = TestRequest::get() + .insert_header(("origin", "https://api.getartcraft.com")) + .to_http_request(); + assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::ArtCraft)); + } + + #[test] + fn fakeyou_dot_com() { + let request = TestRequest::get() + .insert_header(("origin", "https://fakeyou.com")) + .to_http_request(); + assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::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_signup_source_enum(&request), Some(UserSignupSource::FakeYou)); + } + + #[test] + fn storyteller_dot_ai() { + let request = TestRequest::get() + .insert_header(("origin", "https://storyteller.ai")) + .to_http_request(); + assert_eq!(get_request_signup_source_enum(&request), Some(UserSignupSource::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_signup_source_enum(&request), Some(UserSignupSource::Storyteller)); + } + } +} diff --git a/crates/lib/actix_artcraft/src/requests/mod.rs b/crates/lib/actix_artcraft/src/requests/mod.rs new file mode 100644 index 0000000000..022f6662f6 --- /dev/null +++ b/crates/lib/actix_artcraft/src/requests/mod.rs @@ -0,0 +1,2 @@ +pub mod get_request_signup_source; +pub mod get_request_signup_source_enum; \ No newline at end of file diff --git a/crates/lib/users/src/email/generate_random_synthetic_email.rs b/crates/lib/users/src/email/generate_random_synthetic_email.rs new file mode 100644 index 0000000000..2b8c1d397d --- /dev/null +++ b/crates/lib/users/src/email/generate_random_synthetic_email.rs @@ -0,0 +1,29 @@ +use rand::Rng; + +const EMAIL_PREFIX : &str = "synthetic.email"; +const EMAIL_DOMAIN : &str = "getartcraft.com"; + +pub fn generate_random_synthetic_email() -> String { + let random_digit = rand::thread_rng().gen_range(u32::MIN..u32::MAX); + format!("{}.{}@{}", EMAIL_PREFIX, random_digit, EMAIL_DOMAIN) +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + use crate::email::generate_random_synthetic_email::generate_random_synthetic_email; + + #[test] + fn test_base_case_success() { + assert!(generate_random_synthetic_email().len() > 0); + } + + #[test] + fn generate_lots() { + let mut collection = HashSet::new(); + for _ in 0..100 { + collection.insert(generate_random_synthetic_email()); + } + assert!(collection.len() > 50); // NB: Should be an easy bar to hit + } +} \ No newline at end of file diff --git a/crates/lib/users/src/email/mod.rs b/crates/lib/users/src/email/mod.rs index ea1c76b2ef..fd7c8cc925 100644 --- a/crates/lib/users/src/email/mod.rs +++ b/crates/lib/users/src/email/mod.rs @@ -1 +1,2 @@ -pub mod email_to_gravatar; \ No newline at end of file +pub mod email_to_gravatar; +pub mod generate_random_synthetic_email; \ No newline at end of file diff --git a/crates/service/plugins/billing_artcraft/Cargo.toml b/crates/service/plugins/billing_artcraft/Cargo.toml index 45e989c40c..da4fab8d5a 100644 --- a/crates/service/plugins/billing_artcraft/Cargo.toml +++ b/crates/service/plugins/billing_artcraft/Cargo.toml @@ -21,6 +21,7 @@ normal = ["workspace-build-acceleration"] workspace-build-acceleration.workspace = true # Internal +actix_artcraft.workspace = true artcraft_api_defs.workspace = true component_traits.workspace = true enums.workspace = true diff --git a/crates/service/plugins/billing_artcraft/src/endpoints/checkout_with_user_signup/user_creation_case.rs b/crates/service/plugins/billing_artcraft/src/endpoints/checkout_with_user_signup/user_creation_case.rs index 0208780d40..4250005303 100644 --- a/crates/service/plugins/billing_artcraft/src/endpoints/checkout_with_user_signup/user_creation_case.rs +++ b/crates/service/plugins/billing_artcraft/src/endpoints/checkout_with_user_signup/user_creation_case.rs @@ -1,15 +1,18 @@ -use actix_web::HttpRequest; -use log::{info, warn}; use crate::utils::artcraft_stripe_config::ArtcraftStripeConfigWithClient; use crate::utils::common_web_error::CommonWebError; -use sqlx::pool::PoolConnection; -use sqlx::MySql; -use stripe_shared::CheckoutSession; +use actix_artcraft::requests::get_request_signup_source_enum::get_request_signup_source_enum; +use actix_web::HttpRequest; use http_server_common::request::get_request_ip::get_request_ip; +use log::{info, warn}; use mysql_queries::queries::users::user::create::create_account_error::CreateAccountError; use mysql_queries::queries::users::user::create::create_account_from_google_sso::{create_account_from_google_sso, CreateAccountFromGoogleSsoArgs}; use mysql_queries::utils::transactor::Transactor; +use sqlx::pool::PoolConnection; +use sqlx::MySql; +use stripe_shared::CheckoutSession; +use enums::by_table::users::user_feature_flag::UserFeatureFlag; use users::email::email_to_gravatar::email_to_gravatar; +use users::email::generate_random_synthetic_email::generate_random_synthetic_email; use users::username::generate_random_username::generate_random_username; pub (super) async fn user_creation_case( @@ -23,34 +26,43 @@ pub (super) async fn user_creation_case( .await .map_err(|e| { warn!("Could not begin transaction: {:?}", e); - GoogleCreateAccountErrorResponse::server_error() + CommonWebError::ServerError })?; - let ip_address = get_request_ip(&http_request); let maybe_source = get_request_signup_source_enum(&http_request); + // NB: Not sure if these lock important functionality, so we're keeping this + let user_feature_flags = vec![ + UserFeatureFlag::Studio, + UserFeatureFlag::VideoStyleTransfer, + ].into_iter() + .map(|flag| flag.to_string()) + .collect::>() + .join(","); + let mut maybe_user_token = None; let mut maybe_user_display_name = None; for _ in 0..3 { - let user_email_address = "todo@todo.com"; + // NB: We try a few times to make sure we don't hit an email/username collision. + + let user_email_address = generate_random_synthetic_email(); let user_email_gravatar_hash = email_to_gravatar(&user_email_address); - // NB: We try a few times to make sure we don't hit a username collision. let display_name = generate_random_username(); let username = display_name.trim().to_lowercase(); - info!("generated username: {}", username); + info!("generated username: {} ; generated email: {}", username, user_email_address); let result = create_account_from_google_sso( CreateAccountFromGoogleSsoArgs { username: &username, display_name: &display_name, - email_address: &args.user_email_address, + email_address: &user_email_address, email_gravatar_hash: &user_email_gravatar_hash, email_confirmed_by_google: args.claims.email_verified(), - maybe_feature_flags: user_feature_flags.as_deref(), + maybe_feature_flags: Some(&user_feature_flags), ip_address: &ip_address, maybe_source, }, @@ -68,7 +80,7 @@ pub (super) async fn user_creation_case( }, Err(err) => { warn!("error creating account from google sso: {:?}", err); - return Err(GoogleCreateAccountErrorResponse::server_error()); + return Err(CommonWebError::ServerError); }, } } diff --git a/crates/service/web/storyteller_web/Cargo.toml b/crates/service/web/storyteller_web/Cargo.toml index da4a4ede84..f9f81d386e 100644 --- a/crates/service/web/storyteller_web/Cargo.toml +++ b/crates/service/web/storyteller_web/Cargo.toml @@ -32,8 +32,9 @@ path = "src/api_doc_cli.rs" workspace-build-acceleration.workspace = true # Internal +actix_artcraft.workspace = true actix_cors_configs = { path = "../../../lib/actix_cors_configs" } -actix_helpers = { path = "../../../lib/actix_helpers" } +actix_helpers.workspace = true artcraft_api_defs.workspace = true bootstrap = { path = "../../../lib/bootstrap" } bucket_paths = { path = "../../../lib/bucket_paths" } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs index 32af6c897a..04d8f3b0d2 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs @@ -1,7 +1,7 @@ use crate::http_server::endpoints::users::google_sso::google_sso_handler::GoogleCreateAccountErrorResponse; use crate::http_server::endpoints::users::google_sso::handle_new_sso_account::NewSsoAccountInfo; -use crate::http_server::requests::get_request_signup_source::{get_request_signup_source, get_request_signup_source_enum}; use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; +use actix_artcraft::requests::get_request_signup_source_enum::get_request_signup_source_enum; use actix_web::HttpRequest; use enums::by_table::users::user_feature_flag::UserFeatureFlag; use google_sign_in::claims::claims::Claims; diff --git a/crates/service/web/storyteller_web/src/http_server/requests/mod.rs b/crates/service/web/storyteller_web/src/http_server/requests/mod.rs index 83dac24461..68bf0c0996 100644 --- a/crates/service/web/storyteller_web/src/http_server/requests/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/requests/mod.rs @@ -1,3 +1,2 @@ pub mod get_request_domain_branding; -pub mod get_request_signup_source; pub mod request_headers; \ No newline at end of file