Hcaptcha
Build the request and verify
Build the request using the Hcaptcha builder.
Execute verify on the request once to execute.
Following a successful response the additional response in can be
requested from the Hcaptcha struct.
Examples
Token needs to be supplied by the client. This example will fail as a client-provided token is not used.
use hcaptcha::{HcaptchaClient, HcaptchaRequest};
# use itertools::Itertools;
# #[tokio::main]
# async fn main() -> Result<(), hcaptcha::HcaptchaError> {
# let secret = get_your_secret();
# let captcha = get_captcha();
# let remoteip = get_remoteip_address();
let request = HcaptchaRequest::new(&secret, captcha)?
.set_remoteip(&remoteip)?;
let client = HcaptchaClient::new();
let response = client.verify_client_response(request).await?;
let score = match &response.score() {
Some(v) => *v,
None => 0.0,
};
let score_reasons = match &response.score_reason() {
Some(v) => v.iter().join(", "),
None => "".to_owned(),
};
println!("\tScore: {:?}\n\tReasons: {:?}", score, score_reasons);
# Ok(())
# }
# fn get_your_secret() -> String {
# "0x123456789abcde0f123456789abcdef012345678".to_string()
# }
# use hcaptcha::HcaptchaCaptcha;
# use rand::distributions::Alphanumeric;
# use rand::{thread_rng, Rng};
# use std::iter;
# fn random_response() -> String {
# let mut rng = thread_rng();
# iter::repeat(())
# .map(|()| rng.sample(Alphanumeric))
# .map(char::from)
# .take(100)
# .collect()
# }
# fn get_captcha() -> HcaptchaCaptcha {
# HcaptchaCaptcha::new(&random_response())
# .unwrap()
# .set_remoteip(&fakeit::internet::ipv4_address())
# .unwrap()
# .set_sitekey(&fakeit::unique::uuid_v4())
# .unwrap()
# }
# fn get_remoteip_address() -> String {
# "192.168.0.17".to_string()
# }
# use uuid::Uuid;
# fn get_your_sitekey() -> Uuid {
# Uuid::new_v4()
# }
Lambda backend implemetation. See examples for more detail.
# use lambda_runtime::Error;
# use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
# use tracing_log::LogTracer;
# use tracing_subscriber::layer::SubscriberExt;
# use tracing_subscriber::{EnvFilter, Registry};
#
mod handler {
# mod error {
# use thiserror::Error;
# #[derive(Error, Debug)]
# pub enum LambdaContactError {
# #[error("{0}")]
# Hcaptcha(#[from] hcaptcha::HcaptchaError),
# #[error("{0}")]
# RusotoSes(#[from] rusoto_core::RusotoError<rusoto_ses::SendEmailError>),
# #[error("{0}")]
# RusotoSesTemplate(
# #[from] rusoto_core::RusotoError<rusoto_ses::SendTemplatedEmailError>,
# ),
# #[error("{0}")]
# Json(#[from] serde_json::Error),
# }
# }
#
# mod param {
# use super::error::LambdaContactError;
# use tracing::instrument;
# #[instrument(name = "get the secret key from parameter store")]
# pub async fn get_paramater(key: &str) -> Result<String, LambdaContactError> {
# // Extract the secret key from your parameter store
# Ok("0x123456789abcedf0123456789abcedf012345678".to_owned())
# }
# }
#
# mod record {
# use super::error::LambdaContactError;
# use super::send::ContactForm;
# use tracing::instrument;
#
# #[instrument(
# name = "Write record to database"
# skip(form)
# fields(email = %form.email)
# )]
# pub async fn write(form: &ContactForm) -> Result<(), LambdaContactError> {
# // Write the contact form data to dynamodb
# Ok(())
# }
# }
#
# mod send {
# use super::error::LambdaContactError;
# use rusoto_ses::{SendEmailResponse, SendTemplatedEmailResponse};
# use serde_derive::{Deserialize, Serialize};
# use tracing::instrument;
#
# #[derive(Deserialize, Serialize, Clone, Debug, Default)]
# pub struct ContactForm {
# #[serde(default)]
# pub name: String,
# #[serde(default)]
# pub phone: String,
# #[serde(default)]
# pub email: String,
# #[serde(default)]
# pub message: String,
# #[serde(default)]
# pub page: String,
# #[serde(default)]
# pub site: String,
# }
#
# #[instrument(name = "send notification to info mailbox", skip(_contact_form))]
# pub async fn notify_office(
# _contact_form: &ContactForm,
# ) -> Result<SendEmailResponse, LambdaContactError> {
# // Constuct email and send message to the office info mailbox
#
# let res = SendEmailResponse {
# message_id: "generated_message_id".to_owned(),
# };
#
# Ok(res)
# }
#
# #[instrument(name = "Send notification to the contact", skip(_contact_form))]
# pub async fn notify_contact(
# _contact_form: &ContactForm,
# ) -> Result<SendTemplatedEmailResponse, LambdaContactError> {
# // Construct and send email to the contact
# let res = SendTemplatedEmailResponse {
# message_id: "generated_message_id".to_owned(),
# };
#
# Ok(res)
# }
# }
# const HCAPTCHA_SECRET: &str = "/hcaptcha/secret";
#
# use hcaptcha::{HcaptchaCaptcha, HcaptchaClient, HcaptchaRequest};
# use lambda_runtime::{Context, Error};
# use send::ContactForm;
# use serde_derive::{Deserialize, Serialize};
# use tokio::join;
# use tracing::{debug, error};
#
# #[derive(Deserialize, Serialize, Clone, Debug, Default)]
# pub struct CustomEvent {
# body: Option<String>,
# }
#
# #[derive(Deserialize, Serialize, Clone, Default)]
# pub struct Recaptcha {
# #[serde(rename = "reCaptchaResponse")]
# re_captcha_response: String,
# }
#
# #[derive(Serialize, Clone, Debug, PartialEq)]
# pub struct CustomOutput {
# #[serde(rename = "isBase64Encoded")]
# is_base64_encoded: bool,
# #[serde(rename = "statusCode")]
# status_code: u16,
# body: String,
# }
#
# impl CustomOutput {
# fn new(status_code: u16, body: String) -> CustomOutput {
# CustomOutput {
# is_base64_encoded: false,
# status_code,
# body,
# }
# }
# }
#
#
pub async fn my_handler(e: CustomEvent, _c: Context) -> Result<CustomOutput, Error> {
debug!("The event logged is: {:?}", e);
let body_str = e.body.unwrap_or_else(|| "".to_owned());
let captcha: HcaptchaCaptcha = serde_json::from_str(&body_str)?;
let hcaptcha_secret = param::get_paramater(HCAPTCHA_SECRET).await?;
let request = HcaptchaRequest::new(&hcaptcha_secret,
captcha)?;
let client = HcaptchaClient::new();
let _response = client.verify_client_response(request).await?;
let contact_form: ContactForm = serde_json::from_str(&body_str)?;
let notify_office_fut = send::notify_office(&contact_form);
let notify_contact_fut = send::notify_contact(&contact_form);
let write_fut = record::write(&contact_form);
let (notify_office, notify_contact, write) =
join!(notify_office_fut, notify_contact_fut, write_fut);
if let Err(e) = notify_contact {
error!("Notification to the contact not sent: {}", e);
return Err("Notifcation not sent".into());
}
if let Err(e) = notify_office {
error!("Notification to the office not sent: {}", e);
return Err("Info not sent to office".into());
}
if let Err(e) = write {
error!("Contact information not written to database: {}", e);
}
Ok(CustomOutput::new(
200,
format!("{}, thank you for your contact request.", contact_form.name),
))
}
}
#[tokio::main]
async fn main() -> Result<(), Error> {
# LogTracer::init()?;
#
# let app_name = concat!(env!("CARGO_PKG_NAME"), "-", env!("CARGO_PKG_VERSION")).to_string();
# let (non_blocking_writer, _guard) = tracing_appender::non_blocking(std::io::stdout());
# let bunyan_formatting_layer = BunyanFormattingLayer::new(app_name, non_blocking_writer);
# let subscriber = Registry::default()
# .with(EnvFilter::new(
# std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".to_owned()),
# ))
# .with(JsonStorageLayer)
# .with(bunyan_formatting_layer);
# tracing::subscriber::set_global_default(subscriber)?;
lambda_runtime::run(lambda_runtime::handler_fn(handler::my_handler)).await?;
Ok(())
}
Feature Flags
The default library includes extended validation for the secret field. Disable this validation by setting default-features = false.
[]
= { = "2.0.0", = false }
The following feature flags are available:
enterprise- Enable methods to access enterprise service fields in theHcaptchaResponseext- Enables extended validation of secrettrace- Enables tracing instrumentation on all functions. Traces are logged at the debug level. The value of the secret is not logged.
Rust Version
This version of hcaptcha requires Rust v1.46 or later.