examples, doc and v0.1 prep
This commit is contained in:
parent
7dbda0670c
commit
8c4f885a85
9 changed files with 227 additions and 71 deletions
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
13
README.md
13
README.md
|
@ -7,18 +7,25 @@
|
||||||
[](https://mcaptcha.github.io/mCaptcha/m_captcha/index.html)
|
[](https://mcaptcha.github.io/mCaptcha/m_captcha/index.html)
|
||||||
/badge.svg>)
|
/badge.svg>)
|
||||||
[](https://deps.rs/repo/github/mCaptcha/mCaptcha)
|
[](https://deps.rs/repo/github/mCaptcha/mCaptcha)
|
||||||
|
[](http://www.gnu.org/licenses/agpl-3.0)
|
||||||
<br />
|
<br />
|
||||||
[](https://codecov.io/gh/mCaptcha/mCaptcha)
|
[](https://codecov.io/gh/mCaptcha/mCaptcha)
|
||||||
|
[](https://matrix.to/#/+mcaptcha:matrix.batsense.net)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### STATUS: ACTIVE DEVELOPMENT (fancy word for unusable)
|
|
||||||
|
|
||||||
mCaptcha uses SHA256 based proof-of-work(PoW) to rate limit users.
|
mCaptcha uses SHA256 based proof-of-work(PoW) to rate limit users.
|
||||||
|
|
||||||
**If someone wants to hammer your site, they will have to do more work to
|
If someone wants to hammer your site, they will have to do more work to
|
||||||
send requests than your server will have to do to respond to their
|
send requests than your server will have to do to respond to their
|
||||||
request.**
|
request.
|
||||||
|
|
||||||
|
>**NOTE:** `0.1` is out, expect breaking changes as ergonomics and
|
||||||
|
performance is improved. Checkout [changelog](./CHANGELOG.md) for
|
||||||
|
changes and migration pointers.
|
||||||
|
|
||||||
|
|
||||||
## Why use mCaptcha?
|
## Why use mCaptcha?
|
||||||
- Free software, privacy focused
|
- Free software, privacy focused
|
||||||
|
|
115
examples/simple.rs
Normal file
115
examples/simple.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
use m_captcha::{
|
||||||
|
cache::HashCache,
|
||||||
|
master::{AddSiteBuilder, Master},
|
||||||
|
pow::{ConfigBuilder, Work},
|
||||||
|
system::SystemBuilder,
|
||||||
|
DefenseBuilder, LevelBuilder, MCaptchaBuilder,
|
||||||
|
};
|
||||||
|
// traits from actix needs to be in scope for starting actor
|
||||||
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
#[actix_rt::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
// start cahce actor
|
||||||
|
// cache is used to store PoW requirements that are sent to clients
|
||||||
|
// This way, it can be verified that the client computed work over a config
|
||||||
|
// that _we_ sent. Offers protection against rainbow tables powered dictionary attacks
|
||||||
|
let cache = HashCache::default().start();
|
||||||
|
|
||||||
|
// create PoW config with unique salt. Salt has to be safely guarded.
|
||||||
|
// salts protect us from replay attacks
|
||||||
|
let pow = ConfigBuilder::default()
|
||||||
|
.salt("myrandomsaltisnotlongenoug".into())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// start master actor. Master actor is responsible for managing MCaptcha actors
|
||||||
|
// each mCaptcha system should have only one master
|
||||||
|
let master = Master::new().start();
|
||||||
|
|
||||||
|
// Create system. System encapsulates master and cache and provides useful abstraction
|
||||||
|
// each mCaptcha system should have only one system
|
||||||
|
let system = SystemBuilder::default()
|
||||||
|
.master(master)
|
||||||
|
.cache(cache)
|
||||||
|
.pow(pow.clone())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// configure defense. This is a per site configuration. A site can have several levels
|
||||||
|
// of defenses configured
|
||||||
|
let defense = DefenseBuilder::default()
|
||||||
|
// add as many defense as you see fit
|
||||||
|
.add_level(
|
||||||
|
LevelBuilder::default()
|
||||||
|
// visitor_threshold is the threshold/limit at which
|
||||||
|
// mCaptcha will adjust difficulty defense
|
||||||
|
// it is advisable to set small values for the first
|
||||||
|
// defense visitor_threshold and difficulty_factor
|
||||||
|
// as this will be the work that clients will be
|
||||||
|
// computing when there's no load
|
||||||
|
.visitor_threshold(50)
|
||||||
|
.difficulty_factor(500)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.add_level(
|
||||||
|
LevelBuilder::default()
|
||||||
|
.visitor_threshold(5000)
|
||||||
|
.difficulty_factor(50000)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// create and start MCaptcha actor that uses the above defense configuration
|
||||||
|
// This is what manages the difficulty factor of sites that an mCaptcha protects
|
||||||
|
let mcaptcha = MCaptchaBuilder::default()
|
||||||
|
.defense(defense)
|
||||||
|
// leaky bucket algorithm's emission interval
|
||||||
|
.duration(30)
|
||||||
|
// .cache(cache)
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
// unique value identifying an MCaptcha actor
|
||||||
|
let mcaptcha_name = "batsense.net";
|
||||||
|
|
||||||
|
// add MCaptcha to Master
|
||||||
|
let msg = AddSiteBuilder::default()
|
||||||
|
.id(mcaptcha_name.into())
|
||||||
|
.addr(mcaptcha.clone())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
system.master.send(msg).await.unwrap();
|
||||||
|
|
||||||
|
// Get PoW config. Should be called everytime there's a visitor for a
|
||||||
|
// managed site(here mcaptcha_name)
|
||||||
|
let work_req = system.get_pow(mcaptcha_name.into()).await.unwrap();
|
||||||
|
|
||||||
|
// the following computation should be done on the client but for the purpose
|
||||||
|
// of this illustration, we are going to do it on the server it self
|
||||||
|
let work = pow
|
||||||
|
.prove_work(&work_req.string, work_req.difficulty_factor)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// the payload that the client sends to the server
|
||||||
|
let payload = Work {
|
||||||
|
string: work_req.string,
|
||||||
|
result: work.result,
|
||||||
|
nonce: work.nonce,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Server evaluates client's work. Returns true if everything
|
||||||
|
// checksout and Err() if something fishy is happening
|
||||||
|
let res = system.verify_pow(payload.clone()).await.unwrap();
|
||||||
|
assert!(res);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -63,11 +63,6 @@ pub enum CaptchaError {
|
||||||
/// isn't in cache
|
/// isn't in cache
|
||||||
#[display(fmt = "String now found")]
|
#[display(fmt = "String now found")]
|
||||||
StringNotFound,
|
StringNotFound,
|
||||||
|
|
||||||
/// Catcha all default error
|
|
||||||
/// used for development, must remove before production
|
|
||||||
#[display(fmt = "TODO remove before prod")]
|
|
||||||
Default,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [Result] datatype for m_captcha
|
/// [Result] datatype for m_captcha
|
||||||
|
|
89
src/lib.rs
89
src/lib.rs
|
@ -31,20 +31,59 @@
|
||||||
//! - Difficulty(Factor): Minimum ammount of work that a client must do to make a valid
|
//! - Difficulty(Factor): Minimum ammount of work that a client must do to make a valid
|
||||||
//! request.
|
//! request.
|
||||||
//! - [Defense]: A datatype that various visitor-difficulty mappigns
|
//! - [Defense]: A datatype that various visitor-difficulty mappigns
|
||||||
//! - [Visitor][crate::message::Visitor]: Smallest unit of traffic, usually a single request. The more you have, the busier
|
//! - [Visitor][crate::mcaptcha::Visitor]: Smallest unit of traffic, usually a single request. The more you have, the busier
|
||||||
//! your service is. Determines mCaptcha defense defense
|
//! your service is. Determines mCaptcha defense defense
|
||||||
//! - Visitor threshold: The threshold at which [MCaptcha] will adjust defense defense
|
//! - Visitor threshold: The threshold at which [MCaptcha] will adjust defense defense
|
||||||
|
//! - [Cache][crate::cache] : A datatype that implements [Save][crate::cache::Save]. Used to store
|
||||||
|
//! PoW requirements to defend against replay attacks and dictionary attacks.
|
||||||
|
//! - [Master][crate::master::Master]: A datatype that manages [MCaptcha][crate::mcaptcha::MCaptcha] actors. Works like a DNS for [Visitor][crate::mcaptcha::Visitor] messages.
|
||||||
|
//! - [System][crate::system::System]: mCaptcha system that manages cache, master and provides
|
||||||
|
//! useful abstractions. An mCaptcha system/instance can have only a single
|
||||||
|
//! [System][crate::system::System]
|
||||||
//!
|
//!
|
||||||
//! ## Example:
|
//! ## Example:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use m_captcha::{LevelBuilder, cache::HashCache, DefenseBuilder, message::Visitor, MCaptchaBuilder};
|
//! use m_captcha::{
|
||||||
|
//! cache::HashCache,
|
||||||
|
//! master::{AddSiteBuilder, Master},
|
||||||
|
//! pow::{ConfigBuilder, Work},
|
||||||
|
//! system::SystemBuilder,
|
||||||
|
//! DefenseBuilder, LevelBuilder, MCaptchaBuilder,
|
||||||
|
//! };
|
||||||
//! // traits from actix needs to be in scope for starting actor
|
//! // traits from actix needs to be in scope for starting actor
|
||||||
//! use actix::prelude::*;
|
//! use actix::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! #[actix_rt::main]
|
//! #[actix_rt::main]
|
||||||
//! async fn main() -> std::io::Result<()> {
|
//! async fn main() -> std::io::Result<()> {
|
||||||
//! // configure defense
|
//! // start cahce actor
|
||||||
|
//! // cache is used to store PoW requirements that are sent to clients
|
||||||
|
//! // This way, it can be verified that the client computed work over a config
|
||||||
|
//! // that _we_ sent. Offers protection against rainbow tables powered dictionary attacks
|
||||||
|
//! let cache = HashCache::default().start();
|
||||||
|
//!
|
||||||
|
//! // create PoW config with unique salt. Salt has to be safely guarded.
|
||||||
|
//! // salts protect us from replay attacks
|
||||||
|
//! let pow = ConfigBuilder::default()
|
||||||
|
//! .salt("myrandomsaltisnotlongenoug".into())
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! // start master actor. Master actor is responsible for managing MCaptcha actors
|
||||||
|
//! // each mCaptcha system should have only one master
|
||||||
|
//! let master = Master::new().start();
|
||||||
|
//!
|
||||||
|
//! // Create system. System encapsulates master and cache and provides useful abstraction
|
||||||
|
//! // each mCaptcha system should have only one system
|
||||||
|
//! let system = SystemBuilder::default()
|
||||||
|
//! .master(master)
|
||||||
|
//! .cache(cache)
|
||||||
|
//! .pow(pow.clone())
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! // configure defense. This is a per site configuration. A site can have several levels
|
||||||
|
//! // of defenses configured
|
||||||
//! let defense = DefenseBuilder::default()
|
//! let defense = DefenseBuilder::default()
|
||||||
//! // add as many defense as you see fit
|
//! // add as many defense as you see fit
|
||||||
//! .add_level(
|
//! .add_level(
|
||||||
|
@ -74,9 +113,8 @@
|
||||||
//! .build()
|
//! .build()
|
||||||
//! .unwrap();
|
//! .unwrap();
|
||||||
//!
|
//!
|
||||||
//! //let cache = HashCache::default().start();
|
//! // create and start MCaptcha actor that uses the above defense configuration
|
||||||
//!
|
//! // This is what manages the difficulty factor of sites that an mCaptcha protects
|
||||||
//! // create and start MCaptcha actor
|
|
||||||
//! let mcaptcha = MCaptchaBuilder::default()
|
//! let mcaptcha = MCaptchaBuilder::default()
|
||||||
//! .defense(defense)
|
//! .defense(defense)
|
||||||
//! // leaky bucket algorithm's emission interval
|
//! // leaky bucket algorithm's emission interval
|
||||||
|
@ -86,8 +124,38 @@
|
||||||
//! .unwrap()
|
//! .unwrap()
|
||||||
//! .start();
|
//! .start();
|
||||||
//!
|
//!
|
||||||
//! // increment count when user visits protected routes
|
//! // unique value identifying an MCaptcha actor
|
||||||
//! mcaptcha.send(Visitor).await.unwrap();
|
//! let mcaptcha_name = "batsense.net";
|
||||||
|
//!
|
||||||
|
//! // add MCaptcha to Master
|
||||||
|
//! let msg = AddSiteBuilder::default()
|
||||||
|
//! .id(mcaptcha_name.into())
|
||||||
|
//! .addr(mcaptcha.clone())
|
||||||
|
//! .build()
|
||||||
|
//! .unwrap();
|
||||||
|
//! system.master.send(msg).await.unwrap();
|
||||||
|
//!
|
||||||
|
//! // Get PoW config. Should be called everytime there's a visitor for a
|
||||||
|
//! // managed site(here mcaptcha_name)
|
||||||
|
//! let work_req = system.get_pow(mcaptcha_name.into()).await.unwrap();
|
||||||
|
//!
|
||||||
|
//! // the following computation should be done on the client but for the purpose
|
||||||
|
//! // of this illustration, we are going to do it on the server it self
|
||||||
|
//! let work = pow
|
||||||
|
//! .prove_work(&work_req.string, work_req.difficulty_factor)
|
||||||
|
//! .unwrap();
|
||||||
|
//!
|
||||||
|
//! // the payload that the client sends to the server
|
||||||
|
//! let payload = Work {
|
||||||
|
//! string: work_req.string,
|
||||||
|
//! result: work.result,
|
||||||
|
//! nonce: work.nonce,
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! // Server evaluates client's work. Returns true if everything
|
||||||
|
//! // checksout and Err() if something fishy is happening
|
||||||
|
//! let res = system.verify_pow(payload.clone()).await.unwrap();
|
||||||
|
//! assert!(res);
|
||||||
//!
|
//!
|
||||||
//! Ok(())
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
|
@ -98,11 +166,6 @@ pub mod errors;
|
||||||
pub mod master;
|
pub mod master;
|
||||||
pub mod mcaptcha;
|
pub mod mcaptcha;
|
||||||
|
|
||||||
/// message datatypes to interact with [MCaptcha] actor
|
|
||||||
pub mod message {
|
|
||||||
pub use crate::mcaptcha::Visitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// message datatypes to interact with [MCaptcha] actor
|
/// message datatypes to interact with [MCaptcha] actor
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod pow;
|
pub mod pow;
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
//!
|
//!
|
||||||
//! ## Usage:
|
//! ## Usage:
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use m_captcha::{message::Visitor, MCaptchaBuilder, cache::HashCache, LevelBuilder, DefenseBuilder};
|
//! use m_captcha::{mcaptcha::Visitor, MCaptchaBuilder, cache::HashCache, LevelBuilder, DefenseBuilder};
|
||||||
//! // traits from actix needs to be in scope for starting actor
|
//! // traits from actix needs to be in scope for starting actor
|
||||||
//! use actix::prelude::*;
|
//! use actix::prelude::*;
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
use pow_sha256::PoW;
|
use pow_sha256::PoW;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
pub use pow_sha256::ConfigBuilder;
|
||||||
|
|
||||||
/// PoW requirement datatype that is be sent to clients for generating PoW
|
/// PoW requirement datatype that is be sent to clients for generating PoW
|
||||||
#[derive(Clone, Serialize, Debug)]
|
#[derive(Clone, Serialize, Debug)]
|
||||||
pub struct PoWConfig {
|
pub struct PoWConfig {
|
||||||
|
|
|
@ -24,7 +24,6 @@ use crate::cache::messages;
|
||||||
use crate::cache::Save;
|
use crate::cache::Save;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::master::Master;
|
use crate::master::Master;
|
||||||
//use crate::models::*;
|
|
||||||
use crate::pow::*;
|
use crate::pow::*;
|
||||||
|
|
||||||
/// struct describing various bits of data required for an mCaptcha system
|
/// struct describing various bits of data required for an mCaptcha system
|
||||||
|
@ -33,7 +32,6 @@ pub struct System<T: Save> {
|
||||||
pub master: Addr<Master>,
|
pub master: Addr<Master>,
|
||||||
cache: Addr<T>,
|
cache: Addr<T>,
|
||||||
pow: Config,
|
pow: Config,
|
||||||
// db: PgPool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> System<T>
|
impl<T> System<T>
|
||||||
|
@ -65,62 +63,20 @@ where
|
||||||
|
|
||||||
let string = work.string.clone();
|
let string = work.string.clone();
|
||||||
let msg = Retrive(string.clone());
|
let msg = Retrive(string.clone());
|
||||||
let difficulty = self.cache.send(msg).await.unwrap();
|
|
||||||
let pow: PoW<String> = work.into();
|
let pow: PoW<String> = work.into();
|
||||||
|
|
||||||
|
let difficulty = self.cache.send(msg).await.unwrap()?;
|
||||||
match difficulty {
|
match difficulty {
|
||||||
Ok(Some(difficulty)) => {
|
Some(difficulty) => {
|
||||||
if self.pow.is_sufficient_difficulty(&pow, difficulty) {
|
if self.pow.is_sufficient_difficulty(&pow, difficulty) {
|
||||||
Ok(self.pow.is_valid_proof(&pow, &string))
|
Ok(self.pow.is_valid_proof(&pow, &string))
|
||||||
} else {
|
} else {
|
||||||
Err(CaptchaError::InsuffiencientDifficulty)
|
Err(CaptchaError::InsuffiencientDifficulty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => Err(CaptchaError::StringNotFound),
|
None => Err(CaptchaError::StringNotFound),
|
||||||
Err(_) => Err(CaptchaError::Default),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub async fn register(&self, u: &Users) {
|
|
||||||
// sqlx::query!("INSERT INTO mcaptcha_users (name) VALUES ($1)", u.name)
|
|
||||||
// .execute(&self.db)
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub async fn levels(&self, l: &Levels) {
|
|
||||||
// sqlx::query!(
|
|
||||||
// "INSERT INTO mcaptcha_levels (id, difficulty_factor, visitor_threshold) VALUES ($1, $2, $3)",
|
|
||||||
// l.id,
|
|
||||||
// l.difficulty_factor,
|
|
||||||
// l.visitor_threshold
|
|
||||||
// )
|
|
||||||
// .execute(&self.db)
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// pub async fn add_mcaptcha(&self, m: &MCaptchaSystem) {
|
|
||||||
// sqlx::query!(
|
|
||||||
// "INSERT INTO mcaptcha_config (id, name, duration) VALUES ($1, $2, $3)",
|
|
||||||
// m.id,
|
|
||||||
// m.name,
|
|
||||||
// m.duration
|
|
||||||
// )
|
|
||||||
// .execute(&self.db)
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// async fn init_mcaptcha(&self, m: &MCaptchaSystem) {
|
|
||||||
// let id = sqlx::query_as!(
|
|
||||||
// Duration,
|
|
||||||
// "SELECT duration FROM mcaptcha_config WHERE id = ($1)",
|
|
||||||
// m.id,
|
|
||||||
// )
|
|
||||||
// .fetch_one(&self.db)
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
18
src/utils.rs
18
src/utils.rs
|
@ -1,3 +1,21 @@
|
||||||
|
/*
|
||||||
|
* mCaptcha - A proof of work based DoS protection system
|
||||||
|
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
// utility function to get a randomly generated string
|
// utility function to get a randomly generated string
|
||||||
// of size len
|
// of size len
|
||||||
pub fn get_random(len: usize) -> String {
|
pub fn get_random(len: usize) -> String {
|
||||||
|
|
Loading…
Reference in a new issue