mcaptcha/api/v1/pow/
verify_token.rs

1// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
2// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! PoW success token module
7
8use actix_web::{web, HttpResponse, Responder};
9use libmcaptcha::cache::messages::VerifyCaptchaResult;
10use serde::{Deserialize, Serialize};
11
12use crate::errors::*;
13use crate::AppData;
14use crate::V1_API_ROUTES;
15
16#[derive(Clone, Debug, Deserialize, Serialize)]
17pub struct CaptchaValidateResp {
18    pub valid: bool,
19}
20
21#[derive(Clone, Debug, Deserialize, Serialize)]
22pub struct VerifyCaptchaResultPayload {
23    pub secret: String,
24    pub key: String,
25    pub token: String,
26}
27
28impl From<VerifyCaptchaResultPayload> for VerifyCaptchaResult {
29    fn from(m: VerifyCaptchaResultPayload) -> Self {
30        VerifyCaptchaResult {
31            token: m.token,
32            key: m.key,
33        }
34    }
35}
36
37// API keys are mcaptcha actor names
38
39/// route handler that validates a PoW solution token
40#[my_codegen::post(path = "V1_API_ROUTES.pow.validate_captcha_token()")]
41pub async fn validate_captcha_token(
42    payload: web::Json<VerifyCaptchaResultPayload>,
43    data: AppData,
44) -> ServiceResult<impl Responder> {
45    let secret = data.db.get_secret_from_captcha(&payload.key).await?;
46    if secret.secret != payload.secret {
47        return Err(ServiceError::WrongPassword);
48    }
49    let payload: VerifyCaptchaResult = payload.into_inner().into();
50    let key = payload.key.clone();
51    let res = data.captcha.validate_verification_tokens(payload).await?;
52    let resp = CaptchaValidateResp { valid: res };
53    data.stats.record_confirm(&data, &key).await?;
54    //println!("{:?}", &payload);
55    Ok(HttpResponse::Ok().json(resp))
56}
57
58#[cfg(test)]
59pub mod tests {
60    use actix_web::http::StatusCode;
61    use actix_web::test;
62    use libmcaptcha::pow::PoWConfig;
63    use libmcaptcha::pow::Work;
64
65    use super::*;
66    use crate::api::v1::pow::get_config::GetConfigPayload;
67    use crate::api::v1::pow::verify_pow::ValidationToken;
68    use crate::tests::*;
69    use crate::*;
70
71    #[actix_rt::test]
72    async fn validate_captcha_token_works_pg() {
73        let data = crate::tests::pg::get_data().await;
74        validate_captcha_token_works(data).await;
75    }
76
77    #[actix_rt::test]
78    async fn validate_captcha_token_works_maria() {
79        let data = crate::tests::maria::get_data().await;
80        validate_captcha_token_works(data).await;
81    }
82
83    pub async fn validate_captcha_token_works(data: ArcData) {
84        const NAME: &str = "enterprisetken";
85        const PASSWORD: &str = "testingpas";
86        const EMAIL: &str = "verifyuser@enter.com";
87        const VERIFY_CAPTCHA_URL: &str = "/api/v1/pow/verify";
88        const GET_URL: &str = "/api/v1/pow/config";
89        const VERIFY_TOKEN_URL: &str = "/api/v1/pow/siteverify";
90        //        const UPDATE_URL: &str = "/api/v1/mcaptcha/domain/token/duration/update";
91
92        let data = &data;
93        delete_user(data, NAME).await;
94
95        register_and_signin(data, NAME, EMAIL, PASSWORD).await;
96        let (_, signin_resp, token_key) = add_levels_util(data, NAME, PASSWORD).await;
97        let app = get_app!(data).await;
98        let cookies = get_cookie!(signin_resp);
99
100        let secret = test::call_service(
101            &app,
102            test::TestRequest::get()
103                .cookie(cookies.clone())
104                .uri(V1_API_ROUTES.account.get_secret)
105                .to_request(),
106        )
107        .await;
108        assert_eq!(secret.status(), StatusCode::OK);
109        let secret: db_core::Secret = test::read_body_json(secret).await;
110        let secret = secret.secret;
111
112        let get_config_payload = GetConfigPayload {
113            key: token_key.key.clone(),
114        };
115
116        // update and check changes
117
118        let get_config_resp = test::call_service(
119            &app,
120            post_request!(&get_config_payload, GET_URL).to_request(),
121        )
122        .await;
123        assert_eq!(get_config_resp.status(), StatusCode::OK);
124        let config: PoWConfig = test::read_body_json(get_config_resp).await;
125
126        let pow = mcaptcha_pow_sha256::ConfigBuilder::default()
127            .salt(config.salt)
128            .build()
129            .unwrap();
130        let work = pow
131            .prove_work(&config.string.clone(), config.difficulty_factor)
132            .unwrap();
133
134        let work = Work {
135            string: config.string.clone(),
136            result: work.result,
137            nonce: work.nonce,
138            key: token_key.key.clone(),
139        };
140
141        let pow_verify_resp = test::call_service(
142            &app,
143            post_request!(&work, VERIFY_CAPTCHA_URL).to_request(),
144        )
145        .await;
146        assert_eq!(pow_verify_resp.status(), StatusCode::OK);
147        let client_token: ValidationToken = test::read_body_json(pow_verify_resp).await;
148
149        let mut validate_payload = VerifyCaptchaResultPayload {
150            token: client_token.token.clone(),
151            key: token_key.key.clone(),
152            secret: NAME.to_string(),
153        };
154
155        // siteverify authentication failure
156        bad_post_req_test(
157            data,
158            NAME,
159            PASSWORD,
160            VERIFY_TOKEN_URL,
161            &validate_payload,
162            ServiceError::WrongPassword,
163        )
164        .await;
165        //       let validate_client_token = test::call_service(
166        //            &app,
167        //            post_request!(&validate_payload, VERIFY_TOKEN_URL).to_request(),
168        //        )
169        //        .await;
170        //        assert_eq!(validate_client_token.status(), StatusCode::OK);
171        //        let resp: CaptchaValidateResp =
172        //            test::read_body_json(validate_client_token).await;
173        //        assert!(resp.valid);
174
175        // verifying work
176        validate_payload.secret = secret.clone();
177
178        let validate_client_token = test::call_service(
179            &app,
180            post_request!(&validate_payload, VERIFY_TOKEN_URL).to_request(),
181        )
182        .await;
183        assert_eq!(validate_client_token.status(), StatusCode::OK);
184        let resp: CaptchaValidateResp =
185            test::read_body_json(validate_client_token).await;
186        assert!(resp.valid);
187
188        // string not found
189        let string_not_found = test::call_service(
190            &app,
191            post_request!(&validate_payload, VERIFY_TOKEN_URL).to_request(),
192        )
193        .await;
194        let resp: CaptchaValidateResp = test::read_body_json(string_not_found).await;
195        assert!(!resp.valid);
196    }
197}