redis generic client
This commit is contained in:
parent
05dd9bbbb6
commit
07f124fb82
10 changed files with 239 additions and 213 deletions
|
@ -1,7 +1,7 @@
|
||||||
use libmcaptcha::{
|
use libmcaptcha::{
|
||||||
cache::{messages::VerifyCaptchaResult, HashCache},
|
cache::{messages::VerifyCaptchaResult, HashCache},
|
||||||
master::embedded::master::Master,
|
master::embedded::master::Master,
|
||||||
master::AddSiteBuilder,
|
master::messages::AddSiteBuilder,
|
||||||
pow::{ConfigBuilder, Work},
|
pow::{ConfigBuilder, Work},
|
||||||
system::SystemBuilder,
|
system::SystemBuilder,
|
||||||
DefenseBuilder, LevelBuilder, MCaptchaBuilder,
|
DefenseBuilder, LevelBuilder, MCaptchaBuilder,
|
||||||
|
|
17
src/lib.rs
17
src/lib.rs
|
@ -49,7 +49,7 @@
|
||||||
//! use libmcaptcha::{
|
//! use libmcaptcha::{
|
||||||
//! cache::{messages::VerifyCaptchaResult, HashCache},
|
//! cache::{messages::VerifyCaptchaResult, HashCache},
|
||||||
//! master::embedded::master:: Master,
|
//! master::embedded::master:: Master,
|
||||||
//! master::AddSiteBuilder,
|
//! master::messages::AddSiteBuilder,
|
||||||
//! pow::{ConfigBuilder, Work},
|
//! pow::{ConfigBuilder, Work},
|
||||||
//! system::SystemBuilder,
|
//! system::SystemBuilder,
|
||||||
//! DefenseBuilder, LevelBuilder, MCaptchaBuilder,
|
//! DefenseBuilder, LevelBuilder, MCaptchaBuilder,
|
||||||
|
@ -187,6 +187,9 @@ pub mod defense;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod master;
|
pub mod master;
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
mod redis;
|
||||||
|
|
||||||
/// message datatypes to interact with [MCaptcha] actor
|
/// message datatypes to interact with [MCaptcha] actor
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
@ -200,18 +203,20 @@ mod utils;
|
||||||
|
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub use crate::cache::hashcache::HashCache;
|
pub use crate::cache::hashcache::HashCache;
|
||||||
|
|
||||||
pub use crate::defense::{Defense, DefenseBuilder, LevelBuilder};
|
|
||||||
pub use crate::master::{AddVisitorResult, CreateMCaptcha};
|
|
||||||
pub use crate::mcaptcha::{MCaptcha, MCaptchaBuilder};
|
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub use master::embedded::counter::Counter;
|
pub use master::embedded::counter::Counter;
|
||||||
|
|
||||||
|
pub use crate::defense::{Defense, DefenseBuilder, LevelBuilder};
|
||||||
|
pub use crate::master::AddVisitorResult;
|
||||||
|
pub use crate::master::CreateMCaptcha;
|
||||||
|
pub use crate::mcaptcha::{MCaptcha, MCaptchaBuilder};
|
||||||
|
|
||||||
#[cfg(feature = "minimal")]
|
#[cfg(feature = "minimal")]
|
||||||
pub mod dev {
|
pub mod dev {
|
||||||
|
pub use super::AddVisitorResult;
|
||||||
|
pub use super::CreateMCaptcha;
|
||||||
pub use crate::defense;
|
pub use crate::defense;
|
||||||
pub use crate::defense::{Defense, DefenseBuilder, LevelBuilder};
|
pub use crate::defense::{Defense, DefenseBuilder, LevelBuilder};
|
||||||
pub use crate::master::{AddVisitorResult, CreateMCaptcha};
|
|
||||||
pub use crate::mcaptcha;
|
pub use crate::mcaptcha;
|
||||||
pub use crate::mcaptcha::{MCaptcha, MCaptchaBuilder};
|
pub use crate::mcaptcha::{MCaptcha, MCaptchaBuilder};
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,7 +212,7 @@ pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::defense::*;
|
use crate::defense::*;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::master::MCaptchaBuilder;
|
use crate::mcaptcha::MCaptchaBuilder;
|
||||||
|
|
||||||
// constants for testing
|
// constants for testing
|
||||||
// (visitor count, level)
|
// (visitor count, level)
|
||||||
|
|
|
@ -27,8 +27,8 @@ use log::info;
|
||||||
|
|
||||||
use super::counter::Counter;
|
use super::counter::Counter;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use crate::master::messages::{AddSite, AddVisitor};
|
||||||
use crate::master::Master as MasterTrait;
|
use crate::master::Master as MasterTrait;
|
||||||
use crate::master::{AddSite, AddVisitor};
|
|
||||||
|
|
||||||
/// This Actor manages the [Counter] actors.
|
/// This Actor manages the [Counter] actors.
|
||||||
/// A service can have several [Counter] actors with
|
/// A service can have several [Counter] actors with
|
||||||
|
@ -191,7 +191,7 @@ impl Handler<AddSite> for Master {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::master::embedded::counter::tests::*;
|
use crate::master::embedded::counter::tests::*;
|
||||||
use crate::master::AddSiteBuilder;
|
use crate::master::messages::AddSiteBuilder;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn master_actor_works() {
|
async fn master_actor_works() {
|
||||||
|
|
|
@ -16,49 +16,28 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
//! [Master] actor module that manages [MCaptcha] actors
|
//! [Master] actor module that manages [MCaptcha] actors
|
||||||
#[cfg(feature = "full")]
|
|
||||||
use std::sync::mpsc::Receiver;
|
|
||||||
|
|
||||||
#[cfg(feature = "full")]
|
|
||||||
use actix::dev::*;
|
|
||||||
#[cfg(feature = "full")]
|
|
||||||
use derive_builder::Builder;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "full")]
|
use crate::mcaptcha::*;
|
||||||
use crate::errors::CaptchaResult;
|
|
||||||
|
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod embedded;
|
pub mod embedded;
|
||||||
#[allow(
|
|
||||||
unused_variables,
|
|
||||||
unused_imports,
|
|
||||||
unused_variables,
|
|
||||||
dead_code,
|
|
||||||
unused_macros
|
|
||||||
)]
|
|
||||||
use crate::mcaptcha::*;
|
|
||||||
#[allow(
|
|
||||||
unused_variables,
|
|
||||||
unused_imports,
|
|
||||||
unused_variables,
|
|
||||||
dead_code,
|
|
||||||
unused_macros
|
|
||||||
)]
|
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod redis;
|
pub mod redis;
|
||||||
|
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
/// Describes actor handler trait impls that are required by a cache implementation
|
/// Describes actor handler trait impls that are required by a cache implementation
|
||||||
pub trait Master: actix::Actor + actix::Handler<AddVisitor> + actix::Handler<AddSite> {}
|
pub trait Master:
|
||||||
|
actix::Actor + actix::Handler<messages::AddVisitor> + actix::Handler<messages::AddSite>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
//+ actix::Handler<AddSite>
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreateMCaptcha {
|
||||||
/// Message to add visitor to an [MCaptcha] actor
|
pub levels: Vec<crate::defense::Level>,
|
||||||
#[derive(Message)]
|
pub duration: u64,
|
||||||
#[cfg(feature = "full")]
|
}
|
||||||
#[rtype(result = "Receiver<CaptchaResult<Option<AddVisitorResult>>>")]
|
|
||||||
pub struct AddVisitor(pub String);
|
|
||||||
|
|
||||||
/// Struct representing the return datatime of
|
/// Struct representing the return datatime of
|
||||||
/// [AddVisitor] message. Contains MCaptcha lifetime
|
/// [AddVisitor] message. Contains MCaptcha lifetime
|
||||||
|
@ -69,15 +48,6 @@ pub struct AddVisitorResult {
|
||||||
pub difficulty_factor: u32,
|
pub difficulty_factor: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message to add an [Counter] actor to [Master]
|
|
||||||
#[derive(Message, Builder)]
|
|
||||||
#[rtype(result = "()")]
|
|
||||||
#[cfg(feature = "full")]
|
|
||||||
pub struct AddSite {
|
|
||||||
pub id: String,
|
|
||||||
pub mcaptcha: MCaptcha,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AddVisitorResult {
|
impl AddVisitorResult {
|
||||||
pub fn new(m: &MCaptcha) -> Self {
|
pub fn new(m: &MCaptcha) -> Self {
|
||||||
AddVisitorResult {
|
AddVisitorResult {
|
||||||
|
@ -87,9 +57,26 @@ impl AddVisitorResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "minimal")]
|
#[cfg(feature = "full")]
|
||||||
#[derive(Serialize, Deserialize)]
|
pub mod messages {
|
||||||
pub struct CreateMCaptcha {
|
use std::sync::mpsc::Receiver;
|
||||||
pub levels: Vec<crate::defense::Level>,
|
|
||||||
pub duration: u64,
|
use actix::dev::*;
|
||||||
|
use derive_builder::Builder;
|
||||||
|
|
||||||
|
use crate::errors::CaptchaResult;
|
||||||
|
use crate::mcaptcha::MCaptcha;
|
||||||
|
|
||||||
|
/// Message to add visitor to an [MCaptcha] actor
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "Receiver<CaptchaResult<Option<super::AddVisitorResult>>>")]
|
||||||
|
pub struct AddVisitor(pub String);
|
||||||
|
|
||||||
|
/// Message to add an [Counter] actor to [Master]
|
||||||
|
#[derive(Message, Builder)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct AddSite {
|
||||||
|
pub id: String,
|
||||||
|
pub mcaptcha: MCaptcha,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,51 +15,50 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::cell::RefMut;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use redis::cluster::ClusterClient;
|
|
||||||
use redis::RedisError;
|
|
||||||
//use redis::cluster::ClusterConnection;
|
|
||||||
use redis::Client;
|
|
||||||
//use redis::Connection;
|
|
||||||
use redis::RedisResult;
|
|
||||||
use redis::Value;
|
use redis::Value;
|
||||||
use redis::{aio::Connection, cluster::ClusterConnection};
|
|
||||||
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::master::{AddSite, AddVisitor, AddVisitorResult, CreateMCaptcha};
|
use crate::master::messages::{AddSite, AddVisitor};
|
||||||
|
use crate::master::AddVisitorResult;
|
||||||
|
use crate::master::CreateMCaptcha;
|
||||||
|
use crate::redis::Redis;
|
||||||
|
use crate::redis::RedisConfig;
|
||||||
|
use crate::redis::RedisConnection;
|
||||||
|
|
||||||
pub enum RedisConnection {
|
/// Redis instance with mCaptcha Redis module loaded
|
||||||
Single(Rc<RefCell<Connection>>),
|
pub struct MCaptchaRedis(Redis);
|
||||||
Cluster(Rc<RefCell<ClusterConnection>>),
|
|
||||||
}
|
/// Redis instance with mCaptcha Redis module loaded
|
||||||
|
pub struct MCaptchaRedisConnection(RedisConnection);
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const GET: &str = "MCAPTCHA_CACHE.GET";
|
const GET: &str = "MCAPTCHA_CACHE.GET";
|
||||||
#[allow(dead_code)]
|
|
||||||
const ADD_VISITOR: &str = "MCAPTCHA_CACHE.ADD_VISITOR";
|
const ADD_VISITOR: &str = "MCAPTCHA_CACHE.ADD_VISITOR";
|
||||||
#[allow(dead_code)]
|
|
||||||
const DEL: &str = "MCAPTCHA_CACHE.DELETE_CAPTCHA";
|
const DEL: &str = "MCAPTCHA_CACHE.DELETE_CAPTCHA";
|
||||||
#[allow(dead_code)]
|
|
||||||
const ADD_CAPTCHA: &str = "MCAPTCHA_CACHE.ADD_CAPTCHA";
|
const ADD_CAPTCHA: &str = "MCAPTCHA_CACHE.ADD_CAPTCHA";
|
||||||
#[allow(dead_code)]
|
|
||||||
const CAPTCHA_EXISTS: &str = "MCAPTCHA_CACHE.CAPTCHA_EXISTS";
|
const CAPTCHA_EXISTS: &str = "MCAPTCHA_CACHE.CAPTCHA_EXISTS";
|
||||||
|
|
||||||
const MODULE_NAME: &str = "mcaptcha_cahce";
|
const MODULE_NAME: &str = "mcaptcha_cahce";
|
||||||
macro_rules! exec {
|
|
||||||
($cmd:expr, $con:expr) => {
|
impl MCaptchaRedis {
|
||||||
match *$con {
|
pub async fn new(redis: RedisConfig) -> CaptchaResult<Self> {
|
||||||
RedisConnection::Single(con) => $cmd.query_async(&mut *con.borrow_mut()).await,
|
let redis = Redis::new(redis).await?;
|
||||||
RedisConnection::Cluster(con) => $cmd.query(&mut *con.borrow_mut()),
|
let m = MCaptchaRedis(redis);
|
||||||
}
|
m.get_client().is_module_loaded().await?;
|
||||||
};
|
Ok(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_client(&self) -> MCaptchaRedisConnection {
|
||||||
|
MCaptchaRedisConnection(self.0.get_client())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedisConnection {
|
impl MCaptchaRedisConnection {
|
||||||
pub async fn is_module_loaded(&self) -> CaptchaResult<()> {
|
pub async fn is_module_loaded(&self) -> CaptchaResult<()> {
|
||||||
let modules: Vec<Vec<String>> = exec!(redis::cmd("MODULE").arg(&["LIST"]), &self).unwrap();
|
let modules: Vec<Vec<String>> = self
|
||||||
|
.0
|
||||||
|
.exec(redis::cmd("MODULE").arg(&["LIST"]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
for list in modules.iter() {
|
for list in modules.iter() {
|
||||||
match list.iter().find(|module| module.as_str() == MODULE_NAME) {
|
match list.iter().find(|module| module.as_str() == MODULE_NAME) {
|
||||||
|
@ -71,7 +70,12 @@ impl RedisConnection {
|
||||||
let commands = vec![ADD_VISITOR, ADD_CAPTCHA, DEL, CAPTCHA_EXISTS, GET];
|
let commands = vec![ADD_VISITOR, ADD_CAPTCHA, DEL, CAPTCHA_EXISTS, GET];
|
||||||
|
|
||||||
for cmd in commands.iter() {
|
for cmd in commands.iter() {
|
||||||
match exec!(redis::cmd("COMMAND").arg(&["INFO", cmd]), &self).unwrap() {
|
match self
|
||||||
|
.0
|
||||||
|
.exec(redis::cmd("COMMAND").arg(&["INFO", cmd]))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
Value::Bulk(mut val) => {
|
Value::Bulk(mut val) => {
|
||||||
match val.pop() {
|
match val.pop() {
|
||||||
Some(Value::Nil) => {
|
Some(Value::Nil) => {
|
||||||
|
@ -91,7 +95,7 @@ impl RedisConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_visitor(&self, msg: AddVisitor) -> CaptchaResult<Option<AddVisitorResult>> {
|
pub async fn add_visitor(&self, msg: AddVisitor) -> CaptchaResult<Option<AddVisitorResult>> {
|
||||||
let res: String = exec!(redis::cmd(ADD_VISITOR).arg(&[msg.0]), &self)?;
|
let res: String = self.0.exec(redis::cmd(ADD_VISITOR).arg(&[msg.0])).await?;
|
||||||
let res: AddVisitorResult = serde_json::from_str(&res).unwrap();
|
let res: AddVisitorResult = serde_json::from_str(&res).unwrap();
|
||||||
Ok(Some(res))
|
Ok(Some(res))
|
||||||
}
|
}
|
||||||
|
@ -100,12 +104,17 @@ impl RedisConnection {
|
||||||
let name = msg.id;
|
let name = msg.id;
|
||||||
let captcha: CreateMCaptcha = msg.mcaptcha.into();
|
let captcha: CreateMCaptcha = msg.mcaptcha.into();
|
||||||
let payload = serde_json::to_string(&captcha).unwrap();
|
let payload = serde_json::to_string(&captcha).unwrap();
|
||||||
exec!(redis::cmd(ADD_CAPTCHA).arg(&[name, payload]), &self)?;
|
self.0
|
||||||
|
.exec(redis::cmd(ADD_CAPTCHA).arg(&[name, payload]))
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_captcha_exists(&self, captcha: &str) -> CaptchaResult<bool> {
|
pub async fn check_captcha_exists(&self, captcha: &str) -> CaptchaResult<bool> {
|
||||||
let exists: usize = exec!(redis::cmd(CAPTCHA_EXISTS).arg(&[captcha]), &self)?;
|
let exists: usize = self
|
||||||
|
.0
|
||||||
|
.exec(redis::cmd(CAPTCHA_EXISTS).arg(&[captcha]))
|
||||||
|
.await?;
|
||||||
if exists == 1 {
|
if exists == 1 {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
} else if exists == 0 {
|
} else if exists == 0 {
|
||||||
|
@ -121,12 +130,12 @@ impl RedisConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_captcha(&self, captcha: &str) -> CaptchaResult<()> {
|
pub async fn delete_captcha(&self, captcha: &str) -> CaptchaResult<()> {
|
||||||
exec!(redis::cmd(DEL).arg(&[captcha]), &self)?;
|
self.0.exec(redis::cmd(DEL).arg(&[captcha])).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_visitors(&self, captcha: &str) -> CaptchaResult<usize> {
|
pub async fn get_visitors(&self, captcha: &str) -> CaptchaResult<usize> {
|
||||||
let visitors: usize = exec!(redis::cmd(GET).arg(&[captcha]), &self)?;
|
let visitors: usize = self.0.exec(redis::cmd(GET).arg(&[captcha])).await?;
|
||||||
Ok(visitors)
|
Ok(visitors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,32 +143,20 @@ impl RedisConnection {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::defense::{Level, LevelBuilder};
|
|
||||||
use crate::master::embedded::counter::tests::get_mcaptcha;
|
use crate::master::embedded::counter::tests::get_mcaptcha;
|
||||||
use crate::master::redis::master::{Master, Redis, RedisClient};
|
use crate::redis::*;
|
||||||
|
|
||||||
pub async fn connect(redis: &Redis) -> RedisConnection {
|
|
||||||
let redis = redis.connect();
|
|
||||||
match &redis {
|
|
||||||
RedisClient::Single(c) => {
|
|
||||||
let con = c.get_async_connection().await.unwrap();
|
|
||||||
RedisConnection::Single(Rc::new(RefCell::new(con)))
|
|
||||||
}
|
|
||||||
RedisClient::Cluster(c) => {
|
|
||||||
let con = c.get_connection().unwrap();
|
|
||||||
RedisConnection::Cluster(Rc::new(RefCell::new(con)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CAPTCHA_NAME: &str = "REDIS_CAPTCHA_TEST";
|
const CAPTCHA_NAME: &str = "REDIS_CAPTCHA_TEST";
|
||||||
const DURATION: usize = 10;
|
|
||||||
const REDIS_URL: &str = "redis://127.0.1.1/";
|
const REDIS_URL: &str = "redis://127.0.1.1/";
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn redis_master_works() {
|
async fn redis_master_works() {
|
||||||
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
|
let redis = Redis::new(RedisConfig::Single(REDIS_URL.into()))
|
||||||
let r = connect(&Redis::Single(REDIS_URL.into())).await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let r = MCaptchaRedis(redis);
|
||||||
|
let r = r.get_client();
|
||||||
{
|
{
|
||||||
let _ = r.delete_captcha(CAPTCHA_NAME).await;
|
let _ = r.delete_captcha(CAPTCHA_NAME).await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,84 +15,26 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::cell::RefMut;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use actix::dev::*;
|
use actix::dev::*;
|
||||||
use redis::cluster::ClusterClient;
|
|
||||||
use redis::RedisError;
|
|
||||||
//use redis::cluster::ClusterConnection;
|
|
||||||
use redis::Client;
|
|
||||||
//use redis::Connection;
|
|
||||||
use redis::RedisResult;
|
|
||||||
use redis::Value;
|
|
||||||
use redis::{aio::Connection, cluster::ClusterConnection};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::defense::Level;
|
use super::connection::MCaptchaRedis;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::master::AddVisitorResult;
|
use crate::master::messages::{AddSite, AddVisitor};
|
||||||
use crate::master::{AddSite, AddVisitor, CreateMCaptcha, Master as MasterTrait};
|
use crate::master::Master as MasterTrait;
|
||||||
|
use crate::redis::RedisConfig;
|
||||||
use super::connection::RedisConnection;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum Redis {
|
|
||||||
Single(String),
|
|
||||||
Cluster(Vec<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Redis {
|
|
||||||
pub fn connect(&self) -> RedisClient {
|
|
||||||
match self {
|
|
||||||
Self::Single(url) => {
|
|
||||||
let client = Client::open("redis://127.0.0.1/").unwrap();
|
|
||||||
RedisClient::Single(client)
|
|
||||||
}
|
|
||||||
Self::Cluster(nodes) => {
|
|
||||||
let cluster_client = ClusterClient::open(nodes.to_owned()).unwrap();
|
|
||||||
RedisClient::Cluster(cluster_client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum RedisClient {
|
|
||||||
Single(Client),
|
|
||||||
Cluster(ClusterClient),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Master {
|
pub struct Master {
|
||||||
pub redis: RedisClient,
|
pub redis: MCaptchaRedis,
|
||||||
pub con: Rc<RedisConnection>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Master {
|
impl Master {
|
||||||
pub async fn new(redis: Redis) -> CaptchaResult<Self> {
|
pub async fn new(redis: RedisConfig) -> CaptchaResult<Self> {
|
||||||
let (redis, con) = Self::connect(redis).await;
|
let redis = MCaptchaRedis::new(redis).await?;
|
||||||
con.is_module_loaded().await?;
|
let master = Self { redis };
|
||||||
let con = Rc::new(con);
|
|
||||||
let master = Self { redis, con };
|
|
||||||
Ok(master)
|
Ok(master)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect(redis: Redis) -> (RedisClient, RedisConnection) {
|
|
||||||
let redis = redis.connect();
|
|
||||||
let client = match &redis {
|
|
||||||
RedisClient::Single(c) => {
|
|
||||||
let con = c.get_async_connection().await.unwrap();
|
|
||||||
RedisConnection::Single(Rc::new(RefCell::new(con)))
|
|
||||||
}
|
|
||||||
RedisClient::Cluster(c) => {
|
|
||||||
let con = c.get_connection().unwrap();
|
|
||||||
RedisConnection::Cluster(Rc::new(RefCell::new(con)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(redis, client)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterTrait for Master {}
|
impl MasterTrait for Master {}
|
||||||
|
@ -107,7 +49,7 @@ impl Handler<AddVisitor> for Master {
|
||||||
fn handle(&mut self, m: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, m: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
let con = Rc::clone(&self.con);
|
let con = self.redis.get_client();
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let res = con.add_visitor(m).await;
|
let res = con.add_visitor(m).await;
|
||||||
tx.send(res).unwrap()
|
tx.send(res).unwrap()
|
||||||
|
@ -123,10 +65,9 @@ impl Handler<AddSite> for Master {
|
||||||
|
|
||||||
fn handle(&mut self, m: AddSite, ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, m: AddSite, ctx: &mut Self::Context) -> Self::Result {
|
||||||
//let (tx, rx) = mpsc::channel();
|
//let (tx, rx) = mpsc::channel();
|
||||||
|
let con = self.redis.get_client();
|
||||||
let con = Rc::clone(&self.con);
|
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let res = con.add_mcaptcha(m).await;
|
let _res = con.add_mcaptcha(m).await;
|
||||||
//tx.send(res).unwrap();
|
//tx.send(res).unwrap();
|
||||||
}
|
}
|
||||||
.into_actor(self);
|
.into_actor(self);
|
||||||
|
@ -137,33 +78,34 @@ impl Handler<AddSite> for Master {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::defense::{Level, LevelBuilder};
|
|
||||||
use crate::master::embedded::counter::tests::get_mcaptcha;
|
use crate::master::embedded::counter::tests::get_mcaptcha;
|
||||||
use crate::master::redis::connection::tests::connect;
|
use crate::master::redis::master::Master;
|
||||||
use crate::master::redis::master::{Master, Redis};
|
use crate::redis::RedisConfig;
|
||||||
|
|
||||||
const CAPTCHA_NAME: &str = "REDIS_MASTER_CAPTCHA_TEST";
|
const CAPTCHA_NAME: &str = "REDIS_MASTER_CAPTCHA_TEST";
|
||||||
const DURATION: usize = 10;
|
|
||||||
|
|
||||||
const REDIS_URL: &str = "redis://127.0.1.1/";
|
const REDIS_URL: &str = "redis://127.0.1.1/";
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn redis_master_works() {
|
async fn redis_master_works() {
|
||||||
let master = Master::new(Redis::Single(REDIS_URL.into())).await;
|
let master = Master::new(RedisConfig::Single(REDIS_URL.into())).await;
|
||||||
let client = redis::Client::open("redis://127.0.0.1/").unwrap();
|
let sec_master = Master::new(RedisConfig::Single(REDIS_URL.into())).await;
|
||||||
let r = connect(&Redis::Single(REDIS_URL.into())).await;
|
let r = sec_master.unwrap().redis.get_client();
|
||||||
|
|
||||||
assert!(master.is_ok());
|
assert!(master.is_ok());
|
||||||
let master = master.unwrap();
|
let master = master.unwrap();
|
||||||
{
|
{
|
||||||
let _ = r.delete_captcha(CAPTCHA_NAME).await;
|
let _ = master.redis.get_client().delete_captcha(CAPTCHA_NAME).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let addr = master.start();
|
let addr = master.start();
|
||||||
|
|
||||||
|
let mcaptcha = get_mcaptcha();
|
||||||
|
let duration = mcaptcha.get_duration();
|
||||||
|
|
||||||
let add_mcaptcha_msg = AddSite {
|
let add_mcaptcha_msg = AddSite {
|
||||||
id: CAPTCHA_NAME.into(),
|
id: CAPTCHA_NAME.into(),
|
||||||
mcaptcha: get_mcaptcha(),
|
mcaptcha,
|
||||||
};
|
};
|
||||||
addr.send(add_mcaptcha_msg).await.unwrap();
|
addr.send(add_mcaptcha_msg).await.unwrap();
|
||||||
|
|
||||||
|
@ -171,5 +113,8 @@ mod tests {
|
||||||
addr.send(add_visitor_msg).await.unwrap();
|
addr.send(add_visitor_msg).await.unwrap();
|
||||||
let visitors = r.get_visitors(CAPTCHA_NAME).await.unwrap();
|
let visitors = r.get_visitors(CAPTCHA_NAME).await.unwrap();
|
||||||
assert_eq!(visitors, 1);
|
assert_eq!(visitors, 1);
|
||||||
|
|
||||||
|
let timer_expire = std::time::Duration::new(duration, 0);
|
||||||
|
actix::clock::delay_for(timer_expire).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,23 +15,6 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::cell::RefMut;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
|
|
||||||
use actix::dev::*;
|
pub mod connection;
|
||||||
use redis::cluster::ClusterClient;
|
pub mod master;
|
||||||
use redis::RedisError;
|
|
||||||
//use redis::cluster::ClusterConnection;
|
|
||||||
use redis::Client;
|
|
||||||
//use redis::Connection;
|
|
||||||
use redis::RedisResult;
|
|
||||||
use redis::Value;
|
|
||||||
use redis::{aio::Connection, cluster::ClusterConnection};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
mod connection;
|
|
||||||
mod master;
|
|
||||||
|
|
||||||
pub use master::*;
|
|
||||||
|
|
112
src/redis.rs
Normal file
112
src/redis.rs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use redis::cluster::ClusterClient;
|
||||||
|
use redis::FromRedisValue;
|
||||||
|
//use redis::cluster::ClusterConnection;
|
||||||
|
use redis::Client;
|
||||||
|
//use redis::Connection;
|
||||||
|
use redis::{aio::Connection, cluster::ClusterConnection};
|
||||||
|
|
||||||
|
use crate::errors::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum RedisConfig {
|
||||||
|
Single(String),
|
||||||
|
Cluster(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedisConfig {
|
||||||
|
pub fn connect(&self) -> RedisClient {
|
||||||
|
match self {
|
||||||
|
Self::Single(url) => {
|
||||||
|
let client = Client::open(url.as_str()).unwrap();
|
||||||
|
RedisClient::Single(client)
|
||||||
|
}
|
||||||
|
Self::Cluster(nodes) => {
|
||||||
|
let cluster_client = ClusterClient::open(nodes.to_owned()).unwrap();
|
||||||
|
RedisClient::Cluster(cluster_client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RedisConnection {
|
||||||
|
Single(Rc<RefCell<Connection>>),
|
||||||
|
Cluster(Rc<RefCell<ClusterConnection>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedisConnection {
|
||||||
|
#[inline]
|
||||||
|
pub fn get_client(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Single(con) => Self::Single(Rc::clone(&con)),
|
||||||
|
Self::Cluster(con) => Self::Cluster(Rc::clone(&con)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub async fn exec<T: FromRedisValue>(&self, cmd: &mut redis::Cmd) -> redis::RedisResult<T> {
|
||||||
|
match self {
|
||||||
|
RedisConnection::Single(con) => cmd.query_async(&mut *con.borrow_mut()).await,
|
||||||
|
RedisConnection::Cluster(con) => cmd.query(&mut *con.borrow_mut()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum RedisClient {
|
||||||
|
Single(Client),
|
||||||
|
Cluster(ClusterClient),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Redis {
|
||||||
|
_client: RedisClient,
|
||||||
|
connection: RedisConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Redis {
|
||||||
|
pub async fn new(redis: RedisConfig) -> CaptchaResult<Self> {
|
||||||
|
let (_client, connection) = Self::connect(redis).await;
|
||||||
|
let master = Self {
|
||||||
|
_client,
|
||||||
|
connection,
|
||||||
|
};
|
||||||
|
Ok(master)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_client(&self) -> RedisConnection {
|
||||||
|
self.connection.get_client()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect(redis: RedisConfig) -> (RedisClient, RedisConnection) {
|
||||||
|
let redis = redis.connect();
|
||||||
|
let client = match &redis {
|
||||||
|
RedisClient::Single(c) => {
|
||||||
|
let con = c.get_async_connection().await.unwrap();
|
||||||
|
RedisConnection::Single(Rc::new(RefCell::new(con)))
|
||||||
|
}
|
||||||
|
RedisClient::Cluster(c) => {
|
||||||
|
let con = c.get_connection().unwrap();
|
||||||
|
RedisConnection::Cluster(Rc::new(RefCell::new(con)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(redis, client)
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ use pow_sha256::Config;
|
||||||
use crate::cache::messages::*;
|
use crate::cache::messages::*;
|
||||||
use crate::cache::Save;
|
use crate::cache::Save;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use crate::master::messages::*;
|
||||||
use crate::master::Master;
|
use crate::master::Master;
|
||||||
use crate::pow::*;
|
use crate::pow::*;
|
||||||
|
|
||||||
|
@ -42,13 +43,10 @@ where
|
||||||
+ ToEnvelope<T, CacheResult>
|
+ ToEnvelope<T, CacheResult>
|
||||||
+ ToEnvelope<T, VerifyCaptchaResult>,
|
+ ToEnvelope<T, VerifyCaptchaResult>,
|
||||||
X: Master,
|
X: Master,
|
||||||
<X as actix::Actor>::Context:
|
<X as actix::Actor>::Context: ToEnvelope<X, AddVisitor> + ToEnvelope<X, AddSite>,
|
||||||
ToEnvelope<X, crate::master::AddVisitor> + ToEnvelope<X, crate::master::AddSite>,
|
|
||||||
{
|
{
|
||||||
/// utility function to get difficulty factor of site `id` and cache it
|
/// utility function to get difficulty factor of site `id` and cache it
|
||||||
pub async fn get_pow(&self, id: String) -> Option<PoWConfig> {
|
pub async fn get_pow(&self, id: String) -> Option<PoWConfig> {
|
||||||
use crate::master::AddVisitor;
|
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.master
|
.master
|
||||||
.send(AddVisitor(id.clone()))
|
.send(AddVisitor(id.clone()))
|
||||||
|
@ -130,7 +128,6 @@ mod tests {
|
||||||
use crate::cache::HashCache;
|
use crate::cache::HashCache;
|
||||||
use crate::master::embedded::counter::tests::*;
|
use crate::master::embedded::counter::tests::*;
|
||||||
use crate::master::embedded::master::Master;
|
use crate::master::embedded::master::Master;
|
||||||
use crate::master::*;
|
|
||||||
|
|
||||||
const MCAPTCHA_NAME: &str = "batsense.net";
|
const MCAPTCHA_NAME: &str = "batsense.net";
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue