recaptcha_verify/
ent.rs

1use serde::{Deserialize, Serialize};
2use serde_json::json;
3use std::collections::HashMap;
4
5/// Error returned when ReCaptcha verification fails
6#[derive(Debug)]
7pub enum RecaptchaEntError {
8    InvalidReason(String),
9    UnknownReason,
10    ApiError(RecaptchaEntApiResponse),
11    HttpError(reqwest::Error),
12    DecodingError(serde_json::Error),
13    UnexpectedResponse(String, String, String),
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct RecaptchaEntApiResponse {
18    pub error: RecaptchaEntApiError,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct RecaptchaEntApiError {
23    pub code: u32,
24    pub message: String,
25    pub status: String,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct RecaptchaEntEvent {
31    pub token: String,
32    pub site_key: String,
33    pub user_agent: String,
34    pub user_ip_address: String,
35    pub expected_action: Option<String>,
36    #[serde(flatten)]
37    pub extras: HashMap<String, serde_json::Value>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct RecaptchaEntRiskAnalysis {
43    pub score: f32,
44    pub challenge: String,
45    pub reasons: Vec<String>,
46    #[serde(flatten)]
47    pub extras: HashMap<String, serde_json::Value>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct RecaptchaEntTokenProps {
53    pub valid: bool,
54    pub invalid_reason: Option<String>,
55    pub action: Option<String>,
56    #[serde(flatten)]
57    pub extras: HashMap<String, serde_json::Value>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct RecaptchaEntResult {
63    pub name: String,
64    pub event: RecaptchaEntEvent,
65    pub risk_analysis: RecaptchaEntRiskAnalysis,
66    pub token_properties: RecaptchaEntTokenProps,
67}
68
69pub async fn verify_enterprise(
70    project: &str,
71    api_key: &str,
72    site_key: &str,
73    token: &str,
74    action: Option<&str>,
75) -> Result<(), RecaptchaEntError> {
76    let result = verify_enterprise_detailed(project, api_key, site_key, token, action).await?;
77
78    if result.token_properties.valid {
79        Ok(())
80    } else if let Some(reason) = result.token_properties.invalid_reason {
81        Err(RecaptchaEntError::InvalidReason(reason))
82    } else {
83        Err(RecaptchaEntError::UnknownReason)
84    }
85}
86
87pub async fn verify_enterprise_detailed(
88    project: &str,
89    api_key: &str,
90    site_key: &str,
91    token: &str,
92    action: Option<&str>,
93) -> Result<RecaptchaEntResult, RecaptchaEntError> {
94    let request = json!({
95        "event": {
96            "token": &token,
97            "site_key": &site_key,
98            "expected_action": action.map(|a| a.to_string())
99        }
100    });
101
102    let client = reqwest::Client::new();
103    let response = client
104        .post(format!("https://recaptchaenterprise.googleapis.com/v1/projects/{project}/assessments?key={api_key}"))
105        .json(&request)
106        .send()
107        .await
108        .map_err(RecaptchaEntError::HttpError)?;
109
110    let response_body = response
111        .text()
112        .await
113        .map_err(RecaptchaEntError::HttpError)?;
114
115    match serde_json::from_str::<RecaptchaEntResult>(&response_body) {
116        Ok(result) => Ok(result),
117        Err(result_decoding) => {
118            match serde_json::from_str::<RecaptchaEntApiResponse>(&response_body)
119                .map_err(RecaptchaEntError::DecodingError)
120            {
121                Ok(err_response) => Err(RecaptchaEntError::ApiError(err_response)),
122                Err(err_decoding) => Err(RecaptchaEntError::UnexpectedResponse(
123                    response_body,
124                    format!("Error while parsing result response: {:?}", result_decoding),
125                    format!("Error while parsing error response: {:?}", err_decoding),
126                )),
127            }
128        }
129    }
130}