1use actix_web::HttpRequest;
9use actix_web::{web, HttpResponse, Responder};
10use libmcaptcha::pow::Work;
11use serde::{Deserialize, Serialize};
12
13use crate::errors::*;
14use crate::AppData;
15use crate::V1_API_ROUTES;
16
17#[derive(Clone, Debug, Deserialize, Serialize)]
18pub struct ValidationToken {
21 pub token: String,
22}
23
24#[derive(Clone, Debug, Deserialize, Serialize)]
25pub struct ApiWork {
26 pub string: String,
27 pub result: String,
28 pub nonce: u64,
29 pub key: String,
30 pub time: Option<u32>,
31 pub worker_type: Option<String>,
32}
33
34impl From<ApiWork> for Work {
35 fn from(value: ApiWork) -> Self {
36 Self {
37 string: value.string,
38 nonce: value.nonce,
39 result: value.result,
40 key: value.key,
41 }
42 }
43}
44
45#[my_codegen::post(path = "V1_API_ROUTES.pow.verify_pow()")]
50pub async fn verify_pow(
51 req: HttpRequest,
52 payload: web::Json<ApiWork>,
53 data: AppData,
54) -> ServiceResult<impl Responder> {
55 #[cfg(not(test))]
56 let ip = req.connection_info().peer_addr().unwrap().to_string();
57 #[cfg(test)]
62 let ip = "127.0.1.1".into();
63
64 let key = payload.key.clone();
65 let payload = payload.into_inner();
66 let worker_type = payload.worker_type.clone();
67 let time = payload.time;
68 let nonce = payload.nonce;
69 let (res, difficulty_factor) = data.captcha.verify_pow(payload.into(), ip).await?;
70 data.stats.record_solve(&data, &key).await?;
71 if let (Some(time), Some(worker_type)) = (time, worker_type) {
72 let analytics = db_core::CreatePerformanceAnalytics {
73 difficulty_factor,
74 time,
75 worker_type,
76 };
77 data.db.analysis_save(&key, &analytics).await?;
78 }
79 data.db
80 .update_max_nonce_for_level(&key, difficulty_factor, nonce as u32)
81 .await?;
82 let payload = ValidationToken { token: res };
83 Ok(HttpResponse::Ok().json(payload))
84}
85
86#[cfg(test)]
87pub mod tests {
88 use actix_web::http::StatusCode;
89 use actix_web::test;
90 use libmcaptcha::pow::PoWConfig;
91
92 use super::*;
93 use crate::api::v1::pow::get_config::GetConfigPayload;
94 use crate::tests::*;
95 use crate::*;
96
97 #[actix_rt::test]
98 async fn verify_pow_works_pg() {
99 let data = crate::tests::pg::get_data().await;
100 verify_pow_works(data).await;
101 }
102
103 #[actix_rt::test]
104 async fn verify_pow_works_maria() {
105 let data = crate::tests::maria::get_data().await;
106 verify_pow_works(data).await;
107 }
108
109 #[actix_rt::test]
110 async fn verify_analytics_pow_works_pg() {
111 let data = crate::tests::pg::get_data().await;
112 verify_analytics_pow_works(data).await;
113 }
114
115 #[actix_rt::test]
116 async fn verify_analytics_pow_works_maria() {
117 let data = crate::tests::maria::get_data().await;
118 verify_analytics_pow_works(data).await;
119 }
120
121 pub async fn verify_analytics_pow_works(data: ArcData) {
122 const NAME: &str = "powanalyticsuser";
123 const PASSWORD: &str = "testingpas";
124 const EMAIL: &str = "powanalyticsuser@a.com";
125 let data = &data;
126
127 delete_user(data, NAME).await;
128
129 register_and_signin(data, NAME, EMAIL, PASSWORD).await;
130 let (_, _signin_resp, token_key) = add_levels_util(data, NAME, PASSWORD).await;
131 let app = get_app!(data).await;
132
133 let get_config_payload = GetConfigPayload {
134 key: token_key.key.clone(),
135 };
136
137 let get_config_resp = test::call_service(
140 &app,
141 post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
142 .to_request(),
143 )
144 .await;
145 assert_eq!(get_config_resp.status(), StatusCode::OK);
146 let config: PoWConfig = test::read_body_json(get_config_resp).await;
147
148 let pow = mcaptcha_pow_sha256::ConfigBuilder::default()
149 .salt(config.salt)
150 .build()
151 .unwrap();
152 let work = pow
153 .prove_work(&config.string.clone(), config.difficulty_factor)
154 .unwrap();
155
156 let work = ApiWork {
157 string: config.string.clone(),
158 result: work.result,
159 nonce: work.nonce,
160 key: token_key.key.clone(),
161 time: Some(100),
162 worker_type: Some("wasm".into()),
163 };
164
165 let pow_verify_resp = test::call_service(
166 &app,
167 post_request!(&work, V1_API_ROUTES.pow.verify_pow).to_request(),
168 )
169 .await;
170 assert_eq!(pow_verify_resp.status(), StatusCode::OK);
171 let limit = 50;
172 let offset = 0;
173 let mut analytics = data
174 .db
175 .analytics_fetch(&token_key.key, limit, offset)
176 .await
177 .unwrap();
178 assert_eq!(analytics.len(), 1);
179 let a = analytics.pop().unwrap();
180 assert_eq!(a.time, work.time.unwrap());
181 assert_eq!(a.worker_type, work.worker_type.unwrap());
182 }
183
184 pub async fn verify_pow_works(data: ArcData) {
185 const NAME: &str = "powverifyusr";
186 const PASSWORD: &str = "testingpas";
187 const EMAIL: &str = "verifyuser@a.com";
188 let data = &data;
189
190 delete_user(data, NAME).await;
191
192 register_and_signin(data, NAME, EMAIL, PASSWORD).await;
193 let (_, _signin_resp, token_key) = add_levels_util(data, NAME, PASSWORD).await;
194 let app = get_app!(data).await;
195
196 let get_config_payload = GetConfigPayload {
197 key: token_key.key.clone(),
198 };
199
200 let get_config_resp = test::call_service(
203 &app,
204 post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
205 .to_request(),
206 )
207 .await;
208 assert_eq!(get_config_resp.status(), StatusCode::OK);
209 let config: PoWConfig = test::read_body_json(get_config_resp).await;
210
211 let pow = mcaptcha_pow_sha256::ConfigBuilder::default()
212 .salt(config.salt)
213 .build()
214 .unwrap();
215 let work = pow
216 .prove_work(&config.string.clone(), config.difficulty_factor)
217 .unwrap();
218
219 let work = Work {
220 string: config.string.clone(),
221 result: work.result,
222 nonce: work.nonce,
223 key: token_key.key.clone(),
224 };
225
226 let pow_verify_resp = test::call_service(
227 &app,
228 post_request!(&work, V1_API_ROUTES.pow.verify_pow).to_request(),
229 )
230 .await;
231 assert_eq!(pow_verify_resp.status(), StatusCode::OK);
232 assert!(data
233 .db
234 .analytics_fetch(&token_key.key, 50, 0)
235 .await
236 .unwrap()
237 .is_empty());
238
239 let string_not_found = test::call_service(
240 &app,
241 post_request!(&work, V1_API_ROUTES.pow.verify_pow).to_request(),
242 )
243 .await;
244 assert_eq!(string_not_found.status(), StatusCode::BAD_REQUEST);
245 let err: ErrorToResponse = test::read_body_json(string_not_found).await;
246 assert_eq!(err.error, "Challenge: not found");
247
248 }
260}