mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2026-02-11 10:05:41 +00:00
Compare commits
19 Commits
fix-53
...
wip-mail-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f029d2945 | ||
|
|
e7b01a5b06 | ||
|
|
0cfffed52e | ||
|
|
c53fe2e3ff | ||
|
|
78de0b266f | ||
|
|
6ede578ad5 | ||
|
|
efed5f5f93 | ||
|
|
4a6850631a | ||
|
|
0adbb0aa2f | ||
|
|
8f3faaa279 | ||
|
|
5324969bd2 | ||
|
|
43dab030df | ||
|
|
9cc667851c | ||
|
|
9fc7c31083 | ||
|
|
90e60b0486 | ||
|
|
fae50b19f8 | ||
|
|
e890ba0f57 | ||
|
|
744d94cf8d | ||
|
|
7764eda05d |
6
.github/workflows/linux.yml
vendored
6
.github/workflows/linux.yml
vendored
@@ -104,6 +104,9 @@ jobs:
|
||||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: install nightwatch dep
|
||||
run: sudo apt-get install xvfb
|
||||
|
||||
- name: Run migrations
|
||||
run: make migrate
|
||||
env:
|
||||
@@ -128,6 +131,9 @@ jobs:
|
||||
POSTGRES_DATABASE_URL: "${{ env.POSTGRES_DATABASE_URL }}"
|
||||
MARIA_DATABASE_URL: "${{ env.MARIA_DATABASE_URL }}"
|
||||
|
||||
- name: run integration tests
|
||||
run: make test.integration
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: (github.ref == 'refs/heads/master' || github.event_name == 'push') && github.repository == 'mCaptcha/mCaptcha'
|
||||
uses: docker/login-action@v1
|
||||
|
||||
19
Cargo.lock
generated
19
Cargo.lock
generated
@@ -873,6 +873,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1977,9 +1978,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.41"
|
||||
version = "0.10.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
|
||||
checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -2018,9 +2019,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.75"
|
||||
version = "0.9.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
|
||||
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
@@ -3392,6 +3393,16 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "validator"
|
||||
version = "0.15.0"
|
||||
|
||||
@@ -78,7 +78,7 @@ lettre = { version = "0.10.0-rc.3", features = [
|
||||
"smtp-transport"
|
||||
]}
|
||||
|
||||
openssl = { version = "0.10.29", features = ["vendored"] }
|
||||
openssl = { version = "0.10.48", features = ["vendored"] }
|
||||
|
||||
|
||||
[dependencies.db-core]
|
||||
|
||||
3
Makefile
3
Makefile
@@ -123,6 +123,9 @@ test: frontend-test frontend ## Run all available tests
|
||||
cargo test --no-fail-fast
|
||||
# ./scripts/tests.sh
|
||||
|
||||
test.integration: ## run integration tests with nightwatch.js
|
||||
./scripts/integration.sh
|
||||
|
||||
xml-test-coverage: migrate ## Generate code coverage report in XML format
|
||||
$(call cache_bust)
|
||||
cargo tarpaulin -t 1200 --out Xml
|
||||
|
||||
14
README.md
14
README.md
@@ -32,11 +32,11 @@ yourself!](https://demo.mcaptcha.org/widget/?sitekey=pHy0AktWyOKuxZDzFfoaewncWec
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
is click on a button to initiate the process.
|
||||
|
||||
mCaptcha makes interacting with websites (computationally)expensive for
|
||||
the user. A well-behaving user will experience a slight delay(no delay
|
||||
mCaptcha makes interacting with websites (computationally) expensive for
|
||||
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
|
||||
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
|
||||
@@ -68,7 +68,7 @@ to their request.
|
||||
- [x] **No tracking:** Our CAPTCHA routes are cookie free!
|
||||
- [x] **IP address independent:** your users are behind a NAT? We got you covered!
|
||||
- [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
|
||||
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
|
||||
> 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).
|
||||
|
||||
### Self-hosted:
|
||||
|
||||
@@ -15,6 +15,7 @@ serde = { version = "1", features = ["derive"]}
|
||||
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 = { branch = "master", git = "https://github.com/mCaptcha/libmcaptcha", features = ["full"] }
|
||||
uuid = { version = "1.3.3", features = ["v4", "serde"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -31,7 +31,10 @@
|
||||
//! - [errors](crate::auth): error data structures used in this crate
|
||||
//! - [ops](crate::ops): meta operations like connection pool creation, migrations and getting
|
||||
//! connection from pool
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use libmcaptcha::defense::Level;
|
||||
|
||||
@@ -97,6 +100,73 @@ pub struct NameHash {
|
||||
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]
|
||||
/// mCaptcha's database requirements. To implement support for $Database, kindly implement this
|
||||
/// trait.
|
||||
@@ -250,6 +320,19 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
|
||||
|
||||
/// fetch PoWConfig confirms
|
||||
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)]
|
||||
|
||||
@@ -295,4 +295,13 @@ pub async fn database_works<'a, T: MCDatabase>(
|
||||
// delete captcha; updated key = p.username so invoke delete with it
|
||||
db.delete_captcha(p.username, p.username).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",
|
||||
"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": {
|
||||
"describe": {
|
||||
"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;"
|
||||
},
|
||||
"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": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -873,6 +931,16 @@
|
||||
},
|
||||
"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": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
||||
@@ -95,6 +95,22 @@ impl Migrate for Database {
|
||||
.run(&self.pool)
|
||||
.await
|
||||
.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(())
|
||||
}
|
||||
}
|
||||
@@ -895,6 +911,93 @@ impl MCDatabase for Database {
|
||||
|
||||
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)]
|
||||
|
||||
@@ -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 );"
|
||||
},
|
||||
"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": {
|
||||
"describe": {
|
||||
"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) "
|
||||
},
|
||||
"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": {
|
||||
"describe": {
|
||||
"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"
|
||||
},
|
||||
"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": {
|
||||
"describe": {
|
||||
"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;"
|
||||
},
|
||||
"a209d14eb2c2eba8a750d66f74f8edcdbb02cf7c6c5249b226db30f52541a79b": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "INSERT INTO\n mcaptcha_challenge_reason (name)\n VALUES ($1) ON CONFLICT DO NOTHING\n "
|
||||
},
|
||||
"ad196ab3ef9dc32f6de2313577ccd6c26eae9ab19df5f71ce182651983efb99a": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
|
||||
@@ -95,6 +95,23 @@ impl Migrate for Database {
|
||||
.run(&self.pool)
|
||||
.await
|
||||
.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(())
|
||||
}
|
||||
}
|
||||
@@ -901,6 +918,99 @@ impl MCDatabase for Database {
|
||||
|
||||
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)]
|
||||
|
||||
@@ -38,7 +38,7 @@ you will be overriding the values set in the configuration files.
|
||||
| `MCAPTCHA_DATEBASE_USERNAME` | database username |
|
||||
| `MCAPTCHA_DATEBASE_POOL` | database connection pool size |
|
||||
| `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
|
||||
|
||||
|
||||
@@ -1799,9 +1799,9 @@ minimatch@3.0.4, minimatch@^3.0.4:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
mkdirp@^1.0.4:
|
||||
version "1.0.4"
|
||||
|
||||
39
scripts/integration.sh
Executable file
39
scripts/integration.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
trap cleanup SIGINT SIGTERM ERR EXIT
|
||||
|
||||
readonly PROJECT_ROOT=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
|
||||
|
||||
source $PROJECT_ROOT/scripts/lib.sh
|
||||
|
||||
is_ci(){
|
||||
if [ -z ${CI+x} ];
|
||||
then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
docker-compose down -v --remove-orphans || true
|
||||
docker-compose up -d
|
||||
cd $(mktemp -d)
|
||||
pwd
|
||||
find
|
||||
git clone https://github.com/mCaptcha/integration .
|
||||
|
||||
if is_ci
|
||||
then
|
||||
yarn install
|
||||
xvfb-run --auto-servernum npm run test.chrome
|
||||
xvfb-run --auto-servernum npm run test.firefox
|
||||
else
|
||||
yarn install
|
||||
npx nightwatch ./test/mCaptcha.ts
|
||||
fi
|
||||
|
||||
cd $PROJECT_ROOT
|
||||
docker-compose down -v --remove-orphans || true
|
||||
@@ -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
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||
*/
|
||||
|
||||
* {
|
||||
|
||||
@@ -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
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||
*/
|
||||
|
||||
@import '../reset';
|
||||
@import "../reset";
|
||||
|
||||
.widget__contaienr {
|
||||
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
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* Use of this source code is governed by Apache 2.0 or MIT license.
|
||||
* You shoud have received a copy of MIT and Apache 2.0 along with
|
||||
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
|
||||
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
|
||||
*/
|
||||
import prove from "./prove";
|
||||
import { PoWConfig, ServiceWorkerWork } from "./types";
|
||||
|
||||
import log from "../logger";
|
||||
|
||||
import prove from "./prove";
|
||||
import {PoWConfig, ServiceWorkerWork} from "./types";
|
||||
|
||||
log.log("worker registered");
|
||||
onmessage = async (e) => {
|
||||
console.debug("message received at worker");
|
||||
|
||||
Reference in New Issue
Block a user