diff --git a/db/db-core/src/lib.rs b/db/db-core/src/lib.rs index a5fc7b20..1a51b31e 100644 --- a/db/db-core/src/lib.rs +++ b/db/db-core/src/lib.rs @@ -292,6 +292,21 @@ pub trait MCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase { /// Get all psuedo IDs async fn analytics_get_all_psuedo_ids(&self, page: usize) -> DBResult>; + + /// Track maximum nonce received against captcha levels + async fn update_max_nonce_for_level( + &self, + captcha_key: &str, + difficulty_factor: u32, + latest_nonce: u32, + ) -> DBResult<()>; + + /// Get maximum nonce tracked so far for captcha levels + async fn get_max_nonce_for_level( + &self, + captcha_key: &str, + difficulty_factor: u32, + ) -> DBResult; } #[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] diff --git a/db/db-core/src/tests.rs b/db/db-core/src/tests.rs index 675e25d7..fd59230c 100644 --- a/db/db-core/src/tests.rs +++ b/db/db-core/src/tests.rs @@ -310,6 +310,33 @@ pub async fn database_works<'a, T: MCDatabase>( .unwrap(); // analytics end + // nonce tracking start + assert_eq!( + db.get_max_nonce_for_level(c.key, l[0].difficulty_factor) + .await + .unwrap(), + 0 + ); + db.update_max_nonce_for_level(c.key, l[0].difficulty_factor, 1000) + .await + .unwrap(); + assert_eq!( + db.get_max_nonce_for_level(c.key, l[0].difficulty_factor) + .await + .unwrap(), + 1000 + ); + db.update_max_nonce_for_level(c.key, l[0].difficulty_factor, 10_000) + .await + .unwrap(); + assert_eq!( + db.get_max_nonce_for_level(c.key, l[0].difficulty_factor) + .await + .unwrap(), + 10_000 + ); + // nonce tracking end + assert_eq!(db.fetch_solve(p.username, c.key).await.unwrap().len(), 1); assert_eq!( db.fetch_config_fetched(p.username, c.key) diff --git a/db/db-sqlx-maria/.sqlx/query-216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc.json b/db/db-sqlx-maria/.sqlx/query-216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc.json new file mode 100644 index 00000000..e0d329a7 --- /dev/null +++ b/db/db-sqlx-maria/.sqlx/query-216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n AND\n visitor_threshold = ?\n ), ?);", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "216478d53870d7785cd0be43f030883ab79eaafb558d9197d09aea3adbd7b0bc" +} diff --git a/db/db-sqlx-maria/.sqlx/query-349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea.json b/db/db-sqlx-maria/.sqlx/query-349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea.json new file mode 100644 index 00000000..54c52c6e --- /dev/null +++ b/db/db-sqlx-maria/.sqlx/query-349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea.json @@ -0,0 +1,12 @@ +{ + "db_name": "MySQL", + "query": "UPDATE mcaptcha_track_nonce SET nonce = ?\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n )\n AND nonce <= ?;", + "describe": { + "columns": [], + "parameters": { + "Right": 4 + }, + "nullable": [] + }, + "hash": "349ba17ff197aca7ee9fbd43e227d181c27ae04702fd6bdb6ddc32aab3bcb1ea" +} diff --git a/db/db-sqlx-maria/.sqlx/query-b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb.json b/db/db-sqlx-maria/.sqlx/query-b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb.json new file mode 100644 index 00000000..1212099c --- /dev/null +++ b/db/db-sqlx-maria/.sqlx/query-b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb.json @@ -0,0 +1,25 @@ +{ + "db_name": "MySQL", + "query": "SELECT nonce FROM mcaptcha_track_nonce\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?)\n AND\n difficulty_factor = ?\n );", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "nonce", + "type_info": { + "type": "Long", + "flags": "NOT_NULL", + "char_set": 63, + "max_size": 11 + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false + ] + }, + "hash": "b739ec4cfab1ec60947106c8112e931510c3a50a1606facdde0c0ebb540d5beb" +} diff --git a/db/db-sqlx-maria/migrations/20231029000927_mcaptcha_track_nonce.sql b/db/db-sqlx-maria/migrations/20231029000927_mcaptcha_track_nonce.sql new file mode 100644 index 00000000..02b45f99 --- /dev/null +++ b/db/db-sqlx-maria/migrations/20231029000927_mcaptcha_track_nonce.sql @@ -0,0 +1,12 @@ +-- Add migration script here +CREATE TABLE IF NOT EXISTS mcaptcha_track_nonce ( + level_id INTEGER NOT NULL, + nonce INTEGER NOT NULL DEFAULT 0, + ID INT auto_increment, + PRIMARY KEY(ID), + CONSTRAINT `fk_mcaptcha_track_nonce_level_id` + FOREIGN KEY (level_id) + REFERENCES mcaptcha_levels (level_id) + ON DELETE CASCADE + ON UPDATE CASCADE +); diff --git a/db/db-sqlx-maria/src/lib.rs b/db/db-sqlx-maria/src/lib.rs index f0d12bc5..2433f365 100644 --- a/db/db-sqlx-maria/src/lib.rs +++ b/db/db-sqlx-maria/src/lib.rs @@ -433,6 +433,39 @@ impl MCDatabase for Database { futs.push(fut); } + try_join_all(futs) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + let mut futs = Vec::with_capacity(levels.len()); + + for level in levels.iter() { + let difficulty_factor = level.difficulty_factor as i32; + let visitor_threshold = level.visitor_threshold as i32; + let fut = sqlx::query!( + "INSERT INTO + mcaptcha_track_nonce (level_id, nonce) + VALUES (( + SELECT + level_id + FROM + mcaptcha_levels + WHERE + config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?) + AND + difficulty_factor = ? + AND + visitor_threshold = ? + ), ?);", + &captcha_key, + difficulty_factor, + visitor_threshold, + 0, + ) + .execute(&self.pool); + futs.push(fut); + } + try_join_all(futs) .await .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; @@ -1087,6 +1120,70 @@ impl MCDatabase for Database { Ok(res.drain(0..).map(|r| r.psuedo_id).collect()) } + + /// Track maximum nonce received against captcha levels + async fn update_max_nonce_for_level( + &self, + captcha_key: &str, + difficulty_factor: u32, + latest_nonce: u32, + ) -> DBResult<()> { + let latest_nonce = latest_nonce as i64; + sqlx::query!( + "UPDATE mcaptcha_track_nonce SET nonce = ? + WHERE level_id = ( + SELECT + level_id + FROM + mcaptcha_levels + WHERE + config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?) + AND + difficulty_factor = ? + ) + AND nonce <= ?;", + latest_nonce, + &captcha_key, + difficulty_factor as i64, + latest_nonce + ) + .execute(&self.pool).await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + Ok(()) + } + + /// Get maximum nonce tracked so far for captcha levels + async fn get_max_nonce_for_level( + &self, + captcha_key: &str, + difficulty_factor: u32, + ) -> DBResult { + struct X { + nonce: i32, + } + + let res = sqlx::query_as!( + X, + "SELECT nonce FROM mcaptcha_track_nonce + WHERE level_id = ( + SELECT + level_id + FROM + mcaptcha_levels + WHERE + config_id = (SELECT config_id FROM mcaptcha_config WHERE captcha_key = ?) + AND + difficulty_factor = ? + );", + &captcha_key, + difficulty_factor as i32, + ) + .fetch_one(&self.pool).await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + Ok(res.nonce as u32) + } } #[derive(Clone)] diff --git a/db/db-sqlx-postgres/.sqlx/query-133ee23ab5ac7c664a86b6edfaa8da79281b6d1f5ba33c642a6ea1b0682fe0b0.json b/db/db-sqlx-postgres/.sqlx/query-133ee23ab5ac7c664a86b6edfaa8da79281b6d1f5ba33c642a6ea1b0682fe0b0.json new file mode 100644 index 00000000..65f9c670 --- /dev/null +++ b/db/db-sqlx-postgres/.sqlx/query-133ee23ab5ac7c664a86b6edfaa8da79281b6d1f5ba33c642a6ea1b0682fe0b0.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO\n mcaptcha_track_nonce (level_id, nonce)\n VALUES ((\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n AND\n visitor_threshold = $3\n ), $4);", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Int4", + "Int4", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "133ee23ab5ac7c664a86b6edfaa8da79281b6d1f5ba33c642a6ea1b0682fe0b0" +} diff --git a/db/db-sqlx-postgres/.sqlx/query-96f1f1e45144d5add6c4ba4cd2df8eda6043bc8cd6952787f92a687fef778a6e.json b/db/db-sqlx-postgres/.sqlx/query-96f1f1e45144d5add6c4ba4cd2df8eda6043bc8cd6952787f92a687fef778a6e.json new file mode 100644 index 00000000..b74870c2 --- /dev/null +++ b/db/db-sqlx-postgres/.sqlx/query-96f1f1e45144d5add6c4ba4cd2df8eda6043bc8cd6952787f92a687fef778a6e.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT nonce FROM mcaptcha_track_nonce\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n );", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "nonce", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "96f1f1e45144d5add6c4ba4cd2df8eda6043bc8cd6952787f92a687fef778a6e" +} diff --git a/db/db-sqlx-postgres/.sqlx/query-e33ee14cf76cd09d9a157b8784a3fe25b89eaca105aa30e479d31b756cd5c88b.json b/db/db-sqlx-postgres/.sqlx/query-e33ee14cf76cd09d9a157b8784a3fe25b89eaca105aa30e479d31b756cd5c88b.json new file mode 100644 index 00000000..fabcf9bf --- /dev/null +++ b/db/db-sqlx-postgres/.sqlx/query-e33ee14cf76cd09d9a157b8784a3fe25b89eaca105aa30e479d31b756cd5c88b.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE mcaptcha_track_nonce SET nonce = $3\n WHERE level_id = (\n SELECT\n level_id\n FROM\n mcaptcha_levels\n WHERE\n config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1))\n AND\n difficulty_factor = $2\n )\n AND nonce <= $3;", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Int4", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "e33ee14cf76cd09d9a157b8784a3fe25b89eaca105aa30e479d31b756cd5c88b" +} diff --git a/db/db-sqlx-postgres/migrations/20231028235346_mcaptcha_track_nonce.sql b/db/db-sqlx-postgres/migrations/20231028235346_mcaptcha_track_nonce.sql new file mode 100644 index 00000000..eb453924 --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20231028235346_mcaptcha_track_nonce.sql @@ -0,0 +1,6 @@ +-- Add migration script here +CREATE TABLE IF NOT EXISTS mcaptcha_track_nonce ( + nonce INTEGER NOT NULL DEFAULT 0, + level_id INTEGER references mcaptcha_levels(level_id) ON DELETE CASCADE, + ID SERIAL PRIMARY KEY NOT NULL +); diff --git a/db/db-sqlx-postgres/src/lib.rs b/db/db-sqlx-postgres/src/lib.rs index d0b2eb7b..afa7c652 100644 --- a/db/db-sqlx-postgres/src/lib.rs +++ b/db/db-sqlx-postgres/src/lib.rs @@ -445,6 +445,38 @@ impl MCDatabase for Database { futs.push(fut); } + try_join_all(futs) + .await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + let mut futs = Vec::with_capacity(levels.len()); + for level in levels.iter() { + let difficulty_factor = level.difficulty_factor as i32; + let visitor_threshold = level.visitor_threshold as i32; + let fut = sqlx::query!( + "INSERT INTO + mcaptcha_track_nonce (level_id, nonce) + VALUES (( + SELECT + level_id + FROM + mcaptcha_levels + WHERE + config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1)) + AND + difficulty_factor = $2 + AND + visitor_threshold = $3 + ), $4);", + &captcha_key, + difficulty_factor, + visitor_threshold, + 0, + ) + .execute(&self.pool); + futs.push(fut); + } + try_join_all(futs) .await .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; @@ -1097,6 +1129,68 @@ impl MCDatabase for Database { Ok(res.drain(0..).map(|r| r.psuedo_id).collect()) } + + /// Track maximum nonce received against captcha levels + async fn update_max_nonce_for_level( + &self, + captcha_key: &str, + difficulty_factor: u32, + latest_nonce: u32, + ) -> DBResult<()> { + sqlx::query!( + "UPDATE mcaptcha_track_nonce SET nonce = $3 + WHERE level_id = ( + SELECT + level_id + FROM + mcaptcha_levels + WHERE + config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1)) + AND + difficulty_factor = $2 + ) + AND nonce <= $3;", + &captcha_key, + difficulty_factor as i32, + latest_nonce as i32, + ) + .execute(&self.pool).await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + Ok(()) + } + + /// Get maximum nonce tracked so far for captcha levels + async fn get_max_nonce_for_level( + &self, + captcha_key: &str, + difficulty_factor: u32, + ) -> DBResult { + struct X { + nonce: i32, + } + + let res = sqlx::query_as!( + X, + "SELECT nonce FROM mcaptcha_track_nonce + WHERE level_id = ( + SELECT + level_id + FROM + mcaptcha_levels + WHERE + config_id = (SELECT config_id FROM mcaptcha_config WHERE key = ($1)) + AND + difficulty_factor = $2 + );", + &captcha_key, + difficulty_factor as i32, + ) + .fetch_one(&self.pool).await + .map_err(|e| map_row_not_found_err(e, DBError::CaptchaNotFound))?; + + Ok(res.nonce as u32) + } } #[derive(Clone)]