openauth_plugins/captcha/
mod.rs1mod error;
4mod ip;
5mod options;
6mod response;
7
8pub mod verify_handlers;
9
10pub use error::{CaptchaConfigError, CaptchaErrorCode};
11pub use options::{CaptchaOptions, CaptchaProvider, DEFAULT_ENDPOINTS};
12
13use std::sync::Arc;
14
15use openauth_core::plugin::{AuthPlugin, PluginErrorCode};
16use response::error_response;
17use verify_handlers::{verify_captcha, VerifyCaptchaInput};
18
19pub const UPSTREAM_PLUGIN_ID: &str = "captcha";
20
21pub fn captcha(options: CaptchaOptions) -> Result<AuthPlugin, CaptchaConfigError> {
23 options.validate()?;
24
25 let options = Arc::new(options.with_defaults());
26 let serialized_options = serde_json::to_value(options.as_ref())
27 .map_err(|error| CaptchaConfigError::SerializeOptions(error.to_string()))?;
28
29 let mut plugin = AuthPlugin::new(UPSTREAM_PLUGIN_ID)
30 .with_version(env!("CARGO_PKG_VERSION"))
31 .with_options(serialized_options)
32 .with_error_code(PluginErrorCode::new(
33 CaptchaErrorCode::VerificationFailed.as_str(),
34 CaptchaErrorCode::VerificationFailed.message(),
35 ))
36 .with_error_code(PluginErrorCode::new(
37 CaptchaErrorCode::MissingResponse.as_str(),
38 CaptchaErrorCode::MissingResponse.message(),
39 ))
40 .with_error_code(PluginErrorCode::new(
41 CaptchaErrorCode::UnknownError.as_str(),
42 CaptchaErrorCode::UnknownError.message(),
43 ));
44
45 for endpoint in options.endpoints.clone() {
46 let options = Arc::clone(&options);
47 plugin = plugin.with_async_middleware(endpoint, move |context, request| {
48 let options = Arc::clone(&options);
49 Box::pin(async move {
50 let Some(captcha_response) = request
51 .headers()
52 .get("x-captcha-response")
53 .and_then(|value| value.to_str().ok())
54 .map(str::trim)
55 .filter(|value| !value.is_empty())
56 .map(str::to_owned)
57 else {
58 return error_response(CaptchaErrorCode::MissingResponse).map(Some);
59 };
60
61 let input = VerifyCaptchaInput {
62 options: options.as_ref(),
63 captcha_response: &captcha_response,
64 remote_ip: ip::request_ip(context, request),
65 };
66
67 match verify_captcha(input).await {
68 Ok(true) => Ok(None),
69 Ok(false) => error_response(CaptchaErrorCode::VerificationFailed).map(Some),
70 Err(_) => error_response(CaptchaErrorCode::UnknownError).map(Some),
71 }
72 })
73 });
74 }
75
76 Ok(plugin)
77}