examples, doc and v0.1 prep

This commit is contained in:
Aravinth Manivannan 2021-03-09 17:37:46 +05:30
parent 7dbda0670c
commit 8c4f885a85
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
9 changed files with 227 additions and 71 deletions

0
CHANGELOG.md Normal file
View file

View file

@ -7,18 +7,25 @@
[![Documentation](https://img.shields.io/badge/docs-master-blue)](https://mcaptcha.github.io/mCaptcha/m_captcha/index.html)
![CI (Linux)](<https://github.com/mCaptcha/mCaptcha/workflows/CI%20(Linux)/badge.svg>)
[![dependency status](https://deps.rs/repo/github/mCaptcha/mCaptcha/status.svg)](https://deps.rs/repo/github/mCaptcha/mCaptcha)
[![AGPL License](https://img.shields.io/badge/license-AGPL-blue.svg)](http://www.gnu.org/licenses/agpl-3.0)
<br />
[![codecov](https://codecov.io/gh/mCaptcha/mCaptcha/branch/master/graph/badge.svg)](https://codecov.io/gh/mCaptcha/mCaptcha)
[![Documentation](https://img.shields.io/badge/matrix-community-purple)](https://matrix.to/#/+mcaptcha:matrix.batsense.net)
</div>
### STATUS: ACTIVE DEVELOPMENT (fancy word for unusable)
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
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?
- Free software, privacy focused

115
examples/simple.rs Normal file
View 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(())
}

View file

@ -63,11 +63,6 @@ pub enum CaptchaError {
/// isn't in cache
#[display(fmt = "String now found")]
StringNotFound,
/// Catcha all default error
/// used for development, must remove before production
#[display(fmt = "TODO remove before prod")]
Default,
}
/// [Result] datatype for m_captcha

View file

@ -31,20 +31,59 @@
//! - Difficulty(Factor): Minimum ammount of work that a client must do to make a valid
//! request.
//! - [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
//! - 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:
//!
//! ```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
//! use actix::prelude::*;
//!
//! #[actix_rt::main]
//! 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()
//! // add as many defense as you see fit
//! .add_level(
@ -74,20 +113,49 @@
//! .build()
//! .unwrap();
//!
//! //let cache = HashCache::default().start();
//!
//! // create and start MCaptcha actor
//! // 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)
//! // .cache(cache)
//! .build()
//! .unwrap()
//! .start();
//!
//! // increment count when user visits protected routes
//! mcaptcha.send(Visitor).await.unwrap();
//! // 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(())
//! }
@ -98,11 +166,6 @@ pub mod errors;
pub mod master;
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
pub mod cache;
pub mod pow;

View file

@ -19,7 +19,7 @@
//!
//! ## Usage:
//! ```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
//! use actix::prelude::*;
//!

View file

@ -20,6 +20,8 @@
use pow_sha256::PoW;
use serde::Serialize;
pub use pow_sha256::ConfigBuilder;
/// PoW requirement datatype that is be sent to clients for generating PoW
#[derive(Clone, Serialize, Debug)]
pub struct PoWConfig {

View file

@ -24,7 +24,6 @@ use crate::cache::messages;
use crate::cache::Save;
use crate::errors::*;
use crate::master::Master;
//use crate::models::*;
use crate::pow::*;
/// struct describing various bits of data required for an mCaptcha system
@ -33,7 +32,6 @@ pub struct System<T: Save> {
pub master: Addr<Master>,
cache: Addr<T>,
pow: Config,
// db: PgPool,
}
impl<T> System<T>
@ -65,62 +63,20 @@ where
let string = work.string.clone();
let msg = Retrive(string.clone());
let difficulty = self.cache.send(msg).await.unwrap();
let pow: PoW<String> = work.into();
let difficulty = self.cache.send(msg).await.unwrap()?;
match difficulty {
Ok(Some(difficulty)) => {
Some(difficulty) => {
if self.pow.is_sufficient_difficulty(&pow, difficulty) {
Ok(self.pow.is_valid_proof(&pow, &string))
} else {
Err(CaptchaError::InsuffiencientDifficulty)
}
}
Ok(None) => Err(CaptchaError::StringNotFound),
Err(_) => Err(CaptchaError::Default),
None => Err(CaptchaError::StringNotFound),
}
}
// 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)]

View file

@ -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
// of size len
pub fn get_random(len: usize) -> String {