diff --git a/Cargo.lock b/Cargo.lock index 184645b2..27eab688 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -873,6 +873,7 @@ dependencies = [ "serde_json", "thiserror", "url", + "uuid", ] [[package]] @@ -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" diff --git a/db/db-sqlx-postgres/migrations/20230610105810_mcaptcha_challenge.sql b/db/db-sqlx-postgres/migrations/20230610105810_mcaptcha_challenge.sql new file mode 100644 index 00000000..f372749a --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20230610105810_mcaptcha_challenge.sql @@ -0,0 +1,11 @@ +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, + challenge_id varchar(40) NOT NULL UNIQUE, + received timestamptz NOT NULL DEFAULT now() +); diff --git a/db/db-sqlx-postgres/sqlx-data.json b/db/db-sqlx-postgres/sqlx-data.json index 3b4cb791..c220e512 100644 --- a/db/db-sqlx-postgres/sqlx-data.json +++ b/db/db-sqlx-postgres/sqlx-data.json @@ -427,6 +427,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 +466,53 @@ }, "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 " + }, + "aacf81df0a8ca303428d51345cd6f72e015808103516b9f32723aadf302afcdc": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int4" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + } + }, + "query": "SELECT ID\n FROM mcaptcha_challenge\n WHERE\n challenge_id = $1\n AND reason = (SELECT ID FROM mcaptcha_challenge_reason WHERE name = $2);" + }, + "ab2a6711d8a457f055ce005ff971e9fd6f4d77f425d4eb26cc5ae050aac647f1": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Timestamptz", + "Text" + ] + } + }, + "query": "INSERT INTO mcaptcha_challenge (challenge_id, received, reason)\n VALUES ($1, $2, (SELECT ID FROM mcaptcha_challenge_reason WHERE name = $3));\n " + }, "ad196ab3ef9dc32f6de2313577ccd6c26eae9ab19df5f71ce182651983efb99a": { "describe": { "columns": [ diff --git a/db/db-sqlx-postgres/src/lib.rs b/db/db-sqlx-postgres/src/lib.rs index 752026ca..493bafeb 100644 --- a/db/db-sqlx-postgres/src/lib.rs +++ b/db/db-sqlx-postgres/src/lib.rs @@ -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,73 @@ impl MCDatabase for Database { Ok(Date::dates_to_unix(records)) } + + /// Record challenge in database + async fn new_challenge(&self, challenge: &mut Challenge) -> DBResult<()> { + let now = now_unix_time_stamp(); + loop { + let res = sqlx::query!( + "INSERT INTO mcaptcha_challenge (challenge_id, received, reason) + VALUES ($1, $2, (SELECT ID FROM mcaptcha_challenge_reason WHERE name = $3)); + ", + &challenge.challenge.to_string(), + now, + challenge.reason.to_str() + ) + .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(&self, challenge: &Challenge) -> DBResult { + struct C { + id: i32, + } + sqlx::query_as!( + C, + "SELECT 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(challenge.clone()) + } + + /// 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)] diff --git a/db/db-sqlx-postgres/src/tests.rs b/db/db-sqlx-postgres/src/tests.rs index ac098278..cce6e0b2 100644 --- a/db/db-sqlx-postgres/src/tests.rs +++ b/db/db-sqlx-postgres/src/tests.rs @@ -89,4 +89,6 @@ async fn everyting_works() { description: CAPTCHA_DESCRIPTION, }; database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN, &ADD_NOTIFICATION).await; + + challenges_works(&db).await; }