mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2025-02-18 01:19:47 +03:00
Merge pull request #128 from mCaptcha/feat-auto-captcha
Use time (in seconds) instead of difficulty factor to describe PoW
This commit is contained in:
commit
5722a5327c
13 changed files with 651 additions and 47 deletions
|
@ -34,8 +34,11 @@ enable_stats = true
|
||||||
|
|
||||||
[captcha.default_difficulty_strategy]
|
[captcha.default_difficulty_strategy]
|
||||||
avg_traffic_difficulty = 50000 # almost instant solution
|
avg_traffic_difficulty = 50000 # almost instant solution
|
||||||
|
#avg_traffic_time = 1 # almost instant solution
|
||||||
peak_sustainable_traffic_difficulty = 3000000 # roughly 1.5s
|
peak_sustainable_traffic_difficulty = 3000000 # roughly 1.5s
|
||||||
|
#peak_sustainable_traffic_time = 3
|
||||||
broke_my_site_traffic_difficulty = 5000000 # greater than 3.5s
|
broke_my_site_traffic_difficulty = 5000000 # greater than 3.5s
|
||||||
|
#broke_my_site_traffic_time = 5
|
||||||
duration = 30 # cooldown period in seconds
|
duration = 30 # cooldown period in seconds
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
|
|
|
@ -202,6 +202,13 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
|
||||||
captcha_key: &str,
|
captcha_key: &str,
|
||||||
) -> DBResult<TrafficPattern>;
|
) -> DBResult<TrafficPattern>;
|
||||||
|
|
||||||
|
/// Get all easy captcha configurations on instance
|
||||||
|
async fn get_all_easy_captchas(
|
||||||
|
&self,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<EasyCaptcha>>;
|
||||||
|
|
||||||
/// Delete traffic configuration
|
/// Delete traffic configuration
|
||||||
async fn delete_traffic_pattern(
|
async fn delete_traffic_pattern(
|
||||||
&self,
|
&self,
|
||||||
|
@ -383,6 +390,19 @@ pub struct AddNotification<'a> {
|
||||||
pub message: &'a str,
|
pub message: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||||
|
/// Represents Easy captcha configuration
|
||||||
|
pub struct EasyCaptcha {
|
||||||
|
/// traffic pattern of easy captcha
|
||||||
|
pub traffic_pattern: TrafficPattern,
|
||||||
|
/// captcha key/sitekey
|
||||||
|
pub key: String,
|
||||||
|
/// captcha description
|
||||||
|
pub description: String,
|
||||||
|
/// Owner of the captcha configuration
|
||||||
|
pub username: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
#[derive(Default, PartialEq, Serialize, Deserialize, Clone, Debug)]
|
||||||
/// User's traffic pattern; used in generating a captcha configuration
|
/// User's traffic pattern; used in generating a captcha configuration
|
||||||
pub struct TrafficPattern {
|
pub struct TrafficPattern {
|
||||||
|
|
|
@ -223,6 +223,11 @@ pub async fn database_works<'a, T: MCDatabase>(
|
||||||
tp
|
tp
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// get all traffic patterns
|
||||||
|
let patterns = db.get_all_easy_captchas(10, 0).await.unwrap();
|
||||||
|
assert_eq!(patterns.get(0).as_ref().unwrap().key, c.key);
|
||||||
|
assert_eq!(&patterns.get(0).unwrap().traffic_pattern, tp);
|
||||||
|
|
||||||
// delete traffic pattern
|
// delete traffic pattern
|
||||||
db.delete_traffic_pattern(p.username, c.key).await.unwrap();
|
db.delete_traffic_pattern(p.username, c.key).await.unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
|
|
80
db/db-sqlx-maria/.sqlx/query-d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64.json
generated
Normal file
80
db/db-sqlx-maria/.sqlx/query-d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64.json
generated
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "SELECT \n mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,\n mcaptcha_config.name,\n mcaptcha_users.name as username,\n mcaptcha_config.captcha_key\n FROM \n mcaptcha_sitekey_user_provided_avg_traffic \n INNER JOIN\n mcaptcha_config\n ON\n mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id\n INNER JOIN\n mcaptcha_users\n ON\n mcaptcha_config.user_id = mcaptcha_users.ID\n ORDER BY mcaptcha_config.config_id\n LIMIT ? OFFSET ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "avg_traffic",
|
||||||
|
"type_info": {
|
||||||
|
"type": "Long",
|
||||||
|
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "peak_sustainable_traffic",
|
||||||
|
"type_info": {
|
||||||
|
"type": "Long",
|
||||||
|
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "broke_my_site_traffic",
|
||||||
|
"type_info": {
|
||||||
|
"type": "Long",
|
||||||
|
"flags": "",
|
||||||
|
"char_set": 63,
|
||||||
|
"max_size": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "name",
|
||||||
|
"type_info": {
|
||||||
|
"type": "VarString",
|
||||||
|
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 224,
|
||||||
|
"max_size": 400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "username",
|
||||||
|
"type_info": {
|
||||||
|
"type": "VarString",
|
||||||
|
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 224,
|
||||||
|
"max_size": 400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "captcha_key",
|
||||||
|
"type_info": {
|
||||||
|
"type": "VarString",
|
||||||
|
"flags": "NOT_NULL | UNIQUE_KEY | NO_DEFAULT_VALUE",
|
||||||
|
"char_set": 224,
|
||||||
|
"max_size": 400
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "d587844217f202c23d29c3cb4c819551bc204dd459c956c41024fa74aadbba64"
|
||||||
|
}
|
|
@ -1273,6 +1273,66 @@ impl MCDatabase for Database {
|
||||||
Err(e) => Err(map_row_not_found_err(e, DBError::CaptchaNotFound)),
|
Err(e) => Err(map_row_not_found_err(e, DBError::CaptchaNotFound)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all easy captcha configurations on instance
|
||||||
|
async fn get_all_easy_captchas(
|
||||||
|
&self,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<EasyCaptcha>> {
|
||||||
|
struct InnerEasyCaptcha {
|
||||||
|
captcha_key: String,
|
||||||
|
name: String,
|
||||||
|
username: String,
|
||||||
|
peak_sustainable_traffic: i32,
|
||||||
|
avg_traffic: i32,
|
||||||
|
broke_my_site_traffic: Option<i32>,
|
||||||
|
}
|
||||||
|
let mut inner_res = sqlx::query_as!(
|
||||||
|
InnerEasyCaptcha,
|
||||||
|
"SELECT
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic,
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic,
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,
|
||||||
|
mcaptcha_config.name,
|
||||||
|
mcaptcha_users.name as username,
|
||||||
|
mcaptcha_config.captcha_key
|
||||||
|
FROM
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic
|
||||||
|
INNER JOIN
|
||||||
|
mcaptcha_config
|
||||||
|
ON
|
||||||
|
mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id
|
||||||
|
INNER JOIN
|
||||||
|
mcaptcha_users
|
||||||
|
ON
|
||||||
|
mcaptcha_config.user_id = mcaptcha_users.ID
|
||||||
|
ORDER BY mcaptcha_config.config_id
|
||||||
|
LIMIT ? OFFSET ?",
|
||||||
|
limit as i64,
|
||||||
|
offset as i64
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||||
|
let mut res = Vec::with_capacity(inner_res.len());
|
||||||
|
inner_res.drain(0..).for_each(|v| {
|
||||||
|
res.push(EasyCaptcha {
|
||||||
|
key: v.captcha_key,
|
||||||
|
description: v.name,
|
||||||
|
username: v.username,
|
||||||
|
traffic_pattern: TrafficPattern {
|
||||||
|
broke_my_site_traffic: v
|
||||||
|
.broke_my_site_traffic
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| *v as u32),
|
||||||
|
avg_traffic: v.avg_traffic as u32,
|
||||||
|
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT \n mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic, \n mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,\n mcaptcha_config.name,\n mcaptcha_users.name as username,\n mcaptcha_config.key\n FROM \n mcaptcha_sitekey_user_provided_avg_traffic \n INNER JOIN\n mcaptcha_config\n ON\n mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id\n INNER JOIN\n mcaptcha_users\n ON\n mcaptcha_config.user_id = mcaptcha_users.ID\n ORDER BY mcaptcha_config.config_id\n OFFSET $1 LIMIT $2; ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "avg_traffic",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "peak_sustainable_traffic",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "broke_my_site_traffic",
|
||||||
|
"type_info": "Int4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "name",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "username",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "key",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "f01a9c09c8722bc195f477a8c3ce6466d415e7c74665fa882eff4a8566e70577"
|
||||||
|
}
|
|
@ -669,13 +669,8 @@ impl MCDatabase for Database {
|
||||||
username: &str,
|
username: &str,
|
||||||
captcha_key: &str,
|
captcha_key: &str,
|
||||||
) -> DBResult<TrafficPattern> {
|
) -> DBResult<TrafficPattern> {
|
||||||
struct Traffic {
|
|
||||||
peak_sustainable_traffic: i32,
|
|
||||||
avg_traffic: i32,
|
|
||||||
broke_my_site_traffic: Option<i32>,
|
|
||||||
}
|
|
||||||
let res = sqlx::query_as!(
|
let res = sqlx::query_as!(
|
||||||
Traffic,
|
InnerTraffic,
|
||||||
"SELECT
|
"SELECT
|
||||||
avg_traffic,
|
avg_traffic,
|
||||||
peak_sustainable_traffic,
|
peak_sustainable_traffic,
|
||||||
|
@ -706,11 +701,67 @@ impl MCDatabase for Database {
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||||
Ok(TrafficPattern {
|
Ok(res.into())
|
||||||
broke_my_site_traffic: res.broke_my_site_traffic.as_ref().map(|v| *v as u32),
|
}
|
||||||
avg_traffic: res.avg_traffic as u32,
|
|
||||||
peak_sustainable_traffic: res.peak_sustainable_traffic as u32,
|
/// Get all easy captcha configurations on instance
|
||||||
})
|
async fn get_all_easy_captchas(
|
||||||
|
&self,
|
||||||
|
limit: usize,
|
||||||
|
offset: usize,
|
||||||
|
) -> DBResult<Vec<EasyCaptcha>> {
|
||||||
|
struct InnerEasyCaptcha {
|
||||||
|
key: String,
|
||||||
|
peak_sustainable_traffic: i32,
|
||||||
|
avg_traffic: i32,
|
||||||
|
broke_my_site_traffic: Option<i32>,
|
||||||
|
name: String,
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
let mut inner_res = sqlx::query_as!(
|
||||||
|
InnerEasyCaptcha,
|
||||||
|
"SELECT
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.avg_traffic,
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.peak_sustainable_traffic,
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic.broke_my_site_traffic,
|
||||||
|
mcaptcha_config.name,
|
||||||
|
mcaptcha_users.name as username,
|
||||||
|
mcaptcha_config.key
|
||||||
|
FROM
|
||||||
|
mcaptcha_sitekey_user_provided_avg_traffic
|
||||||
|
INNER JOIN
|
||||||
|
mcaptcha_config
|
||||||
|
ON
|
||||||
|
mcaptcha_config.config_id = mcaptcha_sitekey_user_provided_avg_traffic.config_id
|
||||||
|
INNER JOIN
|
||||||
|
mcaptcha_users
|
||||||
|
ON
|
||||||
|
mcaptcha_config.user_id = mcaptcha_users.ID
|
||||||
|
ORDER BY mcaptcha_config.config_id
|
||||||
|
OFFSET $1 LIMIT $2; ",
|
||||||
|
offset as i32,
|
||||||
|
limit as i32
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?;
|
||||||
|
let mut res = Vec::with_capacity(inner_res.len());
|
||||||
|
inner_res.drain(0..).for_each(|v| {
|
||||||
|
res.push(EasyCaptcha {
|
||||||
|
key: v.key,
|
||||||
|
description: v.name,
|
||||||
|
username: v.username,
|
||||||
|
traffic_pattern: TrafficPattern {
|
||||||
|
broke_my_site_traffic: v
|
||||||
|
.broke_my_site_traffic
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| *v as u32),
|
||||||
|
avg_traffic: v.avg_traffic as u32,
|
||||||
|
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete traffic configuration
|
/// Delete traffic configuration
|
||||||
|
@ -1345,3 +1396,19 @@ impl From<InternaleCaptchaConfig> for Captcha {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct InnerTraffic {
|
||||||
|
peak_sustainable_traffic: i32,
|
||||||
|
avg_traffic: i32,
|
||||||
|
broke_my_site_traffic: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InnerTraffic> for TrafficPattern {
|
||||||
|
fn from(v: InnerTraffic) -> Self {
|
||||||
|
TrafficPattern {
|
||||||
|
broke_my_site_traffic: v.broke_my_site_traffic.as_ref().map(|v| *v as u32),
|
||||||
|
avg_traffic: v.avg_traffic as u32,
|
||||||
|
peak_sustainable_traffic: v.peak_sustainable_traffic as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -56,23 +56,22 @@ you will be overriding the values set in the configuration files.
|
||||||
|
|
||||||
### Captcha
|
### Captcha
|
||||||
|
|
||||||
| Name | Value |
|
| Name | Value |
|
||||||
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --- |
|
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `MCAPTCHA_captcha_SALT` | Salt has to be long and random |
|
| `MCAPTCHA_captcha_SALT` | Salt has to be long and random |
|
||||||
| `MCAPTCHA_captcha_GC` | Garbage collection duration in seconds, requires tuning but 30 is a good starting point |
|
| `MCAPTCHA_captcha_GC` | Garbage collection duration in seconds, requires tuning but 30 is a good starting point |
|
||||||
| `MCAPTCHA_captcha_RUNNERS` | [Performance] Number of runners to use for PoW validation. Defaults to number of CPUs available |
|
| `MCAPTCHA_captcha_RUNNERS` | [Performance] Number of runners to use for PoW validation. Defaults to number of CPUs available |
|
||||||
| `MCAPTCHA_captcha_QUEUE_LENGTH` | [Performance] PoW Validation queue length, controls how many pending validation jobs can be held in queue |
|
| `MCAPTCHA_captcha_QUEUE_LENGTH` | [Performance] PoW Validation queue length, controls how many pending validation jobs can be held in queue |
|
||||||
| `MCAPTCHA_captcha_ENABLE_STATS` | Record for CAPTCHA events like configuration fetch, solves and authentication of validation token. Useful for commercial deployments. | |
|
| `MCAPTCHA_captcha_ENABLE_STATS` | Record for CAPTCHA events like configuration fetch, solves and authentication of validation token. Useful for commercial deployments. |
|
||||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
||||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time` | This difficulty factor is used in to use in easy mode CAPTCHA configuration estimation for average traffic metric |
|
||||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty`% | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for traffic that took the website down |
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
||||||
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration`% | Default duration to use in CAPTCHA configuration in easy mode |
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time` | This difficulty factor is used in to use in easy mode CAPTCHA configuration estimation for peak traffic metric |
|
||||||
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty` | Default difficulty factor to use in easy mode CAPTCHA configuration estimation for traffic that took the website down |
|
||||||
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time` | Default time (in seconds) to use to compute difficulty factor using stored PoW performance records. |
|
||||||
|
| `MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration` | Default duration to use in CAPTCHA configuration in easy mode |
|
||||||
|
|
||||||
\% See commits
|
See commits [`54b14291ec140e`](https://github.com/mCaptcha/mCaptcha/commit/54b14291ec140ea4cbbf73462d3d6fc2d39f2d2c) and [`42544ec421e0`](https://github.com/mCaptcha/mCaptcha/commit/42544ec421e0c3ec4a8d132e6101ab4069bf0065) for more info.
|
||||||
[`54b14291ec140e`](https://github.com/mCaptcha/mCaptcha/commit/54b14291ec140ea4cbbf73462d3d6fc2d39f2d2c)
|
|
||||||
and
|
|
||||||
[`42544ec421e0`](https://github.com/mCaptcha/mCaptcha/commit/42544ec421e0c3ec4a8d132e6101ab4069bf0065)
|
|
||||||
for more info.
|
|
||||||
|
|
||||||
### SMTP
|
### SMTP
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,79 @@ pub fn calculate(
|
||||||
Ok(levels)
|
Ok(levels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn calculate_with_percentile(
|
||||||
|
data: &AppData,
|
||||||
|
tp: &TrafficPattern,
|
||||||
|
) -> ServiceResult<Option<Vec<Level>>> {
|
||||||
|
use crate::api::v1::stats::{percentile_bench_runner, PercentileReq};
|
||||||
|
|
||||||
|
let strategy = &data.settings.captcha.default_difficulty_strategy;
|
||||||
|
|
||||||
|
if strategy.avg_traffic_time.is_none()
|
||||||
|
&& strategy.peak_sustainable_traffic_time.is_none()
|
||||||
|
&& strategy.broke_my_site_traffic_time.is_none()
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut req = PercentileReq {
|
||||||
|
time: strategy.avg_traffic_time.unwrap(),
|
||||||
|
percentile: 90.00,
|
||||||
|
};
|
||||||
|
let resp = percentile_bench_runner(data, &req).await?;
|
||||||
|
if resp.difficulty_factor.is_none() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let avg_traffic_difficulty = resp.difficulty_factor.unwrap();
|
||||||
|
|
||||||
|
req.time = strategy.peak_sustainable_traffic_time.unwrap();
|
||||||
|
let resp = percentile_bench_runner(data, &req).await?;
|
||||||
|
if resp.difficulty_factor.is_none() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let peak_sustainable_traffic_difficulty = resp.difficulty_factor.unwrap();
|
||||||
|
|
||||||
|
req.time = strategy.broke_my_site_traffic_time.unwrap();
|
||||||
|
let resp = percentile_bench_runner(data, &req).await?;
|
||||||
|
let broke_my_site_traffic_difficulty = if resp.difficulty_factor.is_none() {
|
||||||
|
resp.difficulty_factor.unwrap()
|
||||||
|
} else {
|
||||||
|
peak_sustainable_traffic_difficulty * 2
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut levels = vec![
|
||||||
|
LevelBuilder::default()
|
||||||
|
.difficulty_factor(avg_traffic_difficulty)?
|
||||||
|
.visitor_threshold(tp.avg_traffic)
|
||||||
|
.build()?,
|
||||||
|
LevelBuilder::default()
|
||||||
|
.difficulty_factor(peak_sustainable_traffic_difficulty)?
|
||||||
|
.visitor_threshold(tp.peak_sustainable_traffic)
|
||||||
|
.build()?,
|
||||||
|
];
|
||||||
|
let mut highest_level = LevelBuilder::default();
|
||||||
|
highest_level.difficulty_factor(broke_my_site_traffic_difficulty)?;
|
||||||
|
|
||||||
|
match tp.broke_my_site_traffic {
|
||||||
|
Some(broke_my_site_traffic) => {
|
||||||
|
highest_level.visitor_threshold(broke_my_site_traffic)
|
||||||
|
}
|
||||||
|
None => match tp
|
||||||
|
.peak_sustainable_traffic
|
||||||
|
.checked_add(tp.peak_sustainable_traffic / 2)
|
||||||
|
{
|
||||||
|
Some(num) => highest_level.visitor_threshold(num),
|
||||||
|
// TODO check for overflow: database saves these values as i32, so this u32 is cast
|
||||||
|
// into i32. Should choose bigger number or casts properly
|
||||||
|
None => highest_level.visitor_threshold(u32::MAX),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
levels.push(highest_level.build()?);
|
||||||
|
|
||||||
|
Ok(Some(levels))
|
||||||
|
}
|
||||||
|
|
||||||
#[my_codegen::post(
|
#[my_codegen::post(
|
||||||
path = "crate::V1_API_ROUTES.captcha.easy.create",
|
path = "crate::V1_API_ROUTES.captcha.easy.create",
|
||||||
wrap = "crate::api::v1::get_middleware()"
|
wrap = "crate::api::v1::get_middleware()"
|
||||||
|
@ -113,8 +186,12 @@ async fn create(
|
||||||
let username = id.identity().unwrap();
|
let username = id.identity().unwrap();
|
||||||
let payload = payload.into_inner();
|
let payload = payload.into_inner();
|
||||||
let pattern = (&payload).into();
|
let pattern = (&payload).into();
|
||||||
let levels =
|
let levels = if let Some(levels) = calculate_with_percentile(&data, &pattern).await?
|
||||||
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?;
|
{
|
||||||
|
levels
|
||||||
|
} else {
|
||||||
|
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?
|
||||||
|
};
|
||||||
let msg = CreateCaptcha {
|
let msg = CreateCaptcha {
|
||||||
levels,
|
levels,
|
||||||
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
duration: data.settings.captcha.default_difficulty_strategy.duration,
|
||||||
|
@ -147,6 +224,15 @@ async fn update(
|
||||||
) -> ServiceResult<impl Responder> {
|
) -> ServiceResult<impl Responder> {
|
||||||
let username = id.identity().unwrap();
|
let username = id.identity().unwrap();
|
||||||
let payload = payload.into_inner();
|
let payload = payload.into_inner();
|
||||||
|
update_runner(&data, payload, username).await?;
|
||||||
|
Ok(HttpResponse::Ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_runner(
|
||||||
|
data: &AppData,
|
||||||
|
payload: UpdateTrafficPattern,
|
||||||
|
username: String,
|
||||||
|
) -> ServiceResult<()> {
|
||||||
let pattern = (&payload.pattern).into();
|
let pattern = (&payload.pattern).into();
|
||||||
let levels =
|
let levels =
|
||||||
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?;
|
calculate(&pattern, &data.settings.captcha.default_difficulty_strategy)?;
|
||||||
|
@ -167,7 +253,7 @@ async fn update(
|
||||||
.add_traffic_pattern(&username, &msg.key, &pattern)
|
.add_traffic_pattern(&username, &msg.key, &pattern)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
50
src/demo.rs
50
src/demo.rs
|
@ -8,6 +8,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
use actix::clock::sleep;
|
use actix::clock::sleep;
|
||||||
use actix::spawn;
|
use actix::spawn;
|
||||||
|
use tokio::sync::oneshot::{channel, error::TryRecvError, Receiver, Sender};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use crate::api::v1::account::delete::runners::delete_user;
|
use crate::api::v1::account::delete::runners::delete_user;
|
||||||
|
@ -23,20 +24,24 @@ pub const DEMO_USER: &str = "aaronsw";
|
||||||
pub const DEMO_PASSWORD: &str = "password";
|
pub const DEMO_PASSWORD: &str = "password";
|
||||||
|
|
||||||
pub struct DemoUser {
|
pub struct DemoUser {
|
||||||
handle: JoinHandle<()>,
|
tx: Sender<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DemoUser {
|
impl DemoUser {
|
||||||
pub async fn spawn(data: AppData, duration: Duration) -> ServiceResult<Self> {
|
pub async fn spawn(
|
||||||
let handle = Self::run(data, duration).await?;
|
data: AppData,
|
||||||
let d = Self { handle };
|
duration: u32,
|
||||||
|
) -> ServiceResult<(Self, JoinHandle<()>)> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let handle = Self::run(data, duration, rx).await?;
|
||||||
|
let d = Self { tx };
|
||||||
|
|
||||||
Ok(d)
|
Ok((d, handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn abort(&self) {
|
pub fn abort(mut self) {
|
||||||
self.handle.abort();
|
self.tx.send(());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// register demo user runner
|
/// register demo user runner
|
||||||
|
@ -71,16 +76,38 @@ impl DemoUser {
|
||||||
|
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
data: AppData,
|
data: AppData,
|
||||||
duration: Duration,
|
duration: u32,
|
||||||
|
mut rx: Receiver<()>,
|
||||||
) -> ServiceResult<JoinHandle<()>> {
|
) -> ServiceResult<JoinHandle<()>> {
|
||||||
Self::register_demo_user(&data).await?;
|
Self::register_demo_user(&data).await?;
|
||||||
|
|
||||||
|
fn can_run(rx: &mut Receiver<()>) -> bool {
|
||||||
|
match rx.try_recv() {
|
||||||
|
Err(TryRecvError::Empty) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut exit = false;
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
loop {
|
loop {
|
||||||
sleep(duration).await;
|
if exit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for _ in 0..duration {
|
||||||
|
if can_run(&mut rx) {
|
||||||
|
sleep(Duration::new(1, 0)).await;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
exit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = Self::delete_demo_user(&data).await {
|
if let Err(e) = Self::delete_demo_user(&data).await {
|
||||||
log::error!("Error while deleting demo user: {:?}", e);
|
log::error!("Error while deleting demo user: {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = Self::register_demo_user(&data).await {
|
if let Err(e) = Self::register_demo_user(&data).await {
|
||||||
log::error!("Error while registering demo user: {:?}", e);
|
log::error!("Error while registering demo user: {:?}", e);
|
||||||
}
|
}
|
||||||
|
@ -133,7 +160,7 @@ mod tests {
|
||||||
assert!(!username_exists(&payload, &data).await.unwrap().exists);
|
assert!(!username_exists(&payload, &data).await.unwrap().exists);
|
||||||
|
|
||||||
// test the runner
|
// test the runner
|
||||||
let user = DemoUser::spawn(data, duration).await.unwrap();
|
let user = DemoUser::spawn(data, DURATION as u32).await.unwrap();
|
||||||
let (_, signin_resp, token_key) =
|
let (_, signin_resp, token_key) =
|
||||||
add_levels_util(data_inner, DEMO_USER, DEMO_PASSWORD).await;
|
add_levels_util(data_inner, DEMO_USER, DEMO_PASSWORD).await;
|
||||||
let cookies = get_cookie!(signin_resp);
|
let cookies = get_cookie!(signin_resp);
|
||||||
|
@ -162,6 +189,7 @@ mod tests {
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
let res_levels: Vec<Level> = test::read_body_json(resp).await;
|
let res_levels: Vec<Level> = test::read_body_json(resp).await;
|
||||||
assert!(res_levels.is_empty());
|
assert!(res_levels.is_empty());
|
||||||
user.abort();
|
user.0.abort();
|
||||||
|
user.1.await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
132
src/easy.rs
Normal file
132
src/easy.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright (C) 2024// Copyright (C) 2024 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
//use std::sync::atomicBool
|
||||||
|
|
||||||
|
use actix::clock::sleep;
|
||||||
|
use actix::spawn;
|
||||||
|
use tokio::sync::oneshot::{channel, error::TryRecvError, Receiver, Sender};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
|
use crate::api::v1::mcaptcha::easy::{
|
||||||
|
update_runner, TrafficPatternRequest, UpdateTrafficPattern,
|
||||||
|
};
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
use errors::*;
|
||||||
|
|
||||||
|
pub struct UpdateEasyCaptcha {
|
||||||
|
tx: Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateEasyCaptcha {
|
||||||
|
pub async fn spawn(
|
||||||
|
data: AppData,
|
||||||
|
duration: u32,
|
||||||
|
) -> ServiceResult<(Self, JoinHandle<()>)> {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let handle = Self::run(data, duration, rx).await?;
|
||||||
|
let d = Self { tx };
|
||||||
|
|
||||||
|
Ok((d, handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn abort(mut self) {
|
||||||
|
self.tx.send(());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// update configurations
|
||||||
|
async fn update_captcha_configurations(
|
||||||
|
data: &AppData,
|
||||||
|
rx: &mut Receiver<()>,
|
||||||
|
) -> ServiceResult<()> {
|
||||||
|
let limit = 10;
|
||||||
|
let mut offset = 0;
|
||||||
|
let mut page = 0;
|
||||||
|
loop {
|
||||||
|
offset = page * limit;
|
||||||
|
|
||||||
|
if !Self::can_run(rx) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut patterns = data.db.get_all_easy_captchas(limit, offset).await?;
|
||||||
|
for pattern in patterns.drain(0..) {
|
||||||
|
if !Self::can_run(rx) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let publish_benchmarks =
|
||||||
|
data.db.analytics_captcha_is_published(&pattern.key).await?;
|
||||||
|
|
||||||
|
let req = UpdateTrafficPattern {
|
||||||
|
pattern: TrafficPatternRequest {
|
||||||
|
avg_traffic: pattern.traffic_pattern.avg_traffic,
|
||||||
|
peak_sustainable_traffic: pattern
|
||||||
|
.traffic_pattern
|
||||||
|
.peak_sustainable_traffic,
|
||||||
|
broke_my_site_traffic: pattern
|
||||||
|
.traffic_pattern
|
||||||
|
.broke_my_site_traffic,
|
||||||
|
description: pattern.description,
|
||||||
|
publish_benchmarks,
|
||||||
|
},
|
||||||
|
key: pattern.key,
|
||||||
|
};
|
||||||
|
if !Self::can_run(rx) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
update_runner(&data, req, pattern.username).await?;
|
||||||
|
}
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_run(rx: &mut Receiver<()>) -> bool {
|
||||||
|
match rx.try_recv() {
|
||||||
|
Err(TryRecvError::Empty) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(
|
||||||
|
data: AppData,
|
||||||
|
duration: u32,
|
||||||
|
mut rx: Receiver<()>,
|
||||||
|
) -> ServiceResult<JoinHandle<()>> {
|
||||||
|
let mut exit = false;
|
||||||
|
let fut = async move {
|
||||||
|
loop {
|
||||||
|
if exit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for _ in 0..duration {
|
||||||
|
if Self::can_run(&mut rx) {
|
||||||
|
sleep(Duration::new(1, 0)).await;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
exit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(err) = Self::update_captcha_configurations(&data, &mut rx)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
{
|
||||||
|
log::error!(
|
||||||
|
"Tried to update easy captcha configurations in background {:?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let handle = spawn(fut);
|
||||||
|
Ok(handle)
|
||||||
|
}
|
||||||
|
}
|
27
src/main.rs
27
src/main.rs
|
@ -14,6 +14,7 @@ use actix_web::{
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod data;
|
mod data;
|
||||||
|
@ -21,6 +22,7 @@ mod date;
|
||||||
mod db;
|
mod db;
|
||||||
mod demo;
|
mod demo;
|
||||||
mod docs;
|
mod docs;
|
||||||
|
mod easy;
|
||||||
mod email;
|
mod email;
|
||||||
mod errors;
|
mod errors;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -110,11 +112,22 @@ async fn main() -> std::io::Result<()> {
|
||||||
let data = Data::new(&settings, secrets.clone()).await;
|
let data = Data::new(&settings, secrets.clone()).await;
|
||||||
let data = actix_web::web::Data::new(data);
|
let data = actix_web::web::Data::new(data);
|
||||||
|
|
||||||
let mut demo_user: Option<DemoUser> = None;
|
let mut demo_user: Option<(DemoUser, JoinHandle<()>)> = None;
|
||||||
|
|
||||||
if settings.allow_demo && settings.allow_registration {
|
if settings.allow_demo && settings.allow_registration {
|
||||||
demo_user = Some(
|
demo_user = Some(DemoUser::spawn(data.clone(), 60 * 30).await.unwrap());
|
||||||
DemoUser::spawn(data.clone(), Duration::from_secs(60 * 30))
|
}
|
||||||
|
|
||||||
|
let mut update_easy_captcha: Option<(easy::UpdateEasyCaptcha, JoinHandle<()>)> =
|
||||||
|
None;
|
||||||
|
if settings
|
||||||
|
.captcha
|
||||||
|
.default_difficulty_strategy
|
||||||
|
.avg_traffic_time
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
update_easy_captcha = Some(
|
||||||
|
easy::UpdateEasyCaptcha::spawn(data.clone(), 60 * 30)
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
|
@ -156,7 +169,13 @@ async fn main() -> std::io::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(demo_user) = demo_user {
|
if let Some(demo_user) = demo_user {
|
||||||
demo_user.abort();
|
demo_user.0.abort();
|
||||||
|
demo_user.1.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(update_easy_captcha) = update_easy_captcha {
|
||||||
|
update_easy_captcha.0.abort();
|
||||||
|
update_easy_captcha.1.await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(survey_upload_handle) = survey_upload_handle {
|
if let Some(survey_upload_handle) = survey_upload_handle {
|
||||||
|
|
|
@ -37,8 +37,11 @@ pub struct Captcha {
|
||||||
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||||
pub struct DefaultDifficultyStrategy {
|
pub struct DefaultDifficultyStrategy {
|
||||||
pub avg_traffic_difficulty: u32,
|
pub avg_traffic_difficulty: u32,
|
||||||
pub broke_my_site_traffic_difficulty: u32,
|
pub avg_traffic_time: Option<u32>,
|
||||||
pub peak_sustainable_traffic_difficulty: u32,
|
pub peak_sustainable_traffic_difficulty: u32,
|
||||||
|
pub peak_sustainable_traffic_time: Option<u32>,
|
||||||
|
pub broke_my_site_traffic_time: Option<u32>,
|
||||||
|
pub broke_my_site_traffic_difficulty: u32,
|
||||||
pub duration: u32,
|
pub duration: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +116,7 @@ pub struct Settings {
|
||||||
pub smtp: Option<Smtp>,
|
pub smtp: Option<Smtp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENV_VAR_CONFIG: [(&str, &str); 29] = [
|
const ENV_VAR_CONFIG: [(&str, &str); 32] = [
|
||||||
/* top-level */
|
/* top-level */
|
||||||
("debug", "MCAPTCHA_debug"),
|
("debug", "MCAPTCHA_debug"),
|
||||||
("commercial", "MCAPTCHA_commercial"),
|
("commercial", "MCAPTCHA_commercial"),
|
||||||
|
@ -150,6 +153,9 @@ const ENV_VAR_CONFIG: [(&str, &str); 29] = [
|
||||||
( "captcha.default_difficulty_strategy.duration",
|
( "captcha.default_difficulty_strategy.duration",
|
||||||
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration"
|
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration"
|
||||||
),
|
),
|
||||||
|
("captcha.default_difficulty_strategy.avg_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time"),
|
||||||
|
("captcha.default_difficulty_strategy.peak_sustainable_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time"),
|
||||||
|
("captcha.default_difficulty_strategy.broke_my_site_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time"),
|
||||||
|
|
||||||
|
|
||||||
/* SMTP */
|
/* SMTP */
|
||||||
|
@ -251,6 +257,28 @@ impl Settings {
|
||||||
|
|
||||||
Ok(settings)
|
Ok(settings)
|
||||||
}
|
}
|
||||||
|
fn check_easy_captcha_config(&self) {
|
||||||
|
let s = &self.captcha.default_difficulty_strategy;
|
||||||
|
if s.avg_traffic_time.is_some() {
|
||||||
|
if s.broke_my_site_traffic_time.is_none()
|
||||||
|
|| s.peak_sustainable_traffic_time.is_none()
|
||||||
|
{
|
||||||
|
panic!("if captcha.default_difficulty_strategy.avg_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.peak_sustainable_traffic_time must also be set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.peak_sustainable_traffic_time.is_some() {
|
||||||
|
if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
|
||||||
|
{
|
||||||
|
panic!("if captcha.default_difficulty_strategy.peak_sustainable_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.broke_my_site_traffic_time.is_some() {
|
||||||
|
if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
|
||||||
|
{
|
||||||
|
panic!("if captcha.default_difficulty_strategy.broke_my_site_traffic_time is set, then captcha.default_difficulty_strategy.peak_sustainable_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
|
||||||
for (parameter, env_var_name) in DEPRECATED_ENV_VARS.iter() {
|
for (parameter, env_var_name) in DEPRECATED_ENV_VARS.iter() {
|
||||||
|
@ -538,6 +566,30 @@ mod tests {
|
||||||
999,
|
999,
|
||||||
captcha.default_difficulty_strategy.duration
|
captcha.default_difficulty_strategy.duration
|
||||||
);
|
);
|
||||||
|
helper!(
|
||||||
|
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time",
|
||||||
|
"10",
|
||||||
|
Some(10),
|
||||||
|
captcha.default_difficulty_strategy.avg_traffic_time
|
||||||
|
);
|
||||||
|
|
||||||
|
helper!(
|
||||||
|
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time",
|
||||||
|
"20",
|
||||||
|
Some(20),
|
||||||
|
captcha
|
||||||
|
.default_difficulty_strategy
|
||||||
|
.peak_sustainable_traffic_time
|
||||||
|
);
|
||||||
|
|
||||||
|
helper!(
|
||||||
|
"MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time",
|
||||||
|
"30",
|
||||||
|
Some(30),
|
||||||
|
captcha
|
||||||
|
.default_difficulty_strategy
|
||||||
|
.broke_my_site_traffic_time
|
||||||
|
);
|
||||||
|
|
||||||
/* SMTP */
|
/* SMTP */
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue