From 651d07a609bab68d70510d0c1eecddab1fcdf670 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Tue, 8 Jul 2025 18:58:05 +0100 Subject: [PATCH] feat: Add ReCaptcha registration flow --- Cargo.lock | 12 ++++++++++++ conduwuit-example.toml | 8 ++++++++ src/api/client/account.rs | 40 ++++++++++++++++++++++++++------------- src/core/config/mod.rs | 4 ++++ src/service/Cargo.toml | 1 + src/service/uiaa/mod.rs | 28 +++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d950e9da..6f711007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1076,6 +1076,7 @@ dependencies = [ "loole", "lru-cache", "rand 0.8.5", + "recaptcha-verify", "regex", "reqwest", "ruma", @@ -3751,6 +3752,17 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recaptcha-verify" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e3be7b2e46e24637ac96b0c9f70070f188652018573f36f4e511dcad09738a" +dependencies = [ + "reqwest", + "serde", + "serde_json", +] + [[package]] name = "redox_syscall" version = "0.5.13" diff --git a/conduwuit-example.toml b/conduwuit-example.toml index b7456237..8358f7c7 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -441,6 +441,14 @@ # #registration_token_file = +# This item is undocumented. Please contribute documentation for it. +# +#recaptcha_site_key = + +# This item is undocumented. Please contribute documentation for it. +# +#recaptcha_private_site_key = + # Controls whether encrypted rooms and events are allowed. # #allow_encryption = true diff --git a/src/api/client/account.rs b/src/api/client/account.rs index 12801e7d..624aa932 100644 --- a/src/api/client/account.rs +++ b/src/api/client/account.rs @@ -291,19 +291,33 @@ pub(crate) async fn register_route( } // UIAA - let mut uiaainfo; - let skip_auth = if services.globals.registration_token.is_some() { + let mut uiaainfo = UiaaInfo { + flows: Vec::new(), + completed: Vec::new(), + params: Box::default(), + session: None, + auth_error: None, + }; + let mut skip_auth = false; + if services.globals.registration_token.is_some() { // Registration token required - uiaainfo = UiaaInfo { - flows: vec![AuthFlow { - stages: vec![AuthType::RegistrationToken], - }], - completed: Vec::new(), - params: Box::default(), - session: None, - auth_error: None, - }; - body.appservice_info.is_some() + uiaainfo.flows.push(AuthFlow { + stages: vec![AuthType::RegistrationToken], + }); + skip_auth = body.appservice_info.is_some(); + } + if let Some(pubkey) = &services.config.recaptcha_site_key { + // ReCaptcha required + uiaainfo + .flows + .push(AuthFlow { stages: vec![AuthType::ReCaptcha] }); + uiaainfo.params = serde_json::value::to_raw_value(&serde_json::json!({ + "m.login.recaptcha": { + "public_key": pubkey, + }, + })) + .expect("Failed to serialize recaptcha params"); + skip_auth = body.appservice_info.is_some() || skip_auth; } else { // No registration token necessary, but clients must still go through the flow uiaainfo = UiaaInfo { @@ -313,7 +327,7 @@ pub(crate) async fn register_route( session: None, auth_error: None, }; - body.appservice_info.is_some() || is_guest + skip_auth = skip_auth || body.appservice_info.is_some() || is_guest; }; if !skip_auth { diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 282caf79..2307583c 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -556,6 +556,10 @@ pub struct Config { /// example: "/etc/continuwuity/.reg_token" pub registration_token_file: Option, + pub recaptcha_site_key: Option, + + pub recaptcha_private_site_key: Option, + /// Controls whether encrypted rooms and events are allowed. #[serde(default = "true_fn")] pub allow_encryption: bool, diff --git a/src/service/Cargo.toml b/src/service/Cargo.toml index 8b0d1405..fdebd1d7 100644 --- a/src/service/Cargo.toml +++ b/src/service/Cargo.toml @@ -111,6 +111,7 @@ webpage.workspace = true webpage.optional = true blurhash.workspace = true blurhash.optional = true +recaptcha-verify = { version = "0.1.5", default-features = false } [lints] workspace = true diff --git a/src/service/uiaa/mod.rs b/src/service/uiaa/mod.rs index 7803c736..e71889be 100644 --- a/src/service/uiaa/mod.rs +++ b/src/service/uiaa/mod.rs @@ -177,6 +177,34 @@ pub async fn try_auth( // Password was correct! Let's add it to `completed` uiaainfo.completed.push(AuthType::Password); }, + | AuthData::ReCaptcha(r) => { + if self.services.config.recaptcha_private_site_key.is_none() { + return Err!(Request(Forbidden("ReCaptcha is not configured."))); + } + match recaptcha_verify::verify( + self.services + .config + .recaptcha_private_site_key + .as_ref() + .unwrap(), + r.response.as_str(), + None, + ) + .await + { + | Ok(_) => { + uiaainfo.completed.push(AuthType::ReCaptcha); + }, + | Err(e) => { + error!("ReCaptcha verification failed: {e:?}"); + uiaainfo.auth_error = Some(ruma::api::client::error::StandardErrorBody { + kind: ErrorKind::forbidden(), + message: "ReCaptcha verification failed.".to_owned(), + }); + return Ok((false, uiaainfo)); + }, + } + }, | AuthData::RegistrationToken(t) => { let tokens = self.read_tokens().await?; if tokens.contains(t.token.trim()) {