1use serde::{Deserialize, Serialize};
2use serde_json::json;
3use std::collections::HashMap;
4
5#[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}