mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2026-02-11 10:05:41 +00:00
Compare commits
11 Commits
nighwatch
...
wip-mail-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f029d2945 | ||
|
|
e7b01a5b06 | ||
|
|
0cfffed52e | ||
|
|
c53fe2e3ff | ||
|
|
78de0b266f | ||
|
|
6ede578ad5 | ||
|
|
efed5f5f93 | ||
|
|
4a6850631a | ||
|
|
0adbb0aa2f | ||
|
|
8f3faaa279 | ||
|
|
9fc7c31083 |
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -873,6 +873,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3392,6 +3393,16 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"serde 1.0.143",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "validator"
|
name = "validator"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -32,11 +32,11 @@ yourself!](https://demo.mcaptcha.org/widget/?sitekey=pHy0AktWyOKuxZDzFfoaewncWec
|
|||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
mCaptcha uses SHA256 based proof-of-work(PoW) to rate limit users.
|
mCaptcha uses SHA256 based proof-of-work (PoW) to rate limit users.
|
||||||
|
|
||||||
When a user wants to do something on an mCaptcha-protected website,
|
When a user wants to do something on a mCaptcha-protected website,
|
||||||
|
|
||||||
1. they will have to generate proof-of-work(a bunch of math that will takes
|
1. they will have to generate proof-of-work (a bunch of math that will takes
|
||||||
time to compute) and submit it to mCaptcha.
|
time to compute) and submit it to mCaptcha.
|
||||||
|
|
||||||
2. We'll validate the proof:
|
2. We'll validate the proof:
|
||||||
@@ -54,8 +54,8 @@ When a user wants to do something on an mCaptcha-protected website,
|
|||||||
The whole process is automated from the user's POV. All they have to do
|
The whole process is automated from the user's POV. All they have to do
|
||||||
is click on a button to initiate the process.
|
is click on a button to initiate the process.
|
||||||
|
|
||||||
mCaptcha makes interacting with websites (computationally)expensive for
|
mCaptcha makes interacting with websites (computationally) expensive for
|
||||||
the user. A well-behaving user will experience a slight delay(no delay
|
the user. A well-behaving user will experience a slight delay (no delay
|
||||||
when under moderate load to 2s when under attack; PoW difficulty is
|
when under moderate load to 2s when under attack; PoW difficulty is
|
||||||
variable) but if someone wants to hammer your site, they will have to do
|
variable) but 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
|
more work to send requests than your server will have to do to respond
|
||||||
@@ -68,7 +68,7 @@ to their request.
|
|||||||
- [x] **No tracking:** Our CAPTCHA routes are cookie free!
|
- [x] **No tracking:** Our CAPTCHA routes are cookie free!
|
||||||
- [x] **IP address independent:** your users are behind a NAT? We got you covered!
|
- [x] **IP address independent:** your users are behind a NAT? We got you covered!
|
||||||
- [x] **Resistant to replay attacks:** proof-of-work configurations have
|
- [x] **Resistant to replay attacks:** proof-of-work configurations have
|
||||||
short lifetimes(30s) and can be used only once. If a user submits a
|
short lifetimes (30s) and can be used only once. If a user submits a
|
||||||
PoW to an already used configuration or an expired one, their proof
|
PoW to an already used configuration or an expired one, their proof
|
||||||
will be rejected.
|
will be rejected.
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ monitor console and network activity.
|
|||||||
> demo servers might be a few versions behind `master`. Please check footer for
|
> demo servers might be a few versions behind `master`. Please check footer for
|
||||||
> build commit.
|
> build commit.
|
||||||
|
|
||||||
Feel free to provide bogus information while signing up(project under
|
Feel free to provide bogus information while signing up (project under
|
||||||
development, database frequently wiped).
|
development, database frequently wiped).
|
||||||
|
|
||||||
### Self-hosted:
|
### Self-hosted:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ serde = { version = "1", features = ["derive"]}
|
|||||||
url = { version = "2.2.2", features = ["serde"] }
|
url = { version = "2.2.2", features = ["serde"] }
|
||||||
#libmcaptcha = { version = "0.2.2", git = "https://github.com/mCaptcha/libmcaptcha", features = ["minimal"], default-features = false, tag = "0.2.2"}
|
#libmcaptcha = { version = "0.2.2", git = "https://github.com/mCaptcha/libmcaptcha", features = ["minimal"], default-features = false, tag = "0.2.2"}
|
||||||
libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
libmcaptcha = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
||||||
|
uuid = { version = "1.3.3", features = ["v4", "serde"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
@@ -31,7 +31,10 @@
|
|||||||
//! - [errors](crate::auth): error data structures used in this crate
|
//! - [errors](crate::auth): error data structures used in this crate
|
||||||
//! - [ops](crate::ops): meta operations like connection pool creation, migrations and getting
|
//! - [ops](crate::ops): meta operations like connection pool creation, migrations and getting
|
||||||
//! connection from pool
|
//! connection from pool
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub use libmcaptcha::defense::Level;
|
pub use libmcaptcha::defense::Level;
|
||||||
|
|
||||||
@@ -97,6 +100,73 @@ pub struct NameHash {
|
|||||||
pub hash: String,
|
pub hash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
/// Email challenge reason
|
||||||
|
pub enum ChallengeReason {
|
||||||
|
/// challenge created to verify a newly registered user
|
||||||
|
EmailVerification,
|
||||||
|
/// Challenge created to verify a password reset request
|
||||||
|
PasswordReset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChallengeReason {
|
||||||
|
pub fn to_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::EmailVerification => "email_verification",
|
||||||
|
Self::PasswordReset => "password_resset",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for ChallengeReason {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
self.to_str().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ChallengeReason {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
for reason in [Self::PasswordReset, Self::EmailVerification].iter() {
|
||||||
|
if s == reason.to_str() {
|
||||||
|
return Ok(reason.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
/// Minimal user representation for use in challenge verification
|
||||||
|
pub struct ChallengeUser {
|
||||||
|
/// username of the user
|
||||||
|
pub username: String,
|
||||||
|
/// email ID of the user
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
/// Email challenge
|
||||||
|
pub struct Challenge {
|
||||||
|
/// challenge unique identifier
|
||||||
|
pub challenge: Uuid,
|
||||||
|
/// reason why the challenge was create
|
||||||
|
pub reason: ChallengeReason,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Challenge {
|
||||||
|
/// create new Challenge instance for a given reason. Challenge text is auto-generated
|
||||||
|
pub fn new(reason: ChallengeReason) -> Self {
|
||||||
|
let challenge = Uuid::new_v4();
|
||||||
|
Self { challenge, reason }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate new ID (useful when ID clashes)
|
||||||
|
pub fn new_id(&mut self) {
|
||||||
|
self.challenge = Uuid::new_v4();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
/// mCaptcha's database requirements. To implement support for $Database, kindly implement this
|
/// mCaptcha's database requirements. To implement support for $Database, kindly implement this
|
||||||
/// trait.
|
/// trait.
|
||||||
@@ -250,6 +320,19 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
|
|||||||
|
|
||||||
/// fetch PoWConfig confirms
|
/// fetch PoWConfig confirms
|
||||||
async fn fetch_confirm(&self, user: &str, key: &str) -> DBResult<Vec<i64>>;
|
async fn fetch_confirm(&self, user: &str, key: &str) -> DBResult<Vec<i64>>;
|
||||||
|
|
||||||
|
/// Record challenge in database
|
||||||
|
async fn new_challenge(&self, user: &str, challenge: &mut Challenge)
|
||||||
|
-> DBResult<()>;
|
||||||
|
|
||||||
|
/// Record challenge in database
|
||||||
|
async fn fetch_challenge_user(
|
||||||
|
&self,
|
||||||
|
challenge: &Challenge,
|
||||||
|
) -> DBResult<ChallengeUser>;
|
||||||
|
|
||||||
|
/// Delete a challenge from database
|
||||||
|
async fn delete_challenge(&self, challenge: &Challenge) -> DBResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
|
||||||
|
|||||||
@@ -295,4 +295,13 @@ pub async fn database_works<'a, T: MCDatabase>(
|
|||||||
// delete captcha; updated key = p.username so invoke delete with it
|
// delete captcha; updated key = p.username so invoke delete with it
|
||||||
db.delete_captcha(p.username, p.username).await.unwrap();
|
db.delete_captcha(p.username, p.username).await.unwrap();
|
||||||
assert!(!db.captcha_exists(Some(p.username), c.key).await.unwrap());
|
assert!(!db.captcha_exists(Some(p.username), c.key).await.unwrap());
|
||||||
|
|
||||||
|
let mut challenge = Challenge::new(ChallengeReason::PasswordReset);
|
||||||
|
db.new_challenge(p.username, &mut challenge).await.unwrap();
|
||||||
|
db.new_challenge(p.username, &mut challenge).await.unwrap();
|
||||||
|
let c = db.fetch_challenge_user(&challenge).await.unwrap();
|
||||||
|
assert_eq!(c.username, p.username);
|
||||||
|
assert_eq!(&c.email, p.email.as_ref().unwrap());
|
||||||
|
db.delete_challenge(&challenge).await.unwrap();
|
||||||
|
assert!(db.fetch_challenge_user(&challenge).await.is_err())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_challenge_reason (
|
||||||
|
id INT auto_increment,
|
||||||
|
PRIMARY KEY(id),
|
||||||
|
name VARCHAR(40) NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_challenge (
|
||||||
|
id INT auto_increment,
|
||||||
|
PRIMARY KEY(id),
|
||||||
|
reason INT NOT NULL,
|
||||||
|
challenge_id varchar(40) NOT NULL UNIQUE,
|
||||||
|
received timestamp NOT NULL DEFAULT now(),
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT `fk_mcaptcha_challenge_user`
|
||||||
|
FOREIGN KEY (user_id)
|
||||||
|
REFERENCES mcaptcha_users (ID)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE,
|
||||||
|
|
||||||
|
CONSTRAINT `fk_mcaptcha_mcaptcha_challenge_reason`
|
||||||
|
FOREIGN KEY (reason)
|
||||||
|
REFERENCES mcaptcha_challenge_reason (id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
|
||||||
|
);
|
||||||
@@ -1,5 +1,53 @@
|
|||||||
{
|
{
|
||||||
"db": "MySQL",
|
"db": "MySQL",
|
||||||
|
"04e79a67bc8c1b18eca95fc4d2602ed5dd41b6d864796f034540efec3da05fa8": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT IGNORE INTO\n mcaptcha_challenge_reason (name)\n VALUES (?)"
|
||||||
|
},
|
||||||
|
"12a7d765fb683c8134d032563f2d101e2fd70c261e71696e7a90387507e0ef43": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": {
|
||||||
|
"char_set": 224,
|
||||||
|
"flags": {
|
||||||
|
"bits": 4101
|
||||||
|
},
|
||||||
|
"max_size": 400,
|
||||||
|
"type": "VarString"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "email",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": {
|
||||||
|
"char_set": 224,
|
||||||
|
"flags": {
|
||||||
|
"bits": 4
|
||||||
|
},
|
||||||
|
"max_size": 400,
|
||||||
|
"type": "VarString"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT name, email\n FROM mcaptcha_users\n WHERE ID = (SELECT user_id \n FROM mcaptcha_challenge\n WHERE\n challenge_id = ?\n AND reason = (\n SELECT id FROM mcaptcha_challenge_reason WHERE name = ?\n )\n );"
|
||||||
|
},
|
||||||
"1367dceb151a766a901b5dd771d0b75d0bc61d2fef17a94a90c8ffa0065e2c44": {
|
"1367dceb151a766a901b5dd771d0b75d0bc61d2fef17a94a90c8ffa0065e2c44": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -247,6 +295,16 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config where captcha_key= (?)\n ) ORDER BY difficulty_factor ASC;"
|
"query": "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config where captcha_key= (?)\n ) ORDER BY difficulty_factor ASC;"
|
||||||
},
|
},
|
||||||
|
"740ed2dab8c07c718c1b0e8e4262251bbf2501cdebfc4872fb903f70ec3d0dc8": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO mcaptcha_challenge (challenge_id, received, reason, user_id)\n VALUES (?, ?,\n (SELECT id FROM mcaptcha_challenge_reason WHERE name = ?),\n (SELECT id FROM mcaptcha_users WHERE name = ?)\n );\n "
|
||||||
|
},
|
||||||
"74d68a86f852d3d85957e94ed04e8acd8e6144744f7b13e383ebcb2bcf3360ae": {
|
"74d68a86f852d3d85957e94ed04e8acd8e6144744f7b13e383ebcb2bcf3360ae": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -873,6 +931,16 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT name, password FROM mcaptcha_users WHERE email = ?"
|
"query": "SELECT name, password FROM mcaptcha_users WHERE email = ?"
|
||||||
},
|
},
|
||||||
|
"f47c05c0a7da41a2176f08a44c6c945dabb84558a4d09369b6108bfce8b9d2bf": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "DELETE\n FROM mcaptcha_challenge\n WHERE\n challenge_id = ?\n AND reason = (SELECT id FROM mcaptcha_challenge_reason WHERE name = ?);"
|
||||||
|
},
|
||||||
"fc717ff0827ccfaa1cc61a71cc7f71c348ebb03d35895c54b011c03121ad2385": {
|
"fc717ff0827ccfaa1cc61a71cc7f71c348ebb03d35895c54b011c03121ad2385": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
|
|||||||
@@ -95,6 +95,22 @@ impl Migrate for Database {
|
|||||||
.run(&self.pool)
|
.run(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DBError::DBError(Box::new(e)))?;
|
.map_err(|e| DBError::DBError(Box::new(e)))?;
|
||||||
|
|
||||||
|
for reason in [
|
||||||
|
ChallengeReason::EmailVerification,
|
||||||
|
ChallengeReason::PasswordReset,
|
||||||
|
] {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT IGNORE INTO
|
||||||
|
mcaptcha_challenge_reason (name)
|
||||||
|
VALUES (?)",
|
||||||
|
reason.to_str()
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| DBError::DBError(Box::new(e)))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -895,6 +911,93 @@ impl MCDatabase for Database {
|
|||||||
|
|
||||||
Ok(Date::dates_to_unix(records))
|
Ok(Date::dates_to_unix(records))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Record challenge in database
|
||||||
|
async fn new_challenge(
|
||||||
|
&self,
|
||||||
|
user: &str,
|
||||||
|
challenge: &mut Challenge,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let now = now_unix_time_stamp();
|
||||||
|
loop {
|
||||||
|
let res = sqlx::query!(
|
||||||
|
"INSERT INTO mcaptcha_challenge (challenge_id, received, reason, user_id)
|
||||||
|
VALUES (?, ?,
|
||||||
|
(SELECT id FROM mcaptcha_challenge_reason WHERE name = ?),
|
||||||
|
(SELECT id FROM mcaptcha_users WHERE name = ?)
|
||||||
|
);
|
||||||
|
",
|
||||||
|
&challenge.challenge.to_string(),
|
||||||
|
now,
|
||||||
|
challenge.reason.to_str(),
|
||||||
|
user
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
if let Err(Error::Database(err)) = res {
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
if err.code() == Some(Cow::from("23505")) {
|
||||||
|
let msg = err.message();
|
||||||
|
if msg.contains("for key 'challenge_id'") {
|
||||||
|
challenge.new_id();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record challenge in database
|
||||||
|
async fn fetch_challenge_user(
|
||||||
|
&self,
|
||||||
|
challenge: &Challenge,
|
||||||
|
) -> DBResult<ChallengeUser> {
|
||||||
|
struct C {
|
||||||
|
name: String,
|
||||||
|
email: Option<String>,
|
||||||
|
}
|
||||||
|
let res = sqlx::query_as!(
|
||||||
|
C,
|
||||||
|
"SELECT name, email
|
||||||
|
FROM mcaptcha_users
|
||||||
|
WHERE ID = (SELECT user_id
|
||||||
|
FROM mcaptcha_challenge
|
||||||
|
WHERE
|
||||||
|
challenge_id = ?
|
||||||
|
AND reason = (
|
||||||
|
SELECT id FROM mcaptcha_challenge_reason WHERE name = ?
|
||||||
|
)
|
||||||
|
);",
|
||||||
|
&challenge.challenge.to_string(),
|
||||||
|
challenge.reason.to_str(),
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(map_register_err)?;
|
||||||
|
Ok(ChallengeUser {
|
||||||
|
username: res.name,
|
||||||
|
email: res.email.unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a challenge from database
|
||||||
|
async fn delete_challenge(&self, challenge: &Challenge) -> DBResult<()> {
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"DELETE
|
||||||
|
FROM mcaptcha_challenge
|
||||||
|
WHERE
|
||||||
|
challenge_id = ?
|
||||||
|
AND reason = (SELECT id FROM mcaptcha_challenge_reason WHERE name = ?);",
|
||||||
|
&challenge.challenge.to_string(),
|
||||||
|
challenge.reason.to_str(),
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_challenge_reason (
|
||||||
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(40) NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS mcaptcha_challenge (
|
||||||
|
id SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
reason INTEGER NOT NULL references mcaptcha_challenge_reason(ID) ON DELETE CASCADE,
|
||||||
|
user_id INTEGER NOT NULL references mcaptcha_users(ID) ON DELETE CASCADE,
|
||||||
|
challenge_id varchar(40) NOT NULL UNIQUE,
|
||||||
|
received timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
@@ -81,6 +81,33 @@
|
|||||||
},
|
},
|
||||||
"query": "DELETE FROM mcaptcha_sitekey_user_provided_avg_traffic\n WHERE config_id = (\n SELECT config_id \n FROM \n mcaptcha_config \n WHERE\n key = ($1) \n AND \n user_id = (SELECT ID FROM mcaptcha_users WHERE name = $2)\n );"
|
"query": "DELETE FROM mcaptcha_sitekey_user_provided_avg_traffic\n WHERE config_id = (\n SELECT config_id \n FROM \n mcaptcha_config \n WHERE\n key = ($1) \n AND \n user_id = (SELECT ID FROM mcaptcha_users WHERE name = $2)\n );"
|
||||||
},
|
},
|
||||||
|
"0fe29ca10e9a83f2064b1b98f570161d339891a74c637077b94d138a4360340e": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "email",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "SELECT\n email, name\n FROM\n mcaptcha_users\n WHERE\n ID = (\n SELECT\n user_id\n FROM\n mcaptcha_challenge\n WHERE\n challenge_id = $1\n AND reason = (SELECT ID FROM mcaptcha_challenge_reason WHERE name = $2)\n );"
|
||||||
|
},
|
||||||
"16864df9cf9a69c299d9ab68bac559c48f4fc433541a10f7c1b60717df2b820e": {
|
"16864df9cf9a69c299d9ab68bac559c48f4fc433541a10f7c1b60717df2b820e": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -119,6 +146,21 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT key, name, config_id, duration FROM mcaptcha_config WHERE\n user_id = (SELECT ID FROM mcaptcha_users WHERE name = $1) "
|
"query": "SELECT key, name, config_id, duration FROM mcaptcha_config WHERE\n user_id = (SELECT ID FROM mcaptcha_users WHERE name = $1) "
|
||||||
},
|
},
|
||||||
|
"1e08fab612b17ab3cf3f76cd1543fb4d4006f7c20e09ecb58e1a1cfd5a7e70a2": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Timestamptz",
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO mcaptcha_challenge (challenge_id, received, reason, user_id)\n VALUES ($1, $2, \n (SELECT ID FROM mcaptcha_challenge_reason WHERE name = $3),\n (SELECT ID FROM mcaptcha_users WHERE name = $4)\n );\n "
|
||||||
|
},
|
||||||
"1e9fe69b23e4bfa7bb369455753100307e334e8dbaf02ff37cda08992fe95910": {
|
"1e9fe69b23e4bfa7bb369455753100307e334e8dbaf02ff37cda08992fe95910": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@@ -427,6 +469,19 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT time FROM mcaptcha_pow_solved_stats \n WHERE config_id = (\n SELECT config_id FROM mcaptcha_config \n WHERE \n key = $1\n AND\n user_id = (\n SELECT \n ID FROM mcaptcha_users WHERE name = $2)) \n ORDER BY time DESC"
|
"query": "SELECT time FROM mcaptcha_pow_solved_stats \n WHERE config_id = (\n SELECT config_id FROM mcaptcha_config \n WHERE \n key = $1\n AND\n user_id = (\n SELECT \n ID FROM mcaptcha_users WHERE name = $2)) \n ORDER BY time DESC"
|
||||||
},
|
},
|
||||||
|
"8a624372ec26200acdbc1c6c330dad841581e9abad586fa7f5a117a7cd289bd9": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "DELETE\n FROM mcaptcha_challenge\n WHERE\n challenge_id = $1\n AND reason = (SELECT ID FROM mcaptcha_challenge_reason WHERE name = $2);"
|
||||||
|
},
|
||||||
"9753721856a47438c5e72f28fd9d149db10c48e677b4613bf3f1e8487908aac8": {
|
"9753721856a47438c5e72f28fd9d149db10c48e677b4613bf3f1e8487908aac8": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@@ -453,6 +508,18 @@
|
|||||||
},
|
},
|
||||||
"query": "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE key = ($1)\n ) ORDER BY difficulty_factor ASC;"
|
"query": "SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE\n config_id = (\n SELECT config_id FROM mcaptcha_config WHERE key = ($1)\n ) ORDER BY difficulty_factor ASC;"
|
||||||
},
|
},
|
||||||
|
"a209d14eb2c2eba8a750d66f74f8edcdbb02cf7c6c5249b226db30f52541a79b": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "INSERT INTO\n mcaptcha_challenge_reason (name)\n VALUES ($1) ON CONFLICT DO NOTHING\n "
|
||||||
|
},
|
||||||
"ad196ab3ef9dc32f6de2313577ccd6c26eae9ab19df5f71ce182651983efb99a": {
|
"ad196ab3ef9dc32f6de2313577ccd6c26eae9ab19df5f71ce182651983efb99a": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
|||||||
@@ -95,6 +95,23 @@ impl Migrate for Database {
|
|||||||
.run(&self.pool)
|
.run(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| DBError::DBError(Box::new(e)))?;
|
.map_err(|e| DBError::DBError(Box::new(e)))?;
|
||||||
|
|
||||||
|
for reason in [
|
||||||
|
ChallengeReason::EmailVerification,
|
||||||
|
ChallengeReason::PasswordReset,
|
||||||
|
] {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO
|
||||||
|
mcaptcha_challenge_reason (name)
|
||||||
|
VALUES ($1) ON CONFLICT DO NOTHING
|
||||||
|
",
|
||||||
|
reason.to_str()
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| DBError::DBError(Box::new(e)))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -901,6 +918,99 @@ impl MCDatabase for Database {
|
|||||||
|
|
||||||
Ok(Date::dates_to_unix(records))
|
Ok(Date::dates_to_unix(records))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Record challenge in database
|
||||||
|
async fn new_challenge(
|
||||||
|
&self,
|
||||||
|
user: &str,
|
||||||
|
challenge: &mut Challenge,
|
||||||
|
) -> DBResult<()> {
|
||||||
|
let now = now_unix_time_stamp();
|
||||||
|
loop {
|
||||||
|
let res = sqlx::query!(
|
||||||
|
"INSERT INTO mcaptcha_challenge (challenge_id, received, reason, user_id)
|
||||||
|
VALUES ($1, $2,
|
||||||
|
(SELECT ID FROM mcaptcha_challenge_reason WHERE name = $3),
|
||||||
|
(SELECT ID FROM mcaptcha_users WHERE name = $4)
|
||||||
|
);
|
||||||
|
",
|
||||||
|
&challenge.challenge.to_string(),
|
||||||
|
now,
|
||||||
|
challenge.reason.to_str(),
|
||||||
|
user
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
if let Err(Error::Database(err)) = res {
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
if err.code() == Some(Cow::from("23505")) {
|
||||||
|
let msg = err.message();
|
||||||
|
if msg.contains("mcaptcha_challenge_challenge_id_key") {
|
||||||
|
challenge.new_id();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record challenge in database
|
||||||
|
async fn fetch_challenge_user(
|
||||||
|
&self,
|
||||||
|
challenge: &Challenge,
|
||||||
|
) -> DBResult<ChallengeUser> {
|
||||||
|
struct U {
|
||||||
|
name: String,
|
||||||
|
email: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = sqlx::query_as!(
|
||||||
|
U,
|
||||||
|
"SELECT
|
||||||
|
email, name
|
||||||
|
FROM
|
||||||
|
mcaptcha_users
|
||||||
|
WHERE
|
||||||
|
ID = (
|
||||||
|
SELECT
|
||||||
|
user_id
|
||||||
|
FROM
|
||||||
|
mcaptcha_challenge
|
||||||
|
WHERE
|
||||||
|
challenge_id = $1
|
||||||
|
AND reason = (SELECT ID FROM mcaptcha_challenge_reason WHERE name = $2)
|
||||||
|
);",
|
||||||
|
challenge.challenge.to_string(),
|
||||||
|
challenge.reason.to_str(),
|
||||||
|
)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(map_register_err)?;
|
||||||
|
|
||||||
|
Ok(ChallengeUser {
|
||||||
|
username: res.name,
|
||||||
|
email: res.email.unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a challenge from database
|
||||||
|
async fn delete_challenge(&self, challenge: &Challenge) -> DBResult<()> {
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"DELETE
|
||||||
|
FROM mcaptcha_challenge
|
||||||
|
WHERE
|
||||||
|
challenge_id = $1
|
||||||
|
AND reason = (SELECT ID FROM mcaptcha_challenge_reason WHERE name = $2);",
|
||||||
|
&challenge.challenge.to_string(),
|
||||||
|
challenge.reason.to_str(),
|
||||||
|
)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ you will be overriding the values set in the configuration files.
|
|||||||
| `MCAPTCHA_DATEBASE_USERNAME` | database username |
|
| `MCAPTCHA_DATEBASE_USERNAME` | database username |
|
||||||
| `MCAPTCHA_DATEBASE_POOL` | database connection pool size |
|
| `MCAPTCHA_DATEBASE_POOL` | database connection pool size |
|
||||||
| `MCAPTCHA_DATEBASE_DATABASE_TYPE` | database tpye: "postgres" or "maria" |
|
| `MCAPTCHA_DATEBASE_DATABASE_TYPE` | database tpye: "postgres" or "maria" |
|
||||||
| `DATABSE_URL` (overrides above vars) | database URL in `postgres://user:pass@host:port/dbname` format |
|
| `DATABASE_URL` (overrides above vars) | database URL in `postgres://user:pass@host:port/dbname` format |
|
||||||
|
|
||||||
#### Redis
|
#### Redis
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
* mCaptcha is a PoW based DoS protection software.
|
||||||
|
* This is the frontend web component of the mCaptcha system
|
||||||
|
* Copyright © 2023 Aravinth Manivnanan <realaravinth@batsense.net>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||||
* License, or (at your option) any later version.
|
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
* mCaptcha is a PoW based DoS protection software.
|
||||||
|
* This is the frontend web component of the mCaptcha system
|
||||||
|
* Copyright © 2023 Aravinth Manivnanan <realaravinth@batsense.net>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||||
* License, or (at your option) any later version.
|
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@import '../reset';
|
@import "../reset";
|
||||||
|
|
||||||
.widget__contaienr {
|
.widget__contaienr {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
* mCaptcha is a PoW based DoS protection software.
|
||||||
|
* This is the frontend web component of the mCaptcha system
|
||||||
|
* Copyright © 2023 Aravinth Manivnanan <realaravinth@batsense.net>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||||
* License, or (at your option) any later version.
|
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||||
*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
*/
|
||||||
import prove from "./prove";
|
|
||||||
import { PoWConfig, ServiceWorkerWork } from "./types";
|
|
||||||
import log from "../logger";
|
import log from "../logger";
|
||||||
|
|
||||||
|
import prove from "./prove";
|
||||||
|
import {PoWConfig, ServiceWorkerWork} from "./types";
|
||||||
|
|
||||||
log.log("worker registered");
|
log.log("worker registered");
|
||||||
onmessage = async (e) => {
|
onmessage = async (e) => {
|
||||||
console.debug("message received at worker");
|
console.debug("message received at worker");
|
||||||
|
|||||||
Reference in New Issue
Block a user