use actix_web::HttpRequest;
use actix_web::{web, HttpResponse, Responder};
use libmcaptcha::pow::Work;
use serde::{Deserialize, Serialize};
use crate::errors::*;
use crate::AppData;
use crate::V1_API_ROUTES;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ValidationToken {
pub token: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ApiWork {
pub string: String,
pub result: String,
pub nonce: u64,
pub key: String,
pub time: Option<u32>,
pub worker_type: Option<String>,
}
impl From<ApiWork> for Work {
fn from(value: ApiWork) -> Self {
Self {
string: value.string,
nonce: value.nonce,
result: value.result,
key: value.key,
}
}
}
#[my_codegen::post(path = "V1_API_ROUTES.pow.verify_pow()")]
pub async fn verify_pow(
req: HttpRequest,
payload: web::Json<ApiWork>,
data: AppData,
) -> ServiceResult<impl Responder> {
#[cfg(not(test))]
let ip = req.connection_info().peer_addr().unwrap().to_string();
#[cfg(test)]
let ip = "127.0.1.1".into();
let key = payload.key.clone();
let payload = payload.into_inner();
let worker_type = payload.worker_type.clone();
let time = payload.time;
let nonce = payload.nonce;
let (res, difficulty_factor) = data.captcha.verify_pow(payload.into(), ip).await?;
data.stats.record_solve(&data, &key).await?;
if let (Some(time), Some(worker_type)) = (time, worker_type) {
let analytics = db_core::CreatePerformanceAnalytics {
difficulty_factor,
time,
worker_type,
};
data.db.analysis_save(&key, &analytics).await?;
}
data.db
.update_max_nonce_for_level(&key, difficulty_factor, nonce as u32)
.await?;
let payload = ValidationToken { token: res };
Ok(HttpResponse::Ok().json(payload))
}
#[cfg(test)]
pub mod tests {
use actix_web::http::StatusCode;
use actix_web::test;
use libmcaptcha::pow::PoWConfig;
use super::*;
use crate::api::v1::pow::get_config::GetConfigPayload;
use crate::tests::*;
use crate::*;
#[actix_rt::test]
async fn verify_pow_works_pg() {
let data = crate::tests::pg::get_data().await;
verify_pow_works(data).await;
}
#[actix_rt::test]
async fn verify_pow_works_maria() {
let data = crate::tests::maria::get_data().await;
verify_pow_works(data).await;
}
#[actix_rt::test]
async fn verify_analytics_pow_works_pg() {
let data = crate::tests::pg::get_data().await;
verify_analytics_pow_works(data).await;
}
#[actix_rt::test]
async fn verify_analytics_pow_works_maria() {
let data = crate::tests::maria::get_data().await;
verify_analytics_pow_works(data).await;
}
pub async fn verify_analytics_pow_works(data: ArcData) {
const NAME: &str = "powanalyticsuser";
const PASSWORD: &str = "testingpas";
const EMAIL: &str = "powanalyticsuser@a.com";
let data = &data;
delete_user(data, NAME).await;
register_and_signin(data, NAME, EMAIL, PASSWORD).await;
let (_, _signin_resp, token_key) = add_levels_util(data, NAME, PASSWORD).await;
let app = get_app!(data).await;
let get_config_payload = GetConfigPayload {
key: token_key.key.clone(),
};
let get_config_resp = test::call_service(
&app,
post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
.to_request(),
)
.await;
assert_eq!(get_config_resp.status(), StatusCode::OK);
let config: PoWConfig = test::read_body_json(get_config_resp).await;
let pow = mcaptcha_pow_sha256::ConfigBuilder::default()
.salt(config.salt)
.build()
.unwrap();
let work = pow
.prove_work(&config.string.clone(), config.difficulty_factor)
.unwrap();
let work = ApiWork {
string: config.string.clone(),
result: work.result,
nonce: work.nonce,
key: token_key.key.clone(),
time: Some(100),
worker_type: Some("wasm".into()),
};
let pow_verify_resp = test::call_service(
&app,
post_request!(&work, V1_API_ROUTES.pow.verify_pow).to_request(),
)
.await;
assert_eq!(pow_verify_resp.status(), StatusCode::OK);
let limit = 50;
let offset = 0;
let mut analytics = data
.db
.analytics_fetch(&token_key.key, limit, offset)
.await
.unwrap();
assert_eq!(analytics.len(), 1);
let a = analytics.pop().unwrap();
assert_eq!(a.time, work.time.unwrap());
assert_eq!(a.worker_type, work.worker_type.unwrap());
}
pub async fn verify_pow_works(data: ArcData) {
const NAME: &str = "powverifyusr";
const PASSWORD: &str = "testingpas";
const EMAIL: &str = "verifyuser@a.com";
let data = &data;
delete_user(data, NAME).await;
register_and_signin(data, NAME, EMAIL, PASSWORD).await;
let (_, _signin_resp, token_key) = add_levels_util(data, NAME, PASSWORD).await;
let app = get_app!(data).await;
let get_config_payload = GetConfigPayload {
key: token_key.key.clone(),
};
let get_config_resp = test::call_service(
&app,
post_request!(&get_config_payload, V1_API_ROUTES.pow.get_config)
.to_request(),
)
.await;
assert_eq!(get_config_resp.status(), StatusCode::OK);
let config: PoWConfig = test::read_body_json(get_config_resp).await;
let pow = mcaptcha_pow_sha256::ConfigBuilder::default()
.salt(config.salt)
.build()
.unwrap();
let work = pow
.prove_work(&config.string.clone(), config.difficulty_factor)
.unwrap();
let work = Work {
string: config.string.clone(),
result: work.result,
nonce: work.nonce,
key: token_key.key.clone(),
};
let pow_verify_resp = test::call_service(
&app,
post_request!(&work, V1_API_ROUTES.pow.verify_pow).to_request(),
)
.await;
assert_eq!(pow_verify_resp.status(), StatusCode::OK);
assert!(data
.db
.analytics_fetch(&token_key.key, 50, 0)
.await
.unwrap()
.is_empty());
let string_not_found = test::call_service(
&app,
post_request!(&work, V1_API_ROUTES.pow.verify_pow).to_request(),
)
.await;
assert_eq!(string_not_found.status(), StatusCode::BAD_REQUEST);
let err: ErrorToResponse = test::read_body_json(string_not_found).await;
assert_eq!(err.error, "Challenge: not found");
}
}