mcaptcha/api/v1/mcaptcha/
update.rs1use actix_identity::Identity;
7use actix_web::{web, HttpResponse, Responder};
8use libmcaptcha::defense::Level;
9use libmcaptcha::master::messages::RenameBuilder;
10use serde::{Deserialize, Serialize};
11
12use db_core::errors::DBError;
13use db_core::CreateCaptcha;
14
15use super::create::MCaptchaDetails;
16use super::get_random;
17use crate::errors::*;
18use crate::AppData;
19
20#[my_codegen::post(
21 path = "crate::V1_API_ROUTES.captcha.update_key",
22 wrap = "crate::api::v1::get_middleware()"
23)]
24pub async fn update_key(
25 payload: web::Json<MCaptchaDetails>,
26 data: AppData,
27 id: Identity,
28) -> ServiceResult<impl Responder> {
29 let username = id.identity().unwrap();
30 let mut key;
31
32 loop {
33 key = get_random(32);
34
35 match data
36 .db
37 .update_captcha_key(&username, &payload.key, &key)
38 .await
39 {
40 Ok(_) => break,
41 Err(DBError::SecretTaken) => continue,
42 Err(e) => return Err(e.into()),
43 }
44 }
45
46 let payload = payload.into_inner();
47 let rename = RenameBuilder::default()
48 .name(payload.key)
49 .rename_to(key.clone())
50 .build()
51 .unwrap();
52 data.captcha.rename(rename).await?;
53
54 let resp = MCaptchaDetails {
55 key,
56 name: payload.name,
57 };
58
59 Ok(HttpResponse::Ok().json(resp))
60}
61
62#[derive(Serialize, Deserialize)]
63pub struct UpdateCaptcha {
64 pub levels: Vec<Level>,
65 pub duration: u32,
66 pub description: String,
67 pub key: String,
68 pub publish_benchmarks: bool,
69}
70
71#[my_codegen::post(
72 path = "crate::V1_API_ROUTES.captcha.update",
73 wrap = "crate::api::v1::get_middleware()"
74)]
75pub async fn update_captcha(
76 payload: web::Json<UpdateCaptcha>,
77 data: AppData,
78 id: Identity,
79) -> ServiceResult<impl Responder> {
80 let username = id.identity().unwrap();
81 runner::update_captcha(&payload, &data, &username).await?;
82 Ok(HttpResponse::Ok())
83}
84
85pub mod runner {
86 use libmcaptcha::{master::messages::RemoveCaptcha, DefenseBuilder};
87
88 use super::*;
89
90 pub async fn update_captcha(
91 payload: &UpdateCaptcha,
92 data: &AppData,
93 username: &str,
94 ) -> ServiceResult<()> {
95 let mut defense = DefenseBuilder::default();
96
97 for level in payload.levels.iter() {
98 defense.add_level(*level)?;
99 }
100
101 defense.build()?;
105
106 data.db
107 .delete_captcha_levels(username, &payload.key)
108 .await?;
109
110 let m = CreateCaptcha {
111 key: &payload.key,
112 duration: payload.duration as i32,
113 description: &payload.description,
114 };
115
116 data.db.update_captcha_metadata(username, &m).await?;
117
118 data.db
119 .add_captcha_levels(username, &payload.key, &payload.levels)
120 .await?;
121 if let Err(ServiceError::CaptchaError(e)) = data
122 .captcha
123 .remove(RemoveCaptcha(payload.key.clone()))
124 .await
125 {
126 log::error!(
127 "Deleting captcha key {} while updating it, error: {:?}",
128 &payload.key,
129 e
130 );
131 }
132
133 if payload.publish_benchmarks {
134 data.db
135 .analytics_create_psuedo_id_if_not_exists(&payload.key)
136 .await?;
137 } else {
138 data.db
139 .analytics_delete_all_records_for_campaign(&payload.key)
140 .await?;
141 }
142 Ok(())
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use actix_web::http::StatusCode;
149 use actix_web::test;
150
151 use crate::api::v1::mcaptcha::create::MCaptchaDetails;
152 use crate::api::v1::mcaptcha::stats::StatsPayload;
153 use crate::api::v1::ROUTES;
154 use crate::tests::*;
155 use crate::*;
156
157 #[actix_rt::test]
158 async fn update_and_get_mcaptcha_works_pg() {
159 let data = crate::tests::pg::get_data().await;
160 update_and_get_mcaptcha_works(data).await;
161 }
162
163 #[actix_rt::test]
164 async fn update_and_get_mcaptcha_works_maria() {
165 let data = crate::tests::maria::get_data().await;
166 update_and_get_mcaptcha_works(data).await;
167 }
168
169 async fn update_and_get_mcaptcha_works(data: ArcData) {
170 const NAME: &str = "updateusermcaptcha";
171 const PASSWORD: &str = "longpassworddomain";
172 const EMAIL: &str = "testupdateusermcaptcha@a.com";
173 let data = &data;
174 delete_user(data, NAME).await;
175
176 register_and_signin(data, NAME, EMAIL, PASSWORD).await;
178 let (_, signin_resp, token_key) = add_levels_util(data, NAME, PASSWORD).await;
179 let cookies = get_cookie!(signin_resp);
180 let app = get_app!(data).await;
181
182 let update_token_resp = test::call_service(
184 &app,
185 post_request!(&token_key, ROUTES.captcha.update_key)
186 .cookie(cookies.clone())
187 .to_request(),
188 )
189 .await;
190 assert_eq!(update_token_resp.status(), StatusCode::OK);
191 let updated_token: MCaptchaDetails =
192 test::read_body_json(update_token_resp).await;
193
194 let get_token_resp = test::call_service(
196 &app,
197 post_request!(&updated_token, ROUTES.captcha.get)
198 .cookie(cookies.clone())
199 .to_request(),
200 )
201 .await;
202 assert_eq!(get_token_resp.status(), StatusCode::OK);
204
205 let paylod = StatsPayload { key: token_key.key };
207 let get_statis_resp = test::call_service(
208 &app,
209 post_request!(&paylod, ROUTES.captcha.stats.get)
210 .cookie(cookies.clone())
211 .to_request(),
212 )
213 .await;
214 assert_eq!(get_statis_resp.status(), StatusCode::OK);
216 }
217}