Microservice to bring 2FA to self hosted PDSes

Add support for sending emails with local Sendmail: mackuba.eu #14

merged opened by baileytownsend.dev targeting main from thank-you-kuba

Original PR #6 from @mackuba.eu with some changes. Thanks for the PR

Labels

None yet.

Participants 1
AT URI
at://did:plc:rnpkyqnmsw4ipey6eotbdnnf/sh.tangled.repo.pull/3mg4qigqq4w22
+318 -13
Diff #0
+262 -1
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" ··· 98 121 ] 99 122 100 123 [[package]] 124 + name = "async-executor" 125 + version = "1.13.3" 126 + source = "registry+https://github.com/rust-lang/crates.io-index" 127 + checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" 128 + dependencies = [ 129 + "async-task", 130 + "concurrent-queue", 131 + "fastrand", 132 + "futures-lite", 133 + "pin-project-lite", 134 + "slab", 135 + ] 136 + 137 + [[package]] 138 + name = "async-global-executor" 139 + version = "2.4.1" 140 + source = "registry+https://github.com/rust-lang/crates.io-index" 141 + checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" 142 + dependencies = [ 143 + "async-channel 2.5.0", 144 + "async-executor", 145 + "async-io", 146 + "async-lock", 147 + "blocking", 148 + "futures-lite", 149 + "once_cell", 150 + ] 151 + 152 + [[package]] 153 + name = "async-io" 154 + version = "2.6.0" 155 + source = "registry+https://github.com/rust-lang/crates.io-index" 156 + checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" 157 + dependencies = [ 158 + "autocfg", 159 + "cfg-if", 160 + "concurrent-queue", 161 + "futures-io", 162 + "futures-lite", 163 + "parking", 164 + "polling", 165 + "rustix", 166 + "slab", 167 + "windows-sys 0.61.2", 168 + ] 169 + 170 + [[package]] 171 + name = "async-lock" 172 + version = "3.4.2" 173 + source = "registry+https://github.com/rust-lang/crates.io-index" 174 + checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" 175 + dependencies = [ 176 + "event-listener 5.4.1", 177 + "event-listener-strategy", 178 + "pin-project-lite", 179 + ] 180 + 181 + [[package]] 182 + name = "async-process" 183 + version = "2.5.0" 184 + source = "registry+https://github.com/rust-lang/crates.io-index" 185 + checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" 186 + dependencies = [ 187 + "async-channel 2.5.0", 188 + "async-io", 189 + "async-lock", 190 + "async-signal", 191 + "async-task", 192 + "blocking", 193 + "cfg-if", 194 + "event-listener 5.4.1", 195 + "futures-lite", 196 + "rustix", 197 + ] 198 + 199 + [[package]] 200 + name = "async-signal" 201 + version = "0.2.13" 202 + source = "registry+https://github.com/rust-lang/crates.io-index" 203 + checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" 204 + dependencies = [ 205 + "async-io", 206 + "async-lock", 207 + "atomic-waker", 208 + "cfg-if", 209 + "futures-core", 210 + "futures-io", 211 + "rustix", 212 + "signal-hook-registry", 213 + "slab", 214 + "windows-sys 0.61.2", 215 + ] 216 + 217 + [[package]] 218 + name = "async-std" 219 + version = "1.13.2" 220 + source = "registry+https://github.com/rust-lang/crates.io-index" 221 + checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" 222 + dependencies = [ 223 + "async-channel 1.9.0", 224 + "async-global-executor", 225 + "async-io", 226 + "async-lock", 227 + "async-process", 228 + "crossbeam-utils", 229 + "futures-channel", 230 + "futures-core", 231 + "futures-io", 232 + "futures-lite", 233 + "gloo-timers", 234 + "kv-log-macro", 235 + "log", 236 + "memchr", 237 + "once_cell", 238 + "pin-project-lite", 239 + "pin-utils", 240 + "slab", 241 + "wasm-bindgen-futures", 242 + ] 243 + 244 + [[package]] 245 + name = "async-task" 246 + version = "4.7.1" 247 + source = "registry+https://github.com/rust-lang/crates.io-index" 248 + checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 249 + 250 + [[package]] 101 251 name = "async-trait" 102 252 version = "0.1.89" 103 253 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 287 437 checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 288 438 dependencies = [ 289 439 "generic-array", 440 + ] 441 + 442 + [[package]] 443 + name = "blocking" 444 + version = "1.6.2" 445 + source = "registry+https://github.com/rust-lang/crates.io-index" 446 + checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" 447 + dependencies = [ 448 + "async-channel 2.5.0", 449 + "async-task", 450 + "futures-io", 451 + "futures-lite", 452 + "piper", 290 453 ] 291 454 292 455 [[package]] ··· 1009 1172 1010 1173 [[package]] 1011 1174 name = "event-listener" 1175 + version = "2.5.3" 1176 + source = "registry+https://github.com/rust-lang/crates.io-index" 1177 + checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 1178 + 1179 + [[package]] 1180 + name = "event-listener" 1012 1181 version = "5.4.1" 1013 1182 source = "registry+https://github.com/rust-lang/crates.io-index" 1014 1183 checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" 1015 1184 dependencies = [ 1016 1185 "concurrent-queue", 1017 1186 "parking", 1187 + "pin-project-lite", 1188 + ] 1189 + 1190 + [[package]] 1191 + name = "event-listener-strategy" 1192 + version = "0.5.4" 1193 + source = "registry+https://github.com/rust-lang/crates.io-index" 1194 + checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 1195 + dependencies = [ 1196 + "event-listener 5.4.1", 1018 1197 "pin-project-lite", 1019 1198 ] 1020 1199 ··· 1302 1481 ] 1303 1482 1304 1483 [[package]] 1484 + name = "gloo-timers" 1485 + version = "0.3.0" 1486 + source = "registry+https://github.com/rust-lang/crates.io-index" 1487 + checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 1488 + dependencies = [ 1489 + "futures-channel", 1490 + "futures-core", 1491 + "js-sys", 1492 + "wasm-bindgen", 1493 + ] 1494 + 1495 + [[package]] 1305 1496 name = "governor" 1306 1497 version = "0.10.4" 1307 1498 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1463 1654 version = "0.5.0" 1464 1655 source = "registry+https://github.com/rust-lang/crates.io-index" 1465 1656 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1657 + 1658 + [[package]] 1659 + name = "hermit-abi" 1660 + version = "0.5.2" 1661 + source = "registry+https://github.com/rust-lang/crates.io-index" 1662 + checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 1466 1663 1467 1664 [[package]] 1468 1665 name = "hex" ··· 2050 2247 ] 2051 2248 2052 2249 [[package]] 2250 + name = "kv-log-macro" 2251 + version = "1.0.7" 2252 + source = "registry+https://github.com/rust-lang/crates.io-index" 2253 + checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 2254 + dependencies = [ 2255 + "log", 2256 + ] 2257 + 2258 + [[package]] 2053 2259 name = "langtag" 2054 2260 version = "0.4.0" 2055 2261 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2075 2281 source = "registry+https://github.com/rust-lang/crates.io-index" 2076 2282 checksum = "9e13e10e8818f8b2a60f52cb127041d388b89f3a96a62be9ceaffa22262fef7f" 2077 2283 dependencies = [ 2284 + "async-std", 2078 2285 "async-trait", 2079 2286 "base64", 2080 2287 "chumsky", ··· 2132 2339 ] 2133 2340 2134 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]] 2135 2348 name = "litemap" 2136 2349 version = "0.8.1" 2137 2350 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2151 2364 version = "0.4.29" 2152 2365 source = "registry+https://github.com/rust-lang/crates.io-index" 2153 2366 checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 2367 + dependencies = [ 2368 + "value-bag", 2369 + ] 2154 2370 2155 2371 [[package]] 2156 2372 name = "loom" ··· 2594 2810 "tower_governor", 2595 2811 "tracing", 2596 2812 "tracing-subscriber", 2813 + "url", 2597 2814 "urlencoding", 2598 2815 ] 2599 2816 ··· 2688 2905 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 2689 2906 2690 2907 [[package]] 2908 + name = "piper" 2909 + version = "0.2.4" 2910 + source = "registry+https://github.com/rust-lang/crates.io-index" 2911 + checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 2912 + dependencies = [ 2913 + "atomic-waker", 2914 + "fastrand", 2915 + "futures-io", 2916 + ] 2917 + 2918 + [[package]] 2691 2919 name = "pkcs1" 2692 2920 version = "0.7.5" 2693 2921 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2715 2943 checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 2716 2944 2717 2945 [[package]] 2946 + name = "polling" 2947 + version = "3.11.0" 2948 + source = "registry+https://github.com/rust-lang/crates.io-index" 2949 + checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" 2950 + dependencies = [ 2951 + "cfg-if", 2952 + "concurrent-queue", 2953 + "hermit-abi", 2954 + "pin-project-lite", 2955 + "rustix", 2956 + "windows-sys 0.61.2", 2957 + ] 2958 + 2959 + [[package]] 2718 2960 name = "portable-atomic" 2719 2961 version = "1.13.0" 2720 2962 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3206 3448 ] 3207 3449 3208 3450 [[package]] 3451 + name = "rustix" 3452 + version = "1.1.3" 3453 + source = "registry+https://github.com/rust-lang/crates.io-index" 3454 + checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 3455 + dependencies = [ 3456 + "bitflags", 3457 + "errno", 3458 + "libc", 3459 + "linux-raw-sys", 3460 + "windows-sys 0.52.0", 3461 + ] 3462 + 3463 + [[package]] 3209 3464 name = "rustls" 3210 3465 version = "0.23.35" 3211 3466 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3666 3921 "crc", 3667 3922 "crossbeam-queue", 3668 3923 "either", 3669 - "event-listener", 3924 + "event-listener 5.4.1", 3670 3925 "futures-core", 3671 3926 "futures-intrusive", 3672 3927 "futures-io", ··· 4422 4677 version = "0.1.1" 4423 4678 source = "registry+https://github.com/rust-lang/crates.io-index" 4424 4679 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 4680 + 4681 + [[package]] 4682 + name = "value-bag" 4683 + version = "1.12.0" 4684 + source = "registry+https://github.com/rust-lang/crates.io-index" 4685 + checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" 4425 4686 4426 4687 [[package]] 4427 4688 name = "vcpkg"
+2 -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"] } ··· 35 35 multibase = "0.9.2" 36 36 reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } 37 37 urlencoding = "2.1" 38 + url = "2.5.7" 38 39 html-escape = "0.2.13" 39 40 josekit = "0.10.3" 40 41 dashmap = "6.1"
+1 -1
src/helpers.rs
··· 17 17 use jacquard_identity::{PublicResolver, resolver::IdentityResolver}; 18 18 use josekit::jwe::alg::direct::DirectJweAlgorithm; 19 19 use lettre::{ 20 - AsyncTransport, Message, 20 + Message, 21 21 message::{MultiPart, SinglePart, header}, 22 22 }; 23 23 use rand::Rng;
+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 + }
+6 -6
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 }; 8 + use anyhow::Result; 7 9 use axum::{ 8 10 Router, 9 11 body::Body, ··· 18 20 use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; 19 21 use jacquard_common::types::did::Did; 20 22 use jacquard_identity::{PublicResolver, resolver::PlcSource}; 21 - use lettre::{AsyncSmtpTransport, Tokio1Executor}; 22 23 use rand::Rng; 23 24 use rust_embed::RustEmbed; 24 25 use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode}; ··· 40 41 mod auth; 41 42 mod gate; 42 43 pub mod helpers; 44 + pub mod mailer; 43 45 mod middleware; 44 46 mod oauth_provider; 45 47 mod xrpc; ··· 151 153 account_pool: SqlitePool, 152 154 pds_gatekeeper_pool: SqlitePool, 153 155 reverse_proxy_client: HyperUtilClient, 154 - mailer: AsyncSmtpTransport<Tokio1Executor>, 156 + mailer: Arc<Mailer>, 155 157 template_engine: Engine<Handlebars<'static>>, 156 158 resolver: Arc<PublicResolver>, 157 159 handle_cache: auth::HandleCache, ··· 213 215 let account_db_url = format!("{pds_root}/account.sqlite"); 214 216 215 217 let account_options = SqliteConnectOptions::new() 218 + .journal_mode(SqliteJournalMode::Wal) 216 219 .filename(account_db_url) 217 220 .busy_timeout(Duration::from_secs(5)); 218 221 ··· 244 247 .build(HttpConnector::new()); 245 248 246 249 //Emailer set up 247 - let smtp_url = 248 - env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file"); 250 + let mailer = Arc::new(build_mailer_from_env()?); 249 251 250 - let mailer: AsyncSmtpTransport<Tokio1Executor> = 251 - AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build(); 252 252 //Email templates setup 253 253 let mut hbs = Handlebars::new(); 254 254
+1 -4
src/xrpc/com_atproto_server.rs
··· 233 233 .bind(&did_row.0) 234 234 .execute(&state.pds_gatekeeper_pool) 235 235 .await 236 - .map_err(|err| { 237 - log::error!("Error enabling 2FA: {err}"); 238 - StatusCode::BAD_REQUEST 239 - })?; 236 + .map_err(|_| StatusCode::BAD_REQUEST)?; 240 237 241 238 Ok(StatusCode::OK.into_response()) 242 239 }

History

1 round 0 comments
sign up or login to add to the discussion
2 commits
expand
added support for sending emails with sendmail
split up some code
expand 0 comments
pull request successfully merged