diff --git a/Cargo.lock b/Cargo.lock index 1485c5a..9c63f7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,6 +97,133 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.2", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel 2.5.0", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.1", + "futures-lite", + "rustix 1.1.2", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.2", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.89" @@ -289,6 +416,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bon" version = "3.8.1" @@ -1007,6 +1147,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "event-listener" version = "5.4.1" @@ -1018,6 +1164,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1301,6 +1457,18 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "governor" version = "0.10.4" @@ -1464,6 +1632,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -2049,6 +2223,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "langtag" version = "0.4.0" @@ -2075,6 +2258,7 @@ version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" dependencies = [ + "async-std", "async-trait", "base64", "chumsky", @@ -2594,6 +2778,7 @@ dependencies = [ "tower_governor", "tracing", "tracing-subscriber", + "url", "urlencoding", ] @@ -2687,6 +2872,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -2714,6 +2910,20 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.2", + "windows-sys 0.61.2", +] + [[package]] name = "portable-atomic" version = "1.13.0" @@ -3205,6 +3415,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.23.35" @@ -3666,7 +3889,7 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", @@ -4423,6 +4646,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index e811f1b..f791277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ jacquard-identity = "0.9.5" multibase = "0.9.2" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } urlencoding = "2.1" +url = "2.5.7" html-escape = "0.2.13" josekit = "0.10.3" dashmap = "6.1" diff --git a/src/helpers.rs b/src/helpers.rs index 1b54c2e..200014f 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -17,7 +17,7 @@ use jacquard_common::{ use jacquard_identity::{PublicResolver, resolver::IdentityResolver}; use josekit::jwe::alg::direct::DirectJweAlgorithm; use lettre::{ - AsyncTransport, Message, + Message, message::{MultiPart, SinglePart, header}, }; use rand::Rng; diff --git a/src/main.rs b/src/main.rs index 674ec1e..72eac77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use crate::oauth_provider::sign_in; use crate::xrpc::com_atproto_server::{ create_account, create_session, describe_server, get_session, update_email, }; +use anyhow::{Result, Context}; use axum::{ Router, body::Body, @@ -18,7 +19,7 @@ use handlebars::Handlebars; use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; use jacquard_common::types::did::Did; use jacquard_identity::{PublicResolver, resolver::PlcSource}; -use lettre::{AsyncSmtpTransport, Tokio1Executor}; +use lettre::{AsyncTransport, AsyncSmtpTransport, AsyncSendmailTransport, Message, Tokio1Executor}; use rand::Rng; use rust_embed::RustEmbed; use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode}; @@ -36,6 +37,7 @@ use tower_http::{ }; use tracing::log; use tracing_subscriber::{EnvFilter, fmt, prelude::*}; +use url::Url; mod auth; mod gate; @@ -151,13 +153,52 @@ pub struct AppState { account_pool: SqlitePool, pds_gatekeeper_pool: SqlitePool, reverse_proxy_client: HyperUtilClient, - mailer: AsyncSmtpTransport, + mailer: Arc, template_engine: Engine>, resolver: Arc, handle_cache: auth::HandleCache, app_config: AppConfig, } +pub enum Mailer { + Smtp(AsyncSmtpTransport), + Sendmail(AsyncSendmailTransport), +} + +impl Mailer { + pub async fn send(&self, msg: Message) -> Result<()> { + match self { + Mailer::Smtp(m) => { + m.send(msg).await.context("SMTP send failed")?; + Ok(()) + } + Mailer::Sendmail(m) => { + m.send(msg).await.context("sendmail send failed")?; + Ok(()) + } + } + } +} + +fn build_mailer_from_env() -> Result { + let raw = env::var("PDS_EMAIL_SMTP_URL") + .context("PDS_EMAIL_SMTP_URL is not set in your pds.env file")?; + + let url = Url::parse(&raw).context("PDS_EMAIL_SMTP_URL is not a valid URL")?; + + let use_sendmail = url.scheme() == "sendmail" + || url.query_pairs().any(|(k, v)| k == "sendmail" && v == "true"); + + if use_sendmail { + Ok(Mailer::Sendmail(AsyncSendmailTransport::::new())) + } else { + Ok(Mailer::Smtp( + AsyncSmtpTransport::::from_url(raw.as_str())? + .build(), + )) + } +} + async fn root_handler() -> impl axum::response::IntoResponse { let body = r" @@ -244,11 +285,8 @@ async fn main() -> Result<(), Box> { .build(HttpConnector::new()); //Emailer set up - let smtp_url = - env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file"); + let mailer = Arc::new(build_mailer_from_env()?); - let mailer: AsyncSmtpTransport = - AsyncSmtpTransport::::from_url(smtp_url.as_str())?.build(); //Email templates setup let mut hbs = Handlebars::new();