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 @@
|
|||
[![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
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
|
||||
#[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
|
||||
|
|
91
src/lib.rs
91
src/lib.rs
|
@ -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;
|
||||
|
|
|
@ -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::*;
|
||||
//!
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
|
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
|
||||
// of size len
|
||||
pub fn get_random(len: usize) -> String {
|
||||
|
|
Loading…
Reference in a new issue