From 51764817f9d8ad354355336e7a8b420b4d5f2aff Mon Sep 17 00:00:00 2001 From: realaravinth Date: Fri, 26 Mar 2021 22:18:01 +0530 Subject: [PATCH] domain verification --- Cargo.lock | 216 ++++++++++++++--- Cargo.toml | 10 +- .../20210310122339_mcaptcha_domains.sql | 4 - ...210310122339_mcaptcha_domains_verified.sql | 4 + migrations/20210310122617_mcaptcha_config.sql | 8 +- ...0324130238_mcaptcha_domains_unverified.sql | 6 + network.sh | 36 +++ src/api/v1/auth.rs | 7 +- src/api/v1/mcaptcha/domains.rs | 219 ++++++++++++++++-- src/api/v1/mcaptcha/mcaptcha.rs | 56 ++--- src/api/v1/mcaptcha/mod.rs | 14 ++ src/api/v1/mod.rs | 2 + src/api/v1/tests/kvserver.rs | 94 ++++++++ src/api/v1/tests/mod.rs | 3 + src/errors.rs | 24 ++ src/main.rs | 9 +- src/tests/mod.rs | 19 +- 17 files changed, 623 insertions(+), 108 deletions(-) delete mode 100644 migrations/20210310122339_mcaptcha_domains.sql create mode 100644 migrations/20210310122339_mcaptcha_domains_verified.sql create mode 100644 migrations/20210324130238_mcaptcha_domains_unverified.sql create mode 100755 network.sh create mode 100644 src/api/v1/tests/kvserver.rs diff --git a/Cargo.lock b/Cargo.lock index 8cc84766..8d02e14e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,16 +40,32 @@ dependencies = [ "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]] name = "actix-connect" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "177837a10863f15ba8d3ae3ec12fac1099099529ed20083a27fdfe247381d0dc" dependencies = [ - "actix-codec", + "actix-codec 0.3.0", "actix-rt 1.1.1", - "actix-service", - "actix-utils", + "actix-service 1.0.6", + "actix-utils 2.0.0", "derive_more", "either", "futures-util", @@ -65,12 +81,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "452299e87817ae5673910e53c243484ca38be3828db819b6011736fc6982e874" dependencies = [ - "actix-codec", + "actix-codec 0.3.0", "actix-connect", "actix-rt 1.1.1", - "actix-service", + "actix-service 1.0.6", "actix-threadpool", - "actix-utils", + "actix-utils 2.0.0", "base64", "bitflags", "brotli2", @@ -85,7 +101,7 @@ dependencies = [ "futures-core", "futures-util", "fxhash", - "h2", + "h2 0.2.7", "http", "httparse", "indexmap", @@ -106,13 +122,58 @@ dependencies = [ "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]] name = "actix-identity" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3263fe74cf505c6f9e18209c89fbdba5569cfd3905a7e907b42aa1c85c18fae5" dependencies = [ - "actix-service", + "actix-service 1.0.6", "actix-web", "futures-util", "serde 1.0.125", @@ -185,10 +246,10 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45407e6e672ca24784baa667c5d32ef109ccdd8d5e0b5ebb9ef8a67f4dfb708e" dependencies = [ - "actix-codec", + "actix-codec 0.3.0", "actix-rt 1.1.1", - "actix-service", - "actix-utils", + "actix-service 1.0.6", + "actix-utils 2.0.0", "futures-channel", "futures-util", "log", @@ -209,6 +270,16 @@ dependencies = [ "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]] name = "actix-testing" version = "1.0.1" @@ -218,7 +289,7 @@ dependencies = [ "actix-macros 0.1.3", "actix-rt 1.1.1", "actix-server", - "actix-service", + "actix-service 1.0.6", "log", "socket2", ] @@ -244,21 +315,38 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24789b7d7361cf5503a504ebe1c10806896f61e96eca9a7350e23001aca715fb" dependencies = [ - "actix-codec", - "actix-service", - "actix-utils", + "actix-codec 0.3.0", + "actix-service 1.0.6", + "actix-utils 2.0.0", "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]] name = "actix-utils" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9022dec56632d1d7979e59af14f0597a28a830a9c1c7fec8b2327eb9f16b5a" dependencies = [ - "actix-codec", + "actix-codec 0.3.0", "actix-rt 1.1.1", - "actix-service", + "actix-service 1.0.6", "bitflags", "bytes 0.5.6", "either", @@ -270,25 +358,40 @@ dependencies = [ "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]] name = "actix-web" version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e641d4a172e7faa0862241a20ff4f1f5ab0ab7c279f00c2d4587b77483477b86" dependencies = [ - "actix-codec", - "actix-http", + "actix-codec 0.3.0", + "actix-http 2.2.0", "actix-macros 0.1.3", "actix-router", "actix-rt 1.1.1", "actix-server", - "actix-service", + "actix-service 1.0.6", "actix-testing", "actix-threadpool", - "actix-tls", - "actix-utils", + "actix-tls 2.0.0", + "actix-utils 2.0.0", "actix-web-codegen", - "awc", + "awc 2.0.3", "bytes 0.5.6", "derive_more", "encoding_rs", @@ -408,6 +511,17 @@ dependencies = [ "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]] name = "aho-corasick" version = "0.7.15" @@ -503,10 +617,10 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b381e490e7b0cfc37ebc54079b0413d8093ef43d14a4e4747083f7fa47a9e691" dependencies = [ - "actix-codec", - "actix-http", + "actix-codec 0.3.0", + "actix-http 2.2.0", "actix-rt 1.1.1", - "actix-service", + "actix-service 1.0.6", "base64", "bytes 0.5.6", "cfg-if 1.0.0", @@ -521,6 +635,32 @@ dependencies = [ "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]] name = "base-x" version = "0.2.8" @@ -1145,14 +1285,17 @@ name = "guard" version = "0.1.0" dependencies = [ "actix", - "actix-http", + "actix-http 2.2.0", "actix-identity", + "actix-rt 1.1.1", "actix-rt 2.1.0", "actix-web", "argon2-creds", + "awc 3.0.0-beta.3", "config", "derive_builder", "derive_more", + "futures", "lazy_static", "log", "m_captcha", @@ -1185,6 +1328,25 @@ dependencies = [ "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]] name = "hashbrown" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index d6f3334a..60de39f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,9 @@ path = "./src/main.rs" name = "tests-migrate" path = "./src/tests-migrate.rs" - +[[bin]] +name = "kv-test-util" +path = "./src/api/v1/tests/kvserver.rs" [dependencies] actix-web = "3" @@ -29,6 +31,9 @@ actix = "0.11" actix-identity = "0.3" actix-http = "2.2" actix-rt = "2" +awc = "3.0.0-beta.3" + +futures = "0.3" sqlx = { version = "0.5.0", features = [ "runtime-actix-rustls", "postgres" ] } 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" } rand = "0.8" + +[dev-dependencies] +rt = { package = "actix-rt", version = "1"} diff --git a/migrations/20210310122339_mcaptcha_domains.sql b/migrations/20210310122339_mcaptcha_domains.sql deleted file mode 100644 index 615c6e50..00000000 --- a/migrations/20210310122339_mcaptcha_domains.sql +++ /dev/null @@ -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 -); diff --git a/migrations/20210310122339_mcaptcha_domains_verified.sql b/migrations/20210310122339_mcaptcha_domains_verified.sql new file mode 100644 index 00000000..12e9506e --- /dev/null +++ b/migrations/20210310122339_mcaptcha_domains_verified.sql @@ -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 +); diff --git a/migrations/20210310122617_mcaptcha_config.sql b/migrations/20210310122617_mcaptcha_config.sql index 3f170e6d..6dd8521e 100644 --- a/migrations/20210310122617_mcaptcha_config.sql +++ b/migrations/20210310122617_mcaptcha_config.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS mcaptcha_config ( config_id SERIAL PRIMARY KEY NOT NULL, - domain_name VARCHAR(100) NOT NULL references mcaptcha_domains(name) ON DELETE CASCADE, - key VARCHAR(100) NOT NULL UNIQUE, - name VARCHAR(100) NOT NULL UNIQUE, - duration INTEGER NOT NULL DEFAULT 30 + domain_name varchar(100) NOT NULL references mcaptcha_domains_verified(name) ON DELETE CASCADE, + key varchar(100) NOT NULL UNIQUE, + name varchar(100) NOT NULL UNIQUE, + duration integer NOT NULL DEFAULT 30 ); diff --git a/migrations/20210324130238_mcaptcha_domains_unverified.sql b/migrations/20210324130238_mcaptcha_domains_unverified.sql new file mode 100644 index 00000000..d6037cb3 --- /dev/null +++ b/migrations/20210324130238_mcaptcha_domains_unverified.sql @@ -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 +); diff --git a/network.sh b/network.sh new file mode 100755 index 00000000..f6153a99 --- /dev/null +++ b/network.sh @@ -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 diff --git a/src/api/v1/auth.rs b/src/api/v1/auth.rs index 12d59b9e..e2986d26 100644 --- a/src/api/v1/auth.rs +++ b/src/api/v1/auth.rs @@ -107,11 +107,8 @@ pub async fn signout(id: Identity) -> impl Responder { // TODO use middleware pub fn is_authenticated(id: &Identity) -> ServiceResult<()> { // access request identity - if let Some(_) = id.identity() { - Ok(()) - } else { - Err(ServiceError::AuthorizationRequired) - } + id.identity().ok_or(ServiceError::AuthorizationRequired)?; + Ok(()) } #[post("/api/v1/account/delete")] diff --git a/src/api/v1/mcaptcha/domains.rs b/src/api/v1/mcaptcha/domains.rs index 01fa4d5f..2048bcd2 100644 --- a/src/api/v1/mcaptcha/domains.rs +++ b/src/api/v1/mcaptcha/domains.rs @@ -17,10 +17,11 @@ use actix_identity::Identity; use actix_web::{post, web, HttpResponse, Responder}; +use awc::Client; use serde::{Deserialize, Serialize}; use url::Url; -use super::is_authenticated; +use super::{get_random, is_authenticated}; use crate::errors::*; use crate::Data; @@ -37,25 +38,115 @@ pub async fn add_domain( ) -> ServiceResult { is_authenticated(&id)?; let url = Url::parse(&payload.name)?; - if let Some(host) = url.host_str() { - let user = id.identity().unwrap(); - let res = sqlx::query!( - "INSERT INTO mcaptcha_domains (name, ID) VALUES - ($1, (SELECT ID FROM mcaptcha_users WHERE name = ($2) ));", - host, - user - ) - .execute(&data.db) - .await; - match res { - Err(e) => Err(dup_error(e, ServiceError::HostnameTaken)), - Ok(_) => Ok(HttpResponse::Ok()), - } - } else { - Err(ServiceError::NotAUrl) + + let host = url.host_str().ok_or(ServiceError::NotAUrl)?; + let user = id.identity().unwrap(); + let challenge = get_random(32); + let res = sqlx::query!( + "INSERT INTO mcaptcha_domains_unverified (name, owner_id, verification_challenge) VALUES + ($1, (SELECT ID FROM mcaptcha_users WHERE name = ($2) ), $3);", + host, + user, + challenge + ) + .execute(&data.db) + .await; + match res { + Err(e) => Err(dup_error(e, ServiceError::HostnameTaken)), + Ok(_) => Ok(HttpResponse::Ok()), } } +#[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, + data: web::Data, + id: Identity, + client: web::Data, +) -> ServiceResult { + 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, + data: web::Data, + client: web::Data, + id: Identity, +) -> ServiceResult { + 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")] pub async fn delete_domain( payload: web::Json, @@ -64,14 +155,14 @@ pub async fn delete_domain( ) -> ServiceResult { is_authenticated(&id)?; let url = Url::parse(&payload.name)?; - if let Some(host) = url.host_str() { - sqlx::query!("DELETE FROM mcaptcha_domains WHERE name = ($1)", host,) - .execute(&data.db) - .await?; - Ok(HttpResponse::Ok()) - } else { - Err(ServiceError::NotAUrl) - } + let host = url.host_str().ok_or(ServiceError::NotAUrl)?; + sqlx::query!( + "DELETE FROM mcaptcha_domains_verified WHERE name = ($1)", + host, + ) + .execute(&data.db) + .await?; + Ok(HttpResponse::Ok()) } // Workflow: @@ -150,4 +241,82 @@ mod tests { ) .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; + } } diff --git a/src/api/v1/mcaptcha/mcaptcha.rs b/src/api/v1/mcaptcha/mcaptcha.rs index d844e91e..503e29cc 100644 --- a/src/api/v1/mcaptcha/mcaptcha.rs +++ b/src/api/v1/mcaptcha/mcaptcha.rs @@ -20,7 +20,7 @@ use actix_web::{post, web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; use url::Url; -use super::is_authenticated; +use super::{get_random, is_authenticated}; use crate::errors::*; use crate::Data; @@ -46,32 +46,30 @@ pub async fn add_mcaptcha( let key = get_random(32); let url = Url::parse(&payload.domain)?; println!("got req"); - if let Some(host) = url.host_str() { - let res = sqlx::query!( - "INSERT INTO mcaptcha_config + + let host = url.host_str().ok_or(ServiceError::NotAUrl)?; + let res = sqlx::query!( + "INSERT INTO mcaptcha_config (name, key, domain_name) VALUES ($1, $2, ( - SELECT name FROM mcaptcha_domains WHERE name = ($3)))", - &payload.name, - &key, - &host, - ) - .execute(&data.db) - .await; + SELECT name FROM mcaptcha_domains_verified WHERE name = ($3)))", + &payload.name, + &key, + &host, + ) + .execute(&data.db) + .await; - match res { - Err(e) => Err(dup_error(e, ServiceError::TokenNameTaken)), - Ok(_) => { - let resp = MCaptchaDetails { - key, - name: payload.into_inner().name, - }; + match res { + Err(e) => Err(dup_error(e, ServiceError::TokenNameTaken)), + Ok(_) => { + let resp = MCaptchaDetails { + key, + name: payload.into_inner().name, + }; - Ok(HttpResponse::Ok().json(resp)) - } + Ok(HttpResponse::Ok().json(resp)) } - } else { - Err(ServiceError::NotAUrl) } } @@ -91,20 +89,6 @@ pub async fn delete_mcaptcha( 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::() -} - // Workflow: // 1. Sign up // 2. Sign in diff --git a/src/api/v1/mcaptcha/mod.rs b/src/api/v1/mcaptcha/mod.rs index a4d34314..355b982d 100644 --- a/src/api/v1/mcaptcha/mod.rs +++ b/src/api/v1/mcaptcha/mod.rs @@ -21,3 +21,17 @@ pub mod levels; pub mod mcaptcha; 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::() +} diff --git a/src/api/v1/mod.rs b/src/api/v1/mod.rs index 9e619636..6900e127 100644 --- a/src/api/v1/mod.rs +++ b/src/api/v1/mod.rs @@ -32,6 +32,8 @@ pub fn services(cfg: &mut ServiceConfig) { // domain cfg.service(mcaptcha::domains::add_domain); cfg.service(mcaptcha::domains::delete_domain); + cfg.service(mcaptcha::domains::verify); + cfg.service(mcaptcha::domains::get_challenge); // mcaptcha cfg.service(mcaptcha::mcaptcha::add_mcaptcha); diff --git a/src/api/v1/tests/kvserver.rs b/src/api/v1/tests/kvserver.rs new file mode 100644 index 00000000..cebb8e59 --- /dev/null +++ b/src/api/v1/tests/kvserver.rs @@ -0,0 +1,94 @@ +/* +* Copyright (C) 2021 Aravinth Manivannan +* +* 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 . +*/ +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) { + 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>>; + +#[cfg(not(tarpaulin_include))] +async fn util_server_retrive( + key: web::Path, + data: web::Data, +) -> 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, + payload: web::Json, + data: web::Data, +) -> impl Responder { + info!("key :{}, value: {:?}", key, payload); + let mut store = data.write().unwrap(); + store.insert(key.into_inner(), payload.into_inner()); + HttpResponse::Ok() +} diff --git a/src/api/v1/tests/mod.rs b/src/api/v1/tests/mod.rs index edcf8801..e24152c2 100644 --- a/src/api/v1/tests/mod.rs +++ b/src/api/v1/tests/mod.rs @@ -16,3 +16,6 @@ */ mod auth; +mod kvserver; + +pub use kvserver::server; diff --git a/src/errors.rs b/src/errors.rs index b669116c..fd9a99d3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -18,6 +18,7 @@ use std::convert::From; use actix_web::{ + client::SendRequestError, dev::HttpResponseBuilder, error::ResponseError, http::{header, StatusCode}, @@ -80,6 +81,13 @@ pub enum ServiceError { #[display(fmt = "{}", _0)] 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)] @@ -117,6 +125,9 @@ impl ResponseError for ServiceError { ServiceError::UsernameTaken => StatusCode::BAD_REQUEST, ServiceError::TokenNameTaken => 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 { CaptchaError::MailboxError => StatusCode::INTERNAL_SERVER_ERROR, @@ -148,6 +159,19 @@ impl From for ServiceError { } } +impl From 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 for ServiceError { fn from(_: ParseError) -> ServiceError { ServiceError::NotAUrl diff --git a/src/main.rs b/src/main.rs index abfa8bd3..5153872f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,8 @@ use std::env; use actix_identity::{CookieIdentityPolicy, IdentityService}; 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 log::info; @@ -60,14 +61,14 @@ async fn main() -> std::io::Result<()> { sqlx::migrate!("./migrations/").run(&data.db).await.unwrap(); HttpServer::new(move || { + let client = Client::default(); App::new() .wrap(middleware::Logger::default()) .wrap(get_identity_service()) .wrap(middleware::Compress::default()) .data(data.clone()) - .wrap(middleware::NormalizePath::new( - middleware::normalize::TrailingSlash::Trim, - )) + .data(client.clone()) + .wrap(middleware::NormalizePath::default()) .app_data(get_json_err()) .configure(v1_services) }) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 35f91345..df7fafef 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -97,31 +97,46 @@ pub async fn signin<'a>(name: &'a str, password: &str) -> (data::Data, Login, Se } /// register and signin and domain +/// bypasses domain verification, use with care pub async fn add_domain_util( name: &str, password: &str, domain: &str, ) -> (data::Data, Login, ServiceResponse) { use crate::api::v1::mcaptcha::domains::Domain; + use url::Url; let (data, creds, signin_resp) = signin(name, password).await; let cookies = get_cookie!(signin_resp); let mut app = get_app!(data).await; // 1. add domain - let domain = Domain { + let add_domain = Domain { name: domain.into(), }; let add_domain_resp = test::call_service( &mut app, - post_request!(&domain, "/api/v1/mcaptcha/domain/add") + post_request!(&add_domain, "/api/v1/mcaptcha/domain/add") .cookie(cookies.clone()) .to_request(), ) .await; 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) }