Compare commits
4 commits
master
...
wip-mail-p
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6f029d2945 | ||
![]() |
e7b01a5b06 | ||
![]() |
0cfffed52e | ||
![]() |
c53fe2e3ff |
10 changed files with 491 additions and 0 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Add table
Reference in a new issue