diff --git a/crates/service/web/storyteller_web/src/email/mod.rs b/crates/service/web/storyteller_web/src/email/mod.rs index 66c46b36e7..2a5e648931 100644 --- a/crates/service/web/storyteller_web/src/email/mod.rs +++ b/crates/service/web/storyteller_web/src/email/mod.rs @@ -1,2 +1,2 @@ -mod send_password_reset_email; -mod get_password_reset_url; \ No newline at end of file +pub mod send_password_reset_email; +pub mod get_password_reset_url; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/email/send_password_reset_email.rs b/crates/service/web/storyteller_web/src/email/send_password_reset_email.rs index b694a1e192..594de96b40 100644 --- a/crates/service/web/storyteller_web/src/email/send_password_reset_email.rs +++ b/crates/service/web/storyteller_web/src/email/send_password_reset_email.rs @@ -6,27 +6,37 @@ use resend_rs::Resend; use server_environment::ServerEnvironment; pub struct SendPasswordResetEmailArgs<'a> { - email_address_destination: &'a str, - verification_token: &'a str, - resend_client: &'a Resend, - server_environment: ServerEnvironment, - domain_branding: DomainBranding, + pub email_address_destination: &'a str, + pub verification_token: &'a str, + pub resend_api_key: &'a str, + pub server_environment: ServerEnvironment, + pub domain_branding: DomainBranding, } pub async fn send_password_reset_email( args: SendPasswordResetEmailArgs<'_> ) -> AnyhowResult<()> { - let resend = Resend::new("re_xxxxxxxxx"); + let resend = Resend::new(&args.resend_api_key); - let from = "ArtCraft "; let to = [args.email_address_destination]; let subject = "ArtCraft Password Reset"; + let from_address = match args.domain_branding { + DomainBranding::ArtCraftDotAi | + DomainBranding::GetArtCraft => "ArtCraft ", + DomainBranding::FakeYou => "FakeYou ", + DomainBranding::Storyteller => "FakeYou ", + }; + let url = match (args.domain_branding, args.server_environment) { + // Legacy FakeYou (DomainBranding::FakeYou, ServerEnvironment::Development) => PasswordResetLocation::FakeYouDevelopment, (DomainBranding::FakeYou, ServerEnvironment::Production) => PasswordResetLocation::FakeYouProduction, - (_, _) => PasswordResetLocation::ArtCraftProduction, + // Everything else "development" is ArtCraft Development + (_, ServerEnvironment::Development) => PasswordResetLocation::ArtCraftDevelopment, + // Everything else "production" is ArtCraft Production + (_, ServerEnvironment::Production) => PasswordResetLocation::ArtCraftProduction, }; let link = get_password_reset_url(args.verification_token, url); @@ -45,10 +55,10 @@ pub async fn send_password_reset_email( Storyteller.ai (FakeYou) Team "#); - let email = CreateEmailBaseOptions::new(from, to, subject) + let email = CreateEmailBaseOptions::new(from_address, to, subject) .with_html(&html_message); - let _email = args.resend_client.emails.send(email).await?; + let _email = resend.emails.send(email).await?; Ok(()) } \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/password_reset_request_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/password_reset_request_handler.rs index 5c050001e4..068820ef35 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/password_reset_request_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/password_reset_request_handler.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use actix_web::http::StatusCode; use actix_web::{web, HttpRequest, HttpResponse, ResponseError}; -use log::{error, warn}; +use log::{error, info, warn}; use serde::Deserialize; use sqlx::MySqlPool; use strum_macros::Display; @@ -20,6 +20,9 @@ use mysql_queries::queries::users::user::get::lookup_user_for_login_by_email::lo use mysql_queries::queries::users::user::get::lookup_user_for_login_by_username::lookup_user_for_login_by_username; use mysql_queries::queries::users::user_password_resets::create_password_reset_request::create_password_reset; use server_environment::ServerEnvironment; +use crate::email::send_password_reset_email::{send_password_reset_email, SendPasswordResetEmailArgs}; +use crate::http_server::requests::get_request_domain_branding::{get_request_domain_branding, DomainBranding}; +use crate::state::server_state::ServerState; #[derive(Deserialize)] pub struct PasswordResetRequestedRequest { @@ -81,7 +84,8 @@ pub async fn password_reset_request_handler( http_request: HttpRequest, request: web::Json, mysql_pool: web::Data, - _server_environment: web::Data, + server_environment: web::Data, + server_state: web::Data, _sender: web::Data, ) -> Result { @@ -134,7 +138,7 @@ pub async fn password_reset_request_handler( email_category: EmailCategory::PasswordReset, maybe_email_args: Some(EmailSenderJobArgs { args: Some(PolymorphicEmailSenderJobArgs::Pr(EmailJobPasswordResetArgs { - password_reset_secret_key: Some(secret_key), + password_reset_secret_key: Some(secret_key.clone()), })), }), ietf_language_tag, @@ -153,7 +157,20 @@ pub async fn password_reset_request_handler( PasswordResetRequestedRequestError::Internal })?; + let domain_branding = get_request_domain_branding(&http_request) + .unwrap_or(DomainBranding::GetArtCraft); + let server_environment = **server_environment; + + info!("Sending password reset email..."); + + send_password_reset_email(SendPasswordResetEmailArgs { + email_address_destination: &user.email_address, + verification_token: &secret_key, + resend_api_key: &server_state.resend.api_key, + server_environment, + domain_branding, + }).await?; success_response() } diff --git a/crates/service/web/storyteller_web/src/main.rs b/crates/service/web/storyteller_web/src/main.rs index 6b02feaea1..344c023c5a 100644 --- a/crates/service/web/storyteller_web/src/main.rs +++ b/crates/service/web/storyteller_web/src/main.rs @@ -80,7 +80,7 @@ use crate::http_server::web_utils::handle_multipart_error::handle_multipart_erro use crate::http_server::web_utils::scoped_temp_dir_creator::ScopedTempDirCreator; use crate::state::certs::google_sign_in_cert::GoogleSignInCert; use crate::state::memory_cache::model_token_to_info_cache::ModelTokenToInfoCache; -use crate::state::server_state::{DurableInMemoryCaches, EnvConfig, EphemeralInMemoryCaches, FalData, InMemoryCaches, OpenAiData, ServerInfo, ServerState, StaticFeatureFlags, StripeSettings, TrollBans}; +use crate::state::server_state::{DurableInMemoryCaches, EnvConfig, EphemeralInMemoryCaches, FalData, InMemoryCaches, OpenAiData, ResendData, ServerInfo, ServerState, StaticFeatureFlags, StripeSettings, TrollBans}; use crate::threads::db_health_checker_thread::db_health_check_status::HealthCheckStatus; use crate::threads::db_health_checker_thread::db_health_checker_thread::db_health_checker_thread; use crate::threads::poll_ip_banlist_thread::poll_ip_bans; @@ -402,6 +402,8 @@ async fn main() -> AnyhowResult<()> { let openai_api_key= easyenv::get_env_string_required("OPENAI_API_KEY")?; + let resend_api_key = easyenv::get_env_string_required("RESEND_API_KEY")?; + let startup_time = Utc::now(); let server_state = ServerState { @@ -460,6 +462,9 @@ async fn main() -> AnyhowResult<()> { openai: OpenAiData { api_key: openai_api_key, }, + resend: ResendData { + api_key: resend_api_key, + }, caches: InMemoryCaches { durable: DurableInMemoryCaches { model_token_info: model_token_info_cache, diff --git a/crates/service/web/storyteller_web/src/state/server_state.rs b/crates/service/web/storyteller_web/src/state/server_state.rs index 7ad97c2d7b..915766af92 100644 --- a/crates/service/web/storyteller_web/src/state/server_state.rs +++ b/crates/service/web/storyteller_web/src/state/server_state.rs @@ -95,6 +95,8 @@ pub struct ServerState { pub openai: OpenAiData, + pub resend: ResendData, + /// Where to store audio uploads for w2l pub audio_uploads_bucket_root: String, @@ -314,3 +316,9 @@ pub struct FalData { pub struct OpenAiData { pub api_key: String, } + +/// Resend integration +#[derive(Clone)] +pub struct ResendData { + pub api_key: String, +}