Merge pull request #2 from mCaptcha/online
Compute PoW, and get auth token from mCaptcha server
This commit is contained in:
commit
fdce2df44f
4 changed files with 1155 additions and 67 deletions
943
Cargo.lock
generated
943
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -11,4 +11,9 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pow_sha256 = { version = "0.3.1", git = "https://github.com/mCaptcha/pow_sha256" }
|
pow_sha256 = { version = "0.3.1", git = "https://github.com/mCaptcha/pow_sha256" }
|
||||||
clap = {version = "4.3", features = ["derive"]}
|
clap = { version = "4.3", features = ["derive"] }
|
||||||
|
tokio = { version = "1.31.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
|
reqwest = { version = "0.11.18", features = ["json", "gzip"] }
|
||||||
|
serde = { version = "1.0.183", features = ["derive"] }
|
||||||
|
serde_json = "1.0.104"
|
||||||
|
url = { version = "2.4.0", features = ["serde"] }
|
||||||
|
|
81
README.md
81
README.md
|
@ -14,22 +14,79 @@
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
### Modes:
|
||||||
|
|
||||||
|
1. Offline: Computes PoW over given CAPTCHA parameters
|
||||||
|
2. Online: CLI alternative for people using browsers without JavaScript
|
||||||
|
support
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mCaptcha PoW CLI 0.1.0
|
CLI tool to solve mCaptcha
|
||||||
Aravinth Manivannan <realaravinth@batsense.net>
|
|
||||||
Generates PoW for mCaptcha
|
|
||||||
|
|
||||||
USAGE:
|
Usage: mcaptcha-cli <COMMAND>
|
||||||
mcaptcha-cli [OPTIONS]
|
|
||||||
|
|
||||||
FLAGS:
|
Commands:
|
||||||
-h, --help Prints help information
|
offline Compute PoW with offline parameters
|
||||||
-V, --version Prints version information
|
online Compute PoW by fetching parameters from CAPTCHA URL
|
||||||
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
OPTIONS:
|
Options:
|
||||||
-d, --difficulty <INTEGER> Difficulty factor
|
-h, --help Print help
|
||||||
-p, --phrase <STRING> Phrase over which PoW should be computed
|
-V, --version Print version
|
||||||
-s, --salt <STRING> Salt with which PoW should be computed
|
```
|
||||||
|
|
||||||
|
#### Offline
|
||||||
|
|
||||||
|
Help menu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Compute PoW with offline parameters
|
||||||
|
|
||||||
|
Usage: mcaptcha-cli offline --salt <SALT> --phrase <PHRASE> --difficulty-factor <DIFFICULTY_FACTOR>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-s, --salt <SALT> Salt with which PoW should be computed
|
||||||
|
-p, --phrase <PHRASE> Phrase over which PoW should be computed
|
||||||
|
-d, --difficulty-factor <DIFFICULTY_FACTOR> Difficulty Factor
|
||||||
|
-h, --help Print help
|
||||||
|
```
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
13:28 atm@lab cli ±|online|→ mcaptcha-cli offline -s $(rand 32) -p $(rand 32) -d 50000
|
||||||
|
difficulty: 50000
|
||||||
|
nonce: 90507
|
||||||
|
original phrase: f351f333d44b2c6b5bf7f033b065bbb8fb5e9dd153bd402e43ed04425f5a3859
|
||||||
|
result: 340276562956196291522979356090220150471
|
||||||
|
```
|
||||||
|
|
||||||
|
(where rand is
|
||||||
|
[this](https://github.com/realaravinth/dotfiles/blob/6fc6c87cc912e17488a35c0d3327ecf393221270/scripts/rand#L20)
|
||||||
|
script)
|
||||||
|
|
||||||
|
#### Online
|
||||||
|
|
||||||
|
Fetches CAPTCHA parameters from CAPTCHA URL, computes PoW, validates
|
||||||
|
against given mCaptcha server and returns authorization token
|
||||||
|
|
||||||
|
Help menu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Compute PoW by fetching parameters from CAPTCHA URL
|
||||||
|
|
||||||
|
Usage: mcaptcha-cli online --url <URL>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-u, --url <URL> URL of the CAPTCHA. Example: https://example.org/widget?sitekey=foo
|
||||||
|
-h, --help Print help
|
||||||
|
```
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
13:32 atm@lab cli ±|online ✗|→ mcaptcha-cli online -u https://demo.mcaptcha.org/widget?sitekey=pHy0AktWyOKuxZDzFfoaewncWecCHo23
|
||||||
|
Authorization token: 3xleN26OctBuVu3X4t6CYyUjErhaxQvz
|
||||||
```
|
```
|
||||||
|
|
||||||
## Funding
|
## Funding
|
||||||
|
|
191
src/main.rs
191
src/main.rs
|
@ -1,29 +1,20 @@
|
||||||
/*
|
// Copyright © 2023 Aravinth Manivannan <realravinth@batsense.net>
|
||||||
* mCaptcha - A proof of work based DoS protection system
|
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
* Copyright © 2023 Aravinth Manivannan <realravinth@batsense.net>
|
//
|
||||||
*
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
use std::time::Instant;
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
use clap::*;
|
use clap::*;
|
||||||
use pow_sha256::ConfigBuilder;
|
use pow_sha256::ConfigBuilder;
|
||||||
|
use pow_sha256::PoW;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Parser, Serialize, Clone, Debug)]
|
||||||
|
/// Compute PoW with offline parameters
|
||||||
/// Simple program to greet a person
|
struct Offline {
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
struct Args {
|
|
||||||
/// Salt with which PoW should be computed
|
/// Salt with which PoW should be computed
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
salt: String,
|
salt: String,
|
||||||
|
@ -32,22 +23,150 @@ struct Args {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
phrase: String,
|
phrase: String,
|
||||||
|
|
||||||
|
|
||||||
/// Difficulty Factor
|
/// Difficulty Factor
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
difficulty_factor: u32,
|
difficulty_factor: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[derive(Deserialize, Parser, Serialize, Clone, Debug)]
|
||||||
let matches = Args::parse();
|
/// Compute PoW by fetching parameters from CAPTCHA URL
|
||||||
let phrase = matches.phrase;
|
struct Online {
|
||||||
let salt = matches.salt;
|
/// URL of the CAPTCHA. Example: https://example.org/widget?sitekey=foo
|
||||||
let difficulty_factor = matches.difficulty_factor;
|
#[arg(short, long)]
|
||||||
|
url: Url,
|
||||||
let config = ConfigBuilder::default().salt(salt.into()).build().unwrap();
|
}
|
||||||
let work = config.prove_work(&phrase, difficulty_factor).unwrap();
|
|
||||||
println!("difficulty: {}", &difficulty_factor);
|
impl Online {
|
||||||
println!("nonce: {}", &work.nonce);
|
fn extract_sitekey(&self) -> Option<String> {
|
||||||
println!("original phrase: {}", &phrase);
|
let mut sitekey = None;
|
||||||
println!("result: {}", &work.result);
|
for (k, v) in self.url.query_pairs() {
|
||||||
|
if &k == "sitekey" {
|
||||||
|
let x: &str = &v;
|
||||||
|
sitekey = Some(x.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sitekey
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_url(&self) -> Url {
|
||||||
|
let mut url = self.url.clone();
|
||||||
|
url.set_path("/api/v1/pow/config");
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_verify_url(&self) -> Url {
|
||||||
|
let mut url = self.url.clone();
|
||||||
|
url.set_path("/api/v1/pow/verify");
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Parser, Serialize, Clone, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
enum Args {
|
||||||
|
Offline(Offline),
|
||||||
|
Online(Online),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prove_work(phrase: String, salt: String, difficulty_factor: u32) -> PoW<String> {
|
||||||
|
let config = ConfigBuilder::default().salt(salt).build().unwrap();
|
||||||
|
config.prove_work(&phrase, difficulty_factor).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
match args {
|
||||||
|
Args::Offline(matches) => {
|
||||||
|
let phrase = matches.phrase;
|
||||||
|
let salt = matches.salt;
|
||||||
|
let difficulty_factor = matches.difficulty_factor;
|
||||||
|
let work = prove_work(phrase.clone(), salt, difficulty_factor);
|
||||||
|
|
||||||
|
// let config = ConfigBuilder::default().salt(salt.into()).build().unwrap();
|
||||||
|
// let work = config.prove_work(&phrase, difficulty_factor).unwrap();
|
||||||
|
println!("difficulty: {}", &difficulty_factor);
|
||||||
|
println!("nonce: {}", &work.nonce);
|
||||||
|
println!("original phrase: {}", &phrase);
|
||||||
|
println!("result: {}", &work.result);
|
||||||
|
}
|
||||||
|
Args::Online(matches) => {
|
||||||
|
let sitekey = matches.extract_sitekey();
|
||||||
|
|
||||||
|
if sitekey.is_none() {
|
||||||
|
println!("ERROR: Sitekey not found in URL. Please enter correct URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let sitekey = sitekey.unwrap();
|
||||||
|
|
||||||
|
let c = Client::default();
|
||||||
|
let url = matches.get_config_url();
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
struct ConfigRequest {
|
||||||
|
key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
struct ConfigResp {
|
||||||
|
string: String,
|
||||||
|
difficulty_factor: u32,
|
||||||
|
salt: String,
|
||||||
|
}
|
||||||
|
let req = ConfigRequest {
|
||||||
|
key: sitekey.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let resp: ConfigResp = c
|
||||||
|
.post(url)
|
||||||
|
.json(&req)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let start = Instant::now();
|
||||||
|
let work = prove_work(resp.string.clone(), resp.salt, resp.difficulty_factor);
|
||||||
|
let finish = Instant::now();
|
||||||
|
let time_elapsed = finish.duration_since(start);
|
||||||
|
let time = time_elapsed.as_millis() as usize;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
struct VerifyRequest {
|
||||||
|
key: String,
|
||||||
|
nonce: u64,
|
||||||
|
result: String,
|
||||||
|
string: String,
|
||||||
|
time: usize,
|
||||||
|
worker_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
struct VerifyResp {
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = VerifyRequest {
|
||||||
|
key: sitekey.clone(),
|
||||||
|
nonce: work.nonce,
|
||||||
|
result: work.result,
|
||||||
|
string: resp.string,
|
||||||
|
time,
|
||||||
|
worker_type: String::from("mCaptcha CLI"),
|
||||||
|
};
|
||||||
|
let url = matches.get_verify_url();
|
||||||
|
let resp: VerifyResp = c
|
||||||
|
.post(url)
|
||||||
|
.json(&req)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
println!("Authorization token: {}", resp.token);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue