domain verification

This commit is contained in:
realaravinth
2021-03-26 22:18:01 +05:30
parent ee548588a8
commit 51764817f9
17 changed files with 623 additions and 108 deletions

216
Cargo.lock generated
View File

@@ -40,16 +40,32 @@ dependencies = [
"tokio-util 0.3.1", "tokio-util 0.3.1",
] ]
[[package]]
name = "actix-codec"
version = "0.4.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90673465c6187bd0829116b02be465dc0195a74d7719f76ffff0effef934a92e"
dependencies = [
"bitflags",
"bytes 1.0.1",
"futures-core",
"futures-sink",
"log",
"pin-project-lite 0.2.6",
"tokio 1.4.0",
"tokio-util 0.6.5",
]
[[package]] [[package]]
name = "actix-connect" name = "actix-connect"
version = "2.0.0" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc"
dependencies = [ dependencies = [
"actix-codec", "actix-codec 0.3.0",
"actix-rt 1.1.1", "actix-rt 1.1.1",
"actix-service", "actix-service 1.0.6",
"actix-utils", "actix-utils 2.0.0",
"derive_more", "derive_more",
"either", "either",
"futures-util", "futures-util",
@@ -65,12 +81,12 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874"
dependencies = [ dependencies = [
"actix-codec", "actix-codec 0.3.0",
"actix-connect", "actix-connect",
"actix-rt 1.1.1", "actix-rt 1.1.1",
"actix-service", "actix-service 1.0.6",
"actix-threadpool", "actix-threadpool",
"actix-utils", "actix-utils 2.0.0",
"base64", "base64",
"bitflags", "bitflags",
"brotli2", "brotli2",
@@ -85,7 +101,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-util", "futures-util",
"fxhash", "fxhash",
"h2", "h2 0.2.7",
"http", "http",
"httparse", "httparse",
"indexmap", "indexmap",
@@ -106,13 +122,58 @@ dependencies = [
"time 0.2.26", "time 0.2.26",
] ]
[[package]]
name = "actix-http"
version = "3.0.0-beta.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a01f9e0681608afa887d4269a0857ac4226f09ba5ceda25939e8391c9da610a"
dependencies = [
"actix-codec 0.4.0-beta.1",
"actix-rt 2.1.0",
"actix-service 2.0.0-beta.5",
"actix-tls 3.0.0-beta.4",
"actix-utils 3.0.0-beta.2",
"ahash 0.7.2",
"base64",
"bitflags",
"brotli2",
"bytes 1.0.1",
"bytestring",
"cfg-if 1.0.0",
"cookie",
"derive_more",
"encoding_rs",
"flate2",
"futures-core",
"futures-util",
"h2 0.3.2",
"http",
"httparse",
"itoa",
"language-tags",
"log",
"mime",
"once_cell",
"percent-encoding",
"pin-project 1.0.5",
"rand 0.8.3",
"regex",
"serde 1.0.125",
"serde_json",
"serde_urlencoded",
"sha-1",
"smallvec",
"time 0.2.26",
"tokio 1.4.0",
]
[[package]] [[package]]
name = "actix-identity" name = "actix-identity"
version = "0.3.1" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3263fe74cf505c6f9e18209c89fbdba5569cfd3905a7e907b42aa1c85c18fae5" checksum = "3263fe74cf505c6f9e18209c89fbdba5569cfd3905a7e907b42aa1c85c18fae5"
dependencies = [ dependencies = [
"actix-service", "actix-service 1.0.6",
"actix-web", "actix-web",
"futures-util", "futures-util",
"serde 1.0.125", "serde 1.0.125",
@@ -185,10 +246,10 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e"
dependencies = [ dependencies = [
"actix-codec", "actix-codec 0.3.0",
"actix-rt 1.1.1", "actix-rt 1.1.1",
"actix-service", "actix-service 1.0.6",
"actix-utils", "actix-utils 2.0.0",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"log", "log",
@@ -209,6 +270,16 @@ dependencies = [
"pin-project 0.4.27", "pin-project 0.4.27",
] ]
[[package]]
name = "actix-service"
version = "2.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf82340ad9f4e4caf43737fd3bbc999778a268015cdc54675f60af6240bd2b05"
dependencies = [
"futures-core",
"pin-project-lite 0.2.6",
]
[[package]] [[package]]
name = "actix-testing" name = "actix-testing"
version = "1.0.1" version = "1.0.1"
@@ -218,7 +289,7 @@ dependencies = [
"actix-macros 0.1.3", "actix-macros 0.1.3",
"actix-rt 1.1.1", "actix-rt 1.1.1",
"actix-server", "actix-server",
"actix-service", "actix-service 1.0.6",
"log", "log",
"socket2", "socket2",
] ]
@@ -244,21 +315,38 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb"
dependencies = [ dependencies = [
"actix-codec", "actix-codec 0.3.0",
"actix-service", "actix-service 1.0.6",
"actix-utils", "actix-utils 2.0.0",
"futures-util", "futures-util",
] ]
[[package]]
name = "actix-tls"
version = "3.0.0-beta.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b1455e3f7a26d40cfc1080b571f41e8165e5a88e937ed579f7a4b3d55b0370"
dependencies = [
"actix-codec 0.4.0-beta.1",
"actix-rt 2.1.0",
"actix-service 2.0.0-beta.5",
"actix-utils 3.0.0-beta.2",
"derive_more",
"futures-core",
"http",
"log",
"tokio-util 0.6.5",
]
[[package]] [[package]]
name = "actix-utils" name = "actix-utils"
version = "2.0.0" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a"
dependencies = [ dependencies = [
"actix-codec", "actix-codec 0.3.0",
"actix-rt 1.1.1", "actix-rt 1.1.1",
"actix-service", "actix-service 1.0.6",
"bitflags", "bitflags",
"bytes 0.5.6", "bytes 0.5.6",
"either", "either",
@@ -270,25 +358,40 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "actix-utils"
version = "3.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458795e09a29bc5557604f9ff6f32236fd0ee457d631672e4ec8f6a0103bb292"
dependencies = [
"actix-codec 0.4.0-beta.1",
"actix-rt 2.1.0",
"actix-service 2.0.0-beta.5",
"futures-core",
"futures-sink",
"log",
"pin-project-lite 0.2.6",
]
[[package]] [[package]]
name = "actix-web" name = "actix-web"
version = "3.3.2" version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86"
dependencies = [ dependencies = [
"actix-codec", "actix-codec 0.3.0",
"actix-http", "actix-http 2.2.0",
"actix-macros 0.1.3", "actix-macros 0.1.3",
"actix-router", "actix-router",
"actix-rt 1.1.1", "actix-rt 1.1.1",
"actix-server", "actix-server",
"actix-service", "actix-service 1.0.6",
"actix-testing", "actix-testing",
"actix-threadpool", "actix-threadpool",
"actix-tls", "actix-tls 2.0.0",
"actix-utils", "actix-utils 2.0.0",
"actix-web-codegen", "actix-web-codegen",
"awc", "awc 2.0.3",
"bytes 0.5.6", "bytes 0.5.6",
"derive_more", "derive_more",
"encoding_rs", "encoding_rs",
@@ -408,6 +511,17 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "ahash"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f200cbb1e856866d9eade941cf3aa0c5d7dd36f74311c4273b494f4ef036957"
dependencies = [
"getrandom 0.2.2",
"once_cell",
"version_check",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.15" version = "0.7.15"
@@ -503,10 +617,10 @@ version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691"
dependencies = [ dependencies = [
"actix-codec", "actix-codec 0.3.0",
"actix-http", "actix-http 2.2.0",
"actix-rt 1.1.1", "actix-rt 1.1.1",
"actix-service", "actix-service 1.0.6",
"base64", "base64",
"bytes 0.5.6", "bytes 0.5.6",
"cfg-if 1.0.0", "cfg-if 1.0.0",
@@ -521,6 +635,32 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
] ]
[[package]]
name = "awc"
version = "3.0.0-beta.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09aecd8728f6491a62b27454ea4b36fb7e50faf32928b0369b644e402c651f4e"
dependencies = [
"actix-codec 0.4.0-beta.1",
"actix-http 3.0.0-beta.4",
"actix-rt 2.1.0",
"actix-service 2.0.0-beta.5",
"base64",
"bytes 1.0.1",
"cfg-if 1.0.0",
"derive_more",
"futures-core",
"itoa",
"log",
"mime",
"percent-encoding",
"pin-project-lite 0.2.6",
"rand 0.8.3",
"serde 1.0.125",
"serde_json",
"serde_urlencoded",
]
[[package]] [[package]]
name = "base-x" name = "base-x"
version = "0.2.8" version = "0.2.8"
@@ -1145,14 +1285,17 @@ name = "guard"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix", "actix",
"actix-http", "actix-http 2.2.0",
"actix-identity", "actix-identity",
"actix-rt 1.1.1",
"actix-rt 2.1.0", "actix-rt 2.1.0",
"actix-web", "actix-web",
"argon2-creds", "argon2-creds",
"awc 3.0.0-beta.3",
"config", "config",
"derive_builder", "derive_builder",
"derive_more", "derive_more",
"futures",
"lazy_static", "lazy_static",
"log", "log",
"m_captcha", "m_captcha",
@@ -1185,6 +1328,25 @@ dependencies = [
"tracing-futures", "tracing-futures",
] ]
[[package]]
name = "h2"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc018e188373e2777d0ef2467ebff62a08e66c3f5857b23c8fbec3018210dc00"
dependencies = [
"bytes 1.0.1",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio 1.4.0",
"tokio-util 0.6.5",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.9.1" version = "0.9.1"

View File

@@ -21,7 +21,9 @@ path = "./src/main.rs"
name = "tests-migrate" name = "tests-migrate"
path = "./src/tests-migrate.rs" path = "./src/tests-migrate.rs"
[[bin]]
name = "kv-test-util"
path = "./src/api/v1/tests/kvserver.rs"
[dependencies] [dependencies]
actix-web = "3" actix-web = "3"
@@ -29,6 +31,9 @@ actix = "0.11"
actix-identity = "0.3" actix-identity = "0.3"
actix-http = "2.2" actix-http = "2.2"
actix-rt = "2" actix-rt = "2"
awc = "3.0.0-beta.3"
futures = "0.3"
sqlx = { version = "0.5.0", features = [ "runtime-actix-rustls", "postgres" ] } sqlx = { version = "0.5.0", features = [ "runtime-actix-rustls", "postgres" ] }
argon2-creds = { version = "0.2", git = "https://github.com/realaravinth/argon2-creds", commit = "61f2d1d" } argon2-creds = { version = "0.2", git = "https://github.com/realaravinth/argon2-creds", commit = "61f2d1d" }
@@ -53,3 +58,6 @@ lazy_static = "1.4"
m_captcha = { version = "0.1.2", git = "https://github.com/mCaptcha/mCaptcha" } m_captcha = { version = "0.1.2", git = "https://github.com/mCaptcha/mCaptcha" }
rand = "0.8" rand = "0.8"
[dev-dependencies]
rt = { package = "actix-rt", version = "1"}

View File

@@ -1,4 +0,0 @@
CREATE TABLE IF NOT EXISTS mcaptcha_domains (
name VARCHAR(100) PRIMARY KEY NOT NULL UNIQUE,
ID INTEGER references mcaptcha_users(ID) ON DELETE CASCADE NOT NULL
);

View File

@@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS mcaptcha_domains_verified (
name VARCHAR(100) PRIMARY KEY NOT NULL UNIQUE,
owner_id INTEGER references mcaptcha_users(ID) ON DELETE CASCADE NOT NULL
);

View File

@@ -1,7 +1,7 @@
CREATE TABLE IF NOT EXISTS mcaptcha_config ( CREATE TABLE IF NOT EXISTS mcaptcha_config (
config_id SERIAL PRIMARY KEY NOT NULL, config_id SERIAL PRIMARY KEY NOT NULL,
domain_name VARCHAR(100) NOT NULL references mcaptcha_domains(name) ON DELETE CASCADE, domain_name varchar(100) NOT NULL references mcaptcha_domains_verified(name) ON DELETE CASCADE,
key VARCHAR(100) NOT NULL UNIQUE, key varchar(100) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL UNIQUE, name varchar(100) NOT NULL UNIQUE,
duration INTEGER NOT NULL DEFAULT 30 duration integer NOT NULL DEFAULT 30
); );

View File

@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS mcaptcha_domains_unverified (
name VARCHAR(100) PRIMARY KEY NOT NULL,
owner_id INTEGER references mcaptcha_users(ID) ON DELETE CASCADE NOT NULL,
verified BOOLEAN DEFAULT NULL,
verification_challenge VARCHAR(32) NOT NULL
);

36
network.sh Executable file
View File

@@ -0,0 +1,36 @@
set -e
launch_kv() {
bash -c "exec -a kv ./target/debug/kv-test-util $1"
}
kill_kv() {
kill -9 $(pidof kv)
}
help() {
cat << EOF
USAGE:
./network.sh
launch launches key-value server
kill kills key-value server
EOF
}
if [ -z $1 ]
then
help
elif [ $1 == 'launch' ]
then
if [ -z $2 ]
then
help
else
launch_kv $2
fi
elif [ $1 == 'kill' ]
then
kill_kv
else
help
fi

View File

@@ -107,11 +107,8 @@ pub async fn signout(id: Identity) -> impl Responder {
// TODO use middleware // TODO use middleware
pub fn is_authenticated(id: &Identity) -> ServiceResult<()> { pub fn is_authenticated(id: &Identity) -> ServiceResult<()> {
// access request identity // access request identity
if let Some(_) = id.identity() { id.identity().ok_or(ServiceError::AuthorizationRequired)?;
Ok(()) Ok(())
} else {
Err(ServiceError::AuthorizationRequired)
}
} }
#[post("/api/v1/account/delete")] #[post("/api/v1/account/delete")]

View File

@@ -17,10 +17,11 @@
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{post, web, HttpResponse, Responder}; use actix_web::{post, web, HttpResponse, Responder};
use awc::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
use super::is_authenticated; use super::{get_random, is_authenticated};
use crate::errors::*; use crate::errors::*;
use crate::Data; use crate::Data;
@@ -37,13 +38,16 @@ pub async fn add_domain(
) -> ServiceResult<impl Responder> { ) -> ServiceResult<impl Responder> {
is_authenticated(&id)?; is_authenticated(&id)?;
let url = Url::parse(&payload.name)?; let url = Url::parse(&payload.name)?;
if let Some(host) = url.host_str() {
let host = url.host_str().ok_or(ServiceError::NotAUrl)?;
let user = id.identity().unwrap(); let user = id.identity().unwrap();
let challenge = get_random(32);
let res = sqlx::query!( let res = sqlx::query!(
"INSERT INTO mcaptcha_domains (name, ID) VALUES "INSERT INTO mcaptcha_domains_unverified (name, owner_id, verification_challenge) VALUES
($1, (SELECT ID FROM mcaptcha_users WHERE name = ($2) ));", ($1, (SELECT ID FROM mcaptcha_users WHERE name = ($2) ), $3);",
host, host,
user user,
challenge
) )
.execute(&data.db) .execute(&data.db)
.await; .await;
@@ -51,9 +55,96 @@ pub async fn add_domain(
Err(e) => Err(dup_error(e, ServiceError::HostnameTaken)), Err(e) => Err(dup_error(e, ServiceError::HostnameTaken)),
Ok(_) => Ok(HttpResponse::Ok()), Ok(_) => Ok(HttpResponse::Ok()),
} }
} else { }
Err(ServiceError::NotAUrl)
} #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Challenge {
verification_challenge: String,
}
#[post("/api/v1/mcaptcha/domain/verify/challenge/get")]
pub async fn get_challenge(
payload: web::Json<Domain>,
data: web::Data<Data>,
id: Identity,
client: web::Data<Client>,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let url = Url::parse(&payload.name)?;
let host = url.host_str().ok_or(ServiceError::NotAUrl).unwrap();
let user = id.identity().unwrap();
let res = sqlx::query_as!(
Challenge,
"SELECT verification_challenge
FROM mcaptcha_domains_unverified where
name = $1 AND owner_id = (SELECT ID from mcaptcha_users where name = $2)",
host,
user,
)
.fetch_one(&data.db)
.await
.unwrap();
Ok(HttpResponse::Ok().json(res))
}
#[post("/api/v1/mcaptcha/domain/verify/challenge/prove")]
pub async fn verify(
payload: web::Json<Domain>,
data: web::Data<Data>,
client: web::Data<Client>,
id: Identity,
) -> ServiceResult<impl Responder> {
use futures::{future::TryFutureExt, try_join};
is_authenticated(&id).unwrap();
//let url = Url::parse(&payload.name).unwrap();
//let host = url.host_str().ok_or(ServiceError::NotAUrl).unwrap();
//let user = id.identity().unwrap();
//let challenge_fut = sqlx::query_as!(
// Challenge,
// "SELECT verification_challenge
// FROM mcaptcha_domains_unverified where
// name = $1 AND owner_id = (SELECT ID from mcaptcha_users where name = $2)",
// &host,
// &user,
//)
//.fetch_one(&data.db)
//.map_err(|e| {
// let r: ServiceError = e.into();
// r
//});
//let res_fut = client.get(host).send().map_err(|e| {
// let r: ServiceError = e.into();
// r
//});
//let (challenge, mut server_res) = try_join!(challenge_fut, res_fut).unwrap();
//let server_resp: Challenge = server_res
// .json()
// .await
// .map_err(|_| return ServiceError::ChallengeCourruption)
// .unwrap();
//if server_resp.verification_challenge == challenge.verification_challenge {
// sqlx::query!(
// "INSERT INTO mcaptcha_domains_verified (name, owner_id) VALUES
// ($1, (SELECT ID from mcaptcha_users WHERE name = $2))",
// &host,
// &user
// )
// .execute(&data.db)
// .await
// .unwrap();
// // TODO delete staging unverified
Ok(HttpResponse::Ok())
//} else {
// Err(ServiceError::ChallengeVerificationFailure)
//}
} }
#[post("/api/v1/mcaptcha/domain/delete")] #[post("/api/v1/mcaptcha/domain/delete")]
@@ -64,14 +155,14 @@ pub async fn delete_domain(
) -> ServiceResult<impl Responder> { ) -> ServiceResult<impl Responder> {
is_authenticated(&id)?; is_authenticated(&id)?;
let url = Url::parse(&payload.name)?; let url = Url::parse(&payload.name)?;
if let Some(host) = url.host_str() { let host = url.host_str().ok_or(ServiceError::NotAUrl)?;
sqlx::query!("DELETE FROM mcaptcha_domains WHERE name = ($1)", host,) sqlx::query!(
"DELETE FROM mcaptcha_domains_verified WHERE name = ($1)",
host,
)
.execute(&data.db) .execute(&data.db)
.await?; .await?;
Ok(HttpResponse::Ok()) Ok(HttpResponse::Ok())
} else {
Err(ServiceError::NotAUrl)
}
} }
// Workflow: // Workflow:
@@ -150,4 +241,82 @@ mod tests {
) )
.await; .await;
} }
#[actix_rt::test]
async fn domain_verification_works() {
use crate::api::v1::tests::*;
use awc::Client;
use std::sync::mpsc;
use std::thread;
const NAME: &str = "testdomainveri";
const PASSWORD: &str = "longpassworddomain";
const EMAIL: &str = "domainverification@a.com";
const DOMAIN: &str = "http://localhost:18001";
const IP: &str = "localhost:18001";
const CHALLENGE_GET: &str = "/api/v1/mcaptcha/domain/verify/challenge/get";
const CHALLENGE_VERIFY: &str = "/api/v1/mcaptcha/domain/verify/challenge/prove";
{
let data = Data::new().await;
delete_user(NAME, &data).await;
}
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
rt::System::new("").block_on(server(IP, tx));
});
let srv = rx.recv().unwrap();
let client = Client::new();
let (data, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let mut app = test::init_service(
App::new()
.wrap(get_identity_service())
.configure(v1_services)
.data(data.clone())
.data(client.clone()),
)
.await;
let domain = Domain {
name: DOMAIN.into(),
};
let add_domain_resp = test::call_service(
&mut app,
post_request!(&domain, "/api/v1/mcaptcha/domain/add")
.cookie(cookies.clone())
.to_request(),
)
.await;
assert_eq!(add_domain_resp.status(), StatusCode::OK);
let get_challenge_resp = test::call_service(
&mut app,
post_request!(&domain, CHALLENGE_GET)
.cookie(cookies.clone())
.to_request(),
)
.await;
assert_eq!(get_challenge_resp.status(), StatusCode::OK);
let challenge: Challenge = test::read_body_json(get_challenge_resp).await;
client
.post(format!("{}/domain_verification_works/", DOMAIN))
.send_json(&challenge)
.await
.unwrap();
let verify_challenge_resp = test::call_service(
&mut app,
post_request!(&domain, CHALLENGE_VERIFY)
.cookie(cookies.clone())
.to_request(),
)
.await;
assert_eq!(verify_challenge_resp.status(), StatusCode::OK);
srv.stop(true).await;
}
} }

View File

@@ -20,7 +20,7 @@ use actix_web::{post, web, HttpResponse, Responder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
use super::is_authenticated; use super::{get_random, is_authenticated};
use crate::errors::*; use crate::errors::*;
use crate::Data; use crate::Data;
@@ -46,12 +46,13 @@ pub async fn add_mcaptcha(
let key = get_random(32); let key = get_random(32);
let url = Url::parse(&payload.domain)?; let url = Url::parse(&payload.domain)?;
println!("got req"); println!("got req");
if let Some(host) = url.host_str() {
let host = url.host_str().ok_or(ServiceError::NotAUrl)?;
let res = sqlx::query!( let res = sqlx::query!(
"INSERT INTO mcaptcha_config "INSERT INTO mcaptcha_config
(name, key, domain_name) (name, key, domain_name)
VALUES ($1, $2, ( VALUES ($1, $2, (
SELECT name FROM mcaptcha_domains WHERE name = ($3)))", SELECT name FROM mcaptcha_domains_verified WHERE name = ($3)))",
&payload.name, &payload.name,
&key, &key,
&host, &host,
@@ -70,9 +71,6 @@ pub async fn add_mcaptcha(
Ok(HttpResponse::Ok().json(resp)) Ok(HttpResponse::Ok().json(resp))
} }
} }
} else {
Err(ServiceError::NotAUrl)
}
} }
#[post("/api/v1/mcaptcha/domain/token/delete")] #[post("/api/v1/mcaptcha/domain/token/delete")]
@@ -91,20 +89,6 @@ pub async fn delete_mcaptcha(
Ok(HttpResponse::Ok()) Ok(HttpResponse::Ok())
} }
fn get_random(len: usize) -> String {
use std::iter;
use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng};
let mut rng: ThreadRng = thread_rng();
iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(len)
.collect::<String>()
}
// Workflow: // Workflow:
// 1. Sign up // 1. Sign up
// 2. Sign in // 2. Sign in

View File

@@ -21,3 +21,17 @@ pub mod levels;
pub mod mcaptcha; pub mod mcaptcha;
pub use super::auth::is_authenticated; pub use super::auth::is_authenticated;
pub fn get_random(len: usize) -> String {
use std::iter;
use rand::{distributions::Alphanumeric, rngs::ThreadRng, thread_rng, Rng};
let mut rng: ThreadRng = thread_rng();
iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(len)
.collect::<String>()
}

View File

@@ -32,6 +32,8 @@ pub fn services(cfg: &mut ServiceConfig) {
// domain // domain
cfg.service(mcaptcha::domains::add_domain); cfg.service(mcaptcha::domains::add_domain);
cfg.service(mcaptcha::domains::delete_domain); cfg.service(mcaptcha::domains::delete_domain);
cfg.service(mcaptcha::domains::verify);
cfg.service(mcaptcha::domains::get_challenge);
// mcaptcha // mcaptcha
cfg.service(mcaptcha::mcaptcha::add_mcaptcha); cfg.service(mcaptcha::mcaptcha::add_mcaptcha);

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2021 Aravinth Manivannan <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 log::info;
use std::collections::HashMap;
use std::env;
use std::sync::mpsc;
use std::sync::{Arc, RwLock};
use actix_web::{dev::Server, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
// from
// use crate::api::v1::mcaptcha::domains::Challenge;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Challenge {
verification_challenge: String,
}
#[cfg(not(tarpaulin_include))]
#[actix_web::main]
async fn main() {
pretty_env_logger::init();
let mut confif = env::args();
confif.next();
let port = confif.next().unwrap();
HttpServer::new(move || {
let store: UtilKVServer = Arc::new(RwLock::new(HashMap::new()));
App::new()
.data(store)
.route("/{key}/", web::post().to(util_server_add))
.route("/{key}/", web::get().to(util_server_retrive))
})
.bind(format!("localhost:{}", port))
.unwrap()
.run()
.await
.unwrap();
}
pub async fn server(ip: &str, tx: mpsc::Sender<Server>) {
pretty_env_logger::init();
let srv = HttpServer::new(move || {
let store: UtilKVServer = Arc::new(RwLock::new(HashMap::new()));
App::new()
.data(store)
.route("/{key}/", web::post().to(util_server_add))
.route("/{key}/", web::get().to(util_server_retrive))
})
.bind(ip)
.unwrap()
.run();
tx.send(srv.clone());
}
type UtilKVServer = Arc<RwLock<HashMap<String, Challenge>>>;
#[cfg(not(tarpaulin_include))]
async fn util_server_retrive(
key: web::Path<String>,
data: web::Data<UtilKVServer>,
) -> impl Responder {
let key = key.into_inner();
let store = data.read().unwrap();
let resp = store.get(&key).unwrap();
info!("key :{}, value: {:?}", key, resp);
HttpResponse::Ok().json(resp)
}
#[cfg(not(tarpaulin_include))]
async fn util_server_add(
key: web::Path<String>,
payload: web::Json<Challenge>,
data: web::Data<UtilKVServer>,
) -> impl Responder {
info!("key :{}, value: {:?}", key, payload);
let mut store = data.write().unwrap();
store.insert(key.into_inner(), payload.into_inner());
HttpResponse::Ok()
}

View File

@@ -16,3 +16,6 @@
*/ */
mod auth; mod auth;
mod kvserver;
pub use kvserver::server;

View File

@@ -18,6 +18,7 @@
use std::convert::From; use std::convert::From;
use actix_web::{ use actix_web::{
client::SendRequestError,
dev::HttpResponseBuilder, dev::HttpResponseBuilder,
error::ResponseError, error::ResponseError,
http::{header, StatusCode}, http::{header, StatusCode},
@@ -80,6 +81,13 @@ pub enum ServiceError {
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
CaptchaError(CaptchaError), CaptchaError(CaptchaError),
#[display(fmt = "Couldn't reach your server. If Problem presists, contact support")]
ClientServerUnreachable,
#[display(fmt = "Couldn't parse challenge from your server. Check for courruption")]
ChallengeCourruption,
#[display(fmt = "Verification failure, vaules didn't match")]
ChallengeVerificationFailure,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@@ -117,6 +125,9 @@ impl ResponseError for ServiceError {
ServiceError::UsernameTaken => StatusCode::BAD_REQUEST, ServiceError::UsernameTaken => StatusCode::BAD_REQUEST,
ServiceError::TokenNameTaken => StatusCode::BAD_REQUEST, ServiceError::TokenNameTaken => StatusCode::BAD_REQUEST,
ServiceError::HostnameTaken => StatusCode::BAD_REQUEST, ServiceError::HostnameTaken => StatusCode::BAD_REQUEST,
ServiceError::ClientServerUnreachable => StatusCode::SERVICE_UNAVAILABLE,
ServiceError::ChallengeCourruption => StatusCode::BAD_REQUEST,
ServiceError::ChallengeVerificationFailure => StatusCode::UNAUTHORIZED,
ServiceError::CaptchaError(e) => match e { ServiceError::CaptchaError(e) => match e {
CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR, CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR,
@@ -148,6 +159,19 @@ impl From<ValidationErrors> for ServiceError {
} }
} }
impl From<SendRequestError> for ServiceError {
fn from(e: SendRequestError) -> ServiceError {
debug!("{:?}", &e);
match e {
SendRequestError::Url(_) => ServiceError::NotAUrl,
SendRequestError::Send(_) => ServiceError::InternalServerError,
SendRequestError::Response(_) => ServiceError::InternalServerError,
SendRequestError::Body(_) => ServiceError::InternalServerError,
_ => ServiceError::ClientServerUnreachable,
}
}
}
impl From<ParseError> for ServiceError { impl From<ParseError> for ServiceError {
fn from(_: ParseError) -> ServiceError { fn from(_: ParseError) -> ServiceError {
ServiceError::NotAUrl ServiceError::NotAUrl

View File

@@ -18,7 +18,8 @@ use std::env;
use actix_identity::{CookieIdentityPolicy, IdentityService}; use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{ use actix_web::{
error::InternalError, http::StatusCode, middleware, web::JsonConfig, App, HttpServer, client::Client, error::InternalError, http::StatusCode, middleware, web::JsonConfig, App,
HttpServer,
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::info; use log::info;
@@ -60,14 +61,14 @@ async fn main() -> std::io::Result<()> {
sqlx::migrate!("./migrations/").run(&data.db).await.unwrap(); sqlx::migrate!("./migrations/").run(&data.db).await.unwrap();
HttpServer::new(move || { HttpServer::new(move || {
let client = Client::default();
App::new() App::new()
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.wrap(get_identity_service()) .wrap(get_identity_service())
.wrap(middleware::Compress::default()) .wrap(middleware::Compress::default())
.data(data.clone()) .data(data.clone())
.wrap(middleware::NormalizePath::new( .data(client.clone())
middleware::normalize::TrailingSlash::Trim, .wrap(middleware::NormalizePath::default())
))
.app_data(get_json_err()) .app_data(get_json_err())
.configure(v1_services) .configure(v1_services)
}) })

View File

@@ -97,31 +97,46 @@ pub async fn signin<'a>(name: &'a str, password: &str) -> (data::Data, Login, Se
} }
/// register and signin and domain /// register and signin and domain
/// bypasses domain verification, use with care
pub async fn add_domain_util( pub async fn add_domain_util(
name: &str, name: &str,
password: &str, password: &str,
domain: &str, domain: &str,
) -> (data::Data, Login, ServiceResponse) { ) -> (data::Data, Login, ServiceResponse) {
use crate::api::v1::mcaptcha::domains::Domain; use crate::api::v1::mcaptcha::domains::Domain;
use url::Url;
let (data, creds, signin_resp) = signin(name, password).await; let (data, creds, signin_resp) = signin(name, password).await;
let cookies = get_cookie!(signin_resp); let cookies = get_cookie!(signin_resp);
let mut app = get_app!(data).await; let mut app = get_app!(data).await;
// 1. add domain // 1. add domain
let domain = Domain { let add_domain = Domain {
name: domain.into(), name: domain.into(),
}; };
let add_domain_resp = test::call_service( let add_domain_resp = test::call_service(
&mut app, &mut app,
post_request!(&domain, "/api/v1/mcaptcha/domain/add") post_request!(&add_domain, "/api/v1/mcaptcha/domain/add")
.cookie(cookies.clone()) .cookie(cookies.clone())
.to_request(), .to_request(),
) )
.await; .await;
assert_eq!(add_domain_resp.status(), StatusCode::OK); assert_eq!(add_domain_resp.status(), StatusCode::OK);
// verification work around
let url = Url::parse(domain).unwrap();
let host = url.host_str().unwrap();
sqlx::query!(
"INSERT INTO mcaptcha_domains_verified (name, owner_id) VALUES
($1, (SELECT ID from mcaptcha_users WHERE name = $2))",
&host,
&name
)
.execute(&data.db)
.await
.unwrap();
(data, creds, signin_resp) (data, creds, signin_resp)
} }