Microservice to bring 2FA to self hosted PDSes

eat: add opt-in per-request logging with JSON format support: eriko.eurosky.social #15

merged opened by baileytownsend.dev targeting main from feat/structured-request-logging
Labels

None yet.

Participants 1
AT URI
at://did:plc:rnpkyqnmsw4ipey6eotbdnnf/sh.tangled.repo.pull/3mg4yntcdwl22
+328 -16
Interdiff #0 #1
+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" ··· 290 440 ] 291 441 292 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", 453 + ] 454 + 455 + [[package]] 293 456 294 457 295 458 ··· 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" ··· 1019 1188 ] 1020 1189 1021 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", 1197 + "pin-project-lite", 1198 + ] 1199 + 1200 + [[package]] 1022 1201 name = "fastrand" 1023 1202 version = "2.3.0" 1024 1203 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 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" ··· 1465 1656 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1466 1657 1467 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" 1663 + 1664 + [[package]] 1468 1665 name = "hex" 1469 1666 1470 1667 ··· 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 "valuable", 2599 2816 ] ··· 2689 2906 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 2690 2907 2691 2908 [[package]] 2909 + name = "piper" 2910 + version = "0.2.4" 2911 + source = "registry+https://github.com/rust-lang/crates.io-index" 2912 + checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 2913 + dependencies = [ 2914 + "atomic-waker", 2915 + "fastrand", 2916 + "futures-io", 2917 + ] 2918 + 2919 + [[package]] 2692 2920 name = "pkcs1" 2693 2921 version = "0.7.5" 2694 2922 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2716 2944 checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 2717 2945 2718 2946 [[package]] 2947 + name = "polling" 2948 + version = "3.11.0" 2949 + source = "registry+https://github.com/rust-lang/crates.io-index" 2950 + checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" 2951 + dependencies = [ 2952 + "cfg-if", 2953 + "concurrent-queue", 2954 + "hermit-abi", 2955 + "pin-project-lite", 2956 + "rustix", 2957 + "windows-sys 0.61.2", 2958 + ] 2959 + 2960 + [[package]] 2719 2961 name = "portable-atomic" 2720 2962 2721 2963 ··· 3207 3449 ] 3208 3450 3209 3451 [[package]] 3452 + name = "rustix" 3453 + version = "1.1.3" 3454 + source = "registry+https://github.com/rust-lang/crates.io-index" 3455 + checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 3456 + dependencies = [ 3457 + "bitflags", 3458 + "errno", 3459 + "libc", 3460 + "linux-raw-sys", 3461 + "windows-sys 0.52.0", 3462 + ] 3463 + 3464 + [[package]] 3210 3465 name = "rustls" 3211 3466 version = "0.23.35" 3212 3467 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3667 3922 "crc", 3668 3923 "crossbeam-queue", 3669 3924 "either", 3670 - "event-listener", 3925 + "event-listener 5.4.1", 3671 3926 "futures-core", 3672 3927 "futures-intrusive", 3673 3928 "futures-io", ··· 4439 4694 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 4440 4695 4441 4696 [[package]] 4697 + name = "value-bag" 4698 + version = "1.12.0" 4699 + source = "registry+https://github.com/rust-lang/crates.io-index" 4700 + checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" 4701 + 4702 + [[package]] 4442 4703 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"
+5 -5
src/gate.rs
··· 38 38 redirect_url: Option<String>, 39 39 } 40 40 41 + /// GET /gate/signup - Display the captcha page 41 - /// GET /gate - Display the captcha page 42 42 pub async fn get_gate( 43 43 Query(params): Query<GateQuery>, 44 44 State(state): State<AppState>, ··· 75 75 .into_response() 76 76 } 77 77 78 + /// POST /gate/signup - Verify captcha and redirect 78 - /// POST /gate - Verify captcha and redirect 79 79 pub async fn post_gate( 80 80 State(state): State<AppState>, 81 81 Query(params): Query<GateQuery>, ··· 134 134 log::error!("Failed to verify hCaptcha: {}", e); 135 135 136 136 return Redirect::to(&format!( 137 + "/gate/signup?handle={}&state={}&error={}", 137 - "/gate?handle={}&state={}&error={}", 138 138 url_encode(&params.handle), 139 139 url_encode(&params.state), 140 140 url_encode("Verification failed. Please try again.") ··· 149 149 log::error!("Failed to parse hCaptcha response: {}", e); 150 150 151 151 return Redirect::to(&format!( 152 + "/gate/signup?handle={}&state={}&error={}", 152 - "/gate?handle={}&state={}&error={}", 153 153 url_encode(&params.handle), 154 154 url_encode(&params.state), 155 155 url_encode("Verification failed. Please try again.") ··· 165 165 captcha_result.error_codes 166 166 ); 167 167 return Redirect::to(&format!( 168 + "/gate/signup?handle={}&state={}&error={}", 168 - "/gate?handle={}&state={}&error={}", 169 169 url_encode(&params.handle), 170 170 url_encode(&params.state), 171 171 url_encode("Verification failed. Please try again.")
+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 + Message, 20 - AsyncTransport, 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 + }
+8 -7
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}; ··· 30 31 use tower_governor::{ 31 32 GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, 32 33 }; 34 + use tower_http::cors::AllowHeaders; 33 35 use tower_http::trace::{DefaultOnRequest, HttpMakeClassifier}; 34 36 use tower_http::{ 35 37 compression::CompressionLayer, ··· 42 44 mod auth; 43 45 mod gate; 44 46 pub mod helpers; 47 + pub mod mailer; 45 48 mod middleware; 46 49 mod oauth_provider; 47 50 mod xrpc; ··· 153 156 account_pool: SqlitePool, 154 157 pds_gatekeeper_pool: SqlitePool, 155 158 reverse_proxy_client: HyperUtilClient, 156 - mailer: AsyncSmtpTransport<Tokio1Executor>, 159 + mailer: Arc<Mailer>, 157 160 template_engine: Engine<Handlebars<'static>>, 158 161 resolver: Arc<PublicResolver>, 159 162 handle_cache: auth::HandleCache, ··· 216 219 let account_db_url = format!("{pds_root}/account.sqlite"); 217 220 218 221 let account_options = SqliteConnectOptions::new() 222 + .journal_mode(SqliteJournalMode::Wal) 219 223 .filename(account_db_url) 220 224 .busy_timeout(Duration::from_secs(5)); 221 225 ··· 247 251 .build(HttpConnector::new()); 248 252 249 253 //Emailer set up 250 - let smtp_url = 251 - env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file"); 254 + let mailer = Arc::new(build_mailer_from_env()?); 252 255 253 - let mailer: AsyncSmtpTransport<Tokio1Executor> = 254 - AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build(); 255 256 //Email templates setup 256 257 let mut hbs = Handlebars::new(); 257 258 ··· 355 356 let cors = CorsLayer::new() 356 357 .allow_origin(Any) 357 358 .allow_methods([Method::GET, Method::OPTIONS, Method::POST]) 358 - .allow_headers(Any); 359 + .allow_headers(AllowHeaders::mirror_request()); 359 360 360 361 let mut app = Router::new() 361 362 .route("/", get(root_handler))
+4 -1
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)?; 237 240 238 241 Ok(StatusCode::OK.into_response()) 239 242 }

History

2 rounds 0 comments
sign up or login to add to the discussion
2 commits
expand
feat: add opt-in per-request logging
light changes to logging changes
expand 0 comments
pull request successfully merged
2 commits
expand
feat: add opt-in per-request logging
light changes to logging changes
expand 0 comments