mcaptcha/api/v1/pow/
get_config.rs1use actix_web::{web, HttpResponse, Responder};
8use libmcaptcha::pow::PoWConfig;
9use libmcaptcha::{
10 defense::LevelBuilder, master::messages::AddSiteBuilder, DefenseBuilder,
11 MCaptchaBuilder,
12};
13use serde::{Deserialize, Serialize};
14
15use crate::errors::*;
16use crate::AppData;
18use crate::V1_API_ROUTES;
19
20#[derive(Clone, Debug, Deserialize, Serialize)]
21pub struct GetConfigPayload {
22 pub key: String,
23}
24
25#[derive(Clone, Serialize, Deserialize, Debug)]
26pub struct ApiPoWConfig {
27 pub string: String,
28 pub difficulty_factor: u32,
29 pub salt: String,
30 pub max_recorded_nonce: u32,
31}
32
33#[my_codegen::post(path = "V1_API_ROUTES.pow.get_config()")]
35pub async fn get_config(
36 payload: web::Json<GetConfigPayload>,
37 data: AppData,
38) -> ServiceResult<impl Responder> {
39 if !data.db.captcha_exists(None, &payload.key).await? {
41 return Err(ServiceError::TokenNotFound);
42 }
43 let payload = payload.into_inner();
44
45 let config: ServiceResult<PoWConfig> =
46 match data.captcha.get_pow(payload.key.clone()).await {
47 Ok(Some(config)) => Ok(config),
48 Ok(None) => {
49 init_mcaptcha(&data, &payload.key).await?;
50 let config = data
51 .captcha
52 .get_pow(payload.key.clone())
53 .await
54 .expect("mcaptcha should be initialized and ready to go");
55 Ok(config.unwrap())
56 }
57 Err(e) => Err(e.into()),
58 };
59 let config = config?;
60 let max_nonce = data
61 .db
62 .get_max_nonce_for_level(&payload.key, config.difficulty_factor)
63 .await?;
64 data.stats.record_fetch(&data, &payload.key).await?;
65
66 let config = ApiPoWConfig {
67 string: config.string,
68 difficulty_factor: config.difficulty_factor,
69 salt: config.salt,
70 max_recorded_nonce: max_nonce,
71 };
72 Ok(HttpResponse::Ok().json(config))
73}
74pub async fn init_mcaptcha(data: &AppData, key: &str) -> ServiceResult<()> {
79 println!("Initializing captcha");
80 let levels = data.db.get_captcha_levels(None, key).await?;
82 let duration = data.db.get_captcha_cooldown(key).await?;
83
84 let mut defense = DefenseBuilder::default();
86
87 for level in levels.iter() {
88 let level = LevelBuilder::default()
89 .visitor_threshold(level.visitor_threshold)
90 .difficulty_factor(level.difficulty_factor)
91 .unwrap()
92 .build()
93 .unwrap();
94 defense.add_level(level).unwrap();
95 }
96
97 let defense = defense.build()?;
98 println!("{:?}", defense);
99
100 let mcaptcha = MCaptchaBuilder::default()
102 .defense(defense)
103 .duration(duration as u64)
105 .build()
107 .unwrap();
108
109 let msg = AddSiteBuilder::default()
111 .id(key.into())
112 .mcaptcha(mcaptcha)
113 .build()
114 .unwrap();
115
116 data.captcha.add_site(msg).await?;
117
118 Ok(())
119}
120
121#[cfg(test)]
122pub mod tests {
123 use crate::*;
124 use libmcaptcha::pow::PoWConfig;
125
126 #[actix_rt::test]
127 async fn get_pow_config_works_pg() {
128 let data = crate::tests::pg::get_data().await;
129 get_pow_config_works(data).await;
130 }
131
132 #[actix_rt::test]
133 async fn get_pow_config_works_maria() {
134 let data = crate::tests::maria::get_data().await;
135 get_pow_config_works(data).await;
136 }
137
138 pub async fn get_pow_config_works(data: ArcData) {
139 use super::*;
140 use crate::tests::*;
141 use crate::*;
142 use actix_web::test;
143
144 const NAME: &str = "powusrworks";
145 const PASSWORD: &str = "testingpas";
146 const EMAIL: &str = "randomuser@a.com";
147
148 let data = &data;
149
150 delete_user(data, NAME).await;
151
152 register_and_signin(data, NAME, EMAIL, PASSWORD).await;
153 let (_, _signin_resp, token_key) = add_levels_util(data, NAME, PASSWORD).await;
154 let app = get_app!(data).await;
155
156 let get_config_payload = GetConfigPayload {
157 key: token_key.key.clone(),
158 };
159
160 let url = V1_API_ROUTES.pow.get_config;
163 println!("{}", &url);
164 let get_config_resp = test::call_service(
165 &app,
166 post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
167 .to_request(),
168 )
169 .await;
170 assert_eq!(get_config_resp.status(), StatusCode::OK);
171 let config: PoWConfig = test::read_body_json(get_config_resp).await;
172 assert_eq!(config.difficulty_factor, L1.difficulty_factor);
173 }
174
175 #[actix_rt::test]
176 async fn pow_difficulty_factor_increases_on_visitor_count_increase_pg() {
177 let data = crate::tests::pg::get_data().await;
178 pow_difficulty_factor_increases_on_visitor_count_increase(data).await;
179 }
180
181 #[actix_rt::test]
182 async fn pow_difficulty_factor_increases_on_visitor_count_increase_maria() {
183 let data = crate::tests::maria::get_data().await;
184 pow_difficulty_factor_increases_on_visitor_count_increase(data).await;
185 }
186
187 pub async fn pow_difficulty_factor_increases_on_visitor_count_increase(
188 data: ArcData,
189 ) {
190 use super::*;
191 use crate::tests::*;
192 use crate::*;
193 use actix_web::test;
194
195 use libmcaptcha::defense::Level;
196
197 use crate::api::v1::mcaptcha::create::CreateCaptcha;
198 use crate::api::v1::mcaptcha::create::MCaptchaDetails;
199
200 const NAME: &str = "powusrworks2";
201 const PASSWORD: &str = "testingpas";
202 const EMAIL: &str = "randomuser2@a.com";
203 pub const L1: Level = Level {
204 difficulty_factor: 10,
205 visitor_threshold: 10,
206 };
207 pub const L2: Level = Level {
208 difficulty_factor: 20,
209 visitor_threshold: 20,
210 };
211
212 pub const L3: Level = Level {
213 difficulty_factor: 30,
214 visitor_threshold: 30,
215 };
216
217 let data = &data;
218 let levels = [L1, L2, L3];
219
220 delete_user(data, NAME).await;
221
222 let (_, signin_resp) = register_and_signin(data, NAME, EMAIL, PASSWORD).await;
223 let cookies = get_cookie!(signin_resp);
224 let app = get_app!(data).await;
225
226 let create_captcha = CreateCaptcha {
227 levels: levels.into(),
228 duration: 30,
229 description: "dummy".into(),
230 publish_benchmarks: true,
231 };
232
233 let add_token_resp = test::call_service(
235 &app,
236 post_request!(&create_captcha, V1_API_ROUTES.captcha.create)
237 .cookie(cookies.clone())
238 .to_request(),
239 )
240 .await;
241 assert_eq!(add_token_resp.status(), StatusCode::OK);
242 let token_key: MCaptchaDetails = test::read_body_json(add_token_resp).await;
243
244 let get_config_payload = GetConfigPayload {
245 key: token_key.key.clone(),
246 };
247
248 let _url = V1_API_ROUTES.pow.get_config;
249 let mut prev = 0;
250 for (count, l) in levels.iter().enumerate() {
251 for _l in prev..l.visitor_threshold * 2 {
252 let _get_config_resp = test::call_service(
253 &app,
254 post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
255 .to_request(),
256 )
257 .await;
258 }
259
260 let get_config_resp = test::call_service(
261 &app,
262 post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
263 .to_request(),
264 )
265 .await;
266
267 let config: PoWConfig = test::read_body_json(get_config_resp).await;
268 println!(
269 "[{count}] received difficulty_factor: {} prev difficulty_factor {}",
270 config.difficulty_factor, prev
271 );
272 if count == levels.len() - 1 {
273 assert!(config.difficulty_factor == prev);
274 } else {
275 assert!(config.difficulty_factor > prev);
276 }
277 prev = config.difficulty_factor;
278 }
279 }
281}