Microservice to bring 2FA to self hosted PDSes

split up some code

authored by baileytownsend.dev and committed by tangled.org 04f863e2 500ac33c

+93 -53
+42 -10
Cargo.lock
··· 85 85 ] 86 86 87 87 [[package]] 88 + name = "async-channel" 89 + version = "1.9.0" 90 + source = "registry+https://github.com/rust-lang/crates.io-index" 91 + checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 92 + dependencies = [ 93 + "concurrent-queue", 94 + "event-listener 2.5.3", 95 + "futures-core", 96 + ] 97 + 98 + [[package]] 99 + name = "async-channel" 100 + version = "2.5.0" 101 + source = "registry+https://github.com/rust-lang/crates.io-index" 102 + checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" 103 + dependencies = [ 104 + "concurrent-queue", 105 + "event-listener-strategy", 106 + "futures-core", 107 + "pin-project-lite", 108 + ] 109 + 110 + [[package]] 88 111 name = "async-compression" 89 112 version = "0.4.36" 90 113 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 139 162 "futures-lite", 140 163 "parking", 141 164 "polling", 142 - "rustix 1.1.2", 165 + "rustix", 143 166 "slab", 144 167 "windows-sys 0.61.2", 145 168 ] 146 169 147 170 [[package]] 148 171 name = "async-lock" 149 - version = "3.4.1" 172 + version = "3.4.2" 150 173 source = "registry+https://github.com/rust-lang/crates.io-index" 151 - checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" 174 + checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" 152 175 dependencies = [ 153 176 "event-listener 5.4.1", 154 177 "event-listener-strategy", ··· 170 193 "cfg-if", 171 194 "event-listener 5.4.1", 172 195 "futures-lite", 173 - "rustix 1.1.2", 196 + "rustix", 174 197 ] 175 198 176 199 [[package]] ··· 185 208 "cfg-if", 186 209 "futures-core", 187 210 "futures-io", 188 - "rustix 1.1.2", 211 + "rustix", 189 212 "signal-hook-registry", 190 213 "slab", 191 214 "windows-sys 0.61.2", ··· 2316 2339 ] 2317 2340 2318 2341 [[package]] 2342 + name = "linux-raw-sys" 2343 + version = "0.11.0" 2344 + source = "registry+https://github.com/rust-lang/crates.io-index" 2345 + checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 2346 + 2347 + [[package]] 2319 2348 name = "litemap" 2320 2349 version = "0.8.1" 2321 2350 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2335 2364 version = "0.4.29" 2336 2365 source = "registry+https://github.com/rust-lang/crates.io-index" 2337 2366 checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 2367 + dependencies = [ 2368 + "value-bag", 2369 + ] 2338 2370 2339 2371 [[package]] 2340 2372 name = "loom" ··· 2920 2952 "concurrent-queue", 2921 2953 "hermit-abi", 2922 2954 "pin-project-lite", 2923 - "rustix 1.1.2", 2955 + "rustix", 2924 2956 "windows-sys 0.61.2", 2925 2957 ] 2926 2958 ··· 3417 3449 3418 3450 [[package]] 3419 3451 name = "rustix" 3420 - version = "1.1.2" 3452 + version = "1.1.3" 3421 3453 source = "registry+https://github.com/rust-lang/crates.io-index" 3422 - checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 3454 + checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 3423 3455 dependencies = [ 3424 3456 "bitflags", 3425 3457 "errno", 3426 3458 "libc", 3427 - "linux-raw-sys 0.11.0", 3428 - "windows-sys 0.59.0", 3459 + "linux-raw-sys", 3460 + "windows-sys 0.52.0", 3429 3461 ] 3430 3462 3431 3463 [[package]]
+1 -1
Cargo.toml
··· 22 22 #Leaveing these two cause I think it is needed by the email crate for ssl 23 23 aws-lc-rs = "1.15.2" 24 24 rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] } 25 - lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "tokio1", "tokio1-rustls"] } 25 + lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "sendmail-transport", "tokio1", "tokio1-rustls"] } 26 26 handlebars = { version = "6.4.0", features = ["rust-embed"] } 27 27 rust-embed = "8.9.0" 28 28 axum-template = { version = "3.0.0", features = ["handlebars"] }
+46
src/mailer.rs
··· 1 + use anyhow::Context; 2 + use lettre::{AsyncSendmailTransport, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor}; 3 + use std::env; 4 + use url::Url; 5 + 6 + pub enum Mailer { 7 + Smtp(AsyncSmtpTransport<Tokio1Executor>), 8 + Sendmail(AsyncSendmailTransport<Tokio1Executor>), 9 + } 10 + 11 + impl Mailer { 12 + pub async fn send(&self, msg: Message) -> anyhow::Result<()> { 13 + match self { 14 + Mailer::Smtp(m) => { 15 + m.send(msg).await.context("SMTP send failed")?; 16 + Ok(()) 17 + } 18 + Mailer::Sendmail(m) => { 19 + m.send(msg).await.context("sendmail send failed")?; 20 + Ok(()) 21 + } 22 + } 23 + } 24 + } 25 + 26 + pub fn build_mailer_from_env() -> anyhow::Result<Mailer> { 27 + let raw = env::var("PDS_EMAIL_SMTP_URL") 28 + .context("PDS_EMAIL_SMTP_URL is not set in your pds.env file")?; 29 + 30 + let url = Url::parse(&raw).context("PDS_EMAIL_SMTP_URL is not a valid URL")?; 31 + 32 + let use_sendmail = url.scheme() == "sendmail" 33 + || url 34 + .query_pairs() 35 + .any(|(k, v)| k == "sendmail" && v == "true"); 36 + 37 + if use_sendmail { 38 + Ok(Mailer::Sendmail( 39 + AsyncSendmailTransport::<Tokio1Executor>::new(), 40 + )) 41 + } else { 42 + Ok(Mailer::Smtp( 43 + AsyncSmtpTransport::<Tokio1Executor>::from_url(raw.as_str())?.build(), 44 + )) 45 + } 46 + }
+4 -42
src/main.rs
··· 1 1 #![warn(clippy::unwrap_used)] 2 2 use crate::gate::{get_gate, post_gate}; 3 + use crate::mailer::{Mailer, build_mailer_from_env}; 3 4 use crate::oauth_provider::sign_in; 4 5 use crate::xrpc::com_atproto_server::{ 5 6 create_account, create_session, describe_server, get_session, update_email, 6 7 }; 7 - use anyhow::{Result, Context}; 8 + use anyhow::Result; 8 9 use axum::{ 9 10 Router, 10 11 body::Body, ··· 19 20 use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; 20 21 use jacquard_common::types::did::Did; 21 22 use jacquard_identity::{PublicResolver, resolver::PlcSource}; 22 - use lettre::{AsyncTransport, AsyncSmtpTransport, AsyncSendmailTransport, Message, Tokio1Executor}; 23 23 use rand::Rng; 24 24 use rust_embed::RustEmbed; 25 25 use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode}; ··· 37 37 }; 38 38 use tracing::log; 39 39 use tracing_subscriber::{EnvFilter, fmt, prelude::*}; 40 - use url::Url; 41 40 42 41 mod auth; 43 42 mod gate; 44 43 pub mod helpers; 44 + pub mod mailer; 45 45 mod middleware; 46 46 mod oauth_provider; 47 47 mod xrpc; ··· 160 160 app_config: AppConfig, 161 161 } 162 162 163 - pub enum Mailer { 164 - Smtp(AsyncSmtpTransport<Tokio1Executor>), 165 - Sendmail(AsyncSendmailTransport<Tokio1Executor>), 166 - } 167 - 168 - impl Mailer { 169 - pub async fn send(&self, msg: Message) -> Result<()> { 170 - match self { 171 - Mailer::Smtp(m) => { 172 - m.send(msg).await.context("SMTP send failed")?; 173 - Ok(()) 174 - } 175 - Mailer::Sendmail(m) => { 176 - m.send(msg).await.context("sendmail send failed")?; 177 - Ok(()) 178 - } 179 - } 180 - } 181 - } 182 - 183 - fn build_mailer_from_env() -> Result<Mailer> { 184 - let raw = env::var("PDS_EMAIL_SMTP_URL") 185 - .context("PDS_EMAIL_SMTP_URL is not set in your pds.env file")?; 186 - 187 - let url = Url::parse(&raw).context("PDS_EMAIL_SMTP_URL is not a valid URL")?; 188 - 189 - let use_sendmail = url.scheme() == "sendmail" 190 - || url.query_pairs().any(|(k, v)| k == "sendmail" && v == "true"); 191 - 192 - if use_sendmail { 193 - Ok(Mailer::Sendmail(AsyncSendmailTransport::<Tokio1Executor>::new())) 194 - } else { 195 - Ok(Mailer::Smtp( 196 - AsyncSmtpTransport::<Tokio1Executor>::from_url(raw.as_str())? 197 - .build(), 198 - )) 199 - } 200 - } 201 - 202 163 async fn root_handler() -> impl axum::response::IntoResponse { 203 164 let body = r" 204 165 ··· 254 215 let account_db_url = format!("{pds_root}/account.sqlite"); 255 216 256 217 let account_options = SqliteConnectOptions::new() 218 + .journal_mode(SqliteJournalMode::Wal) 257 219 .filename(account_db_url) 258 220 .busy_timeout(Duration::from_secs(5)); 259 221