rm domains and added authorization check at all endpoints

This commit is contained in:
realaravinth 2021-04-09 16:52:05 +05:30
parent 646a92b28f
commit 4e7e8da574
No known key found for this signature in database
GPG key ID: AD9F0F08E855ED88
12 changed files with 201 additions and 638 deletions

View file

@ -1,4 +0,0 @@
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 (
config_id SERIAL PRIMARY KEY NOT NULL,
domain_name varchar(100) NOT NULL references mcaptcha_domains_verified(name) ON DELETE CASCADE,
user_id INTEGER NOT NULL references mcaptcha_users(ID) ON DELETE CASCADE,
key varchar(100) NOT NULL UNIQUE,
name varchar(100) NOT NULL UNIQUE,
name varchar(100) DEFAULT NULL,
duration integer NOT NULL DEFAULT 30
);

View file

@ -1,6 +0,0 @@
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
);

View file

@ -1,324 +0,0 @@
/*
* 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 actix_identity::Identity;
use actix_web::{client::Client, post, web, HttpResponse, Responder};
//use awc::Client;
use serde::{Deserialize, Serialize};
use url::Url;
use super::{get_random, is_authenticated};
use crate::errors::*;
use crate::Data;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Domain {
pub name: String,
}
#[post("/api/v1/mcaptcha/domain/add")]
pub async fn add_domain(
payload: web::Json<Domain>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let url = Url::parse(&payload.name)?;
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<Domain>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let url = Url::parse(&payload.name)?;
let host = url.host_str().ok_or(ServiceError::NotAUrl)?;
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?;
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 crate::VERIFICATION_PATH;
use futures::{future::TryFutureExt, try_join};
is_authenticated(&id)?;
let url = Url::parse(&payload.name)?;
let host = url.host_str().ok_or(ServiceError::NotAUrl)?;
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(format!("{}{}", url.to_string(), VERIFICATION_PATH))
.send()
.map_err(|e| {
let r: ServiceError = e.into();
r
});
let (challenge, mut server_res) = try_join!(challenge_fut, res_fut)?;
let server_resp: Challenge = server_res
.json()
.await
.map_err(|_| return ServiceError::ChallengeCourruption)?;
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?;
// 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<Domain>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let url = Url::parse(&payload.name)?;
let host = url.host_str().ok_or(ServiceError::NotAUrl)?;
sqlx::query!(
"DELETE FROM mcaptcha_domains_verified WHERE name = ($1)",
host,
)
.execute(&data.db)
.await?;
// TODO check running actors and delete
// if domain(api_key) matches mcaptcha actor id
Ok(HttpResponse::Ok())
}
// Workflow:
// 1. Sign up
// 2. Sign in
// 3. Add domain(DNS TXT record verification? / put string at path)
// 4. Create token
// 5. Add levels
// 6. Update duration
// 7. Start syatem
#[cfg(test)]
mod tests {
use actix_web::http::{header, StatusCode};
use actix_web::test;
use super::*;
use crate::api::v1::services as v1_services;
use crate::tests::*;
use crate::*;
#[actix_rt::test]
async fn add_domains_work() {
const NAME: &str = "testuserdomainn";
const PASSWORD: &str = "longpassworddomain";
const EMAIL: &str = "testuserdomain@a.com";
const DOMAIN: &str = "http://example.com";
const ADD_URL: &str = "/api/v1/mcaptcha/domain/add";
{
let data = Data::new().await;
delete_user(NAME, &data).await;
}
register_and_signin(NAME, EMAIL, PASSWORD).await;
// 1. add domain
let (data, _, signin_resp) = add_domain_util(NAME, PASSWORD, DOMAIN).await;
let cookies = get_cookie!(signin_resp);
let mut app = get_app!(data).await;
let mut domain = Domain {
name: DOMAIN.into(),
};
// 2. duplicate domain
bad_post_req_test(
NAME,
PASSWORD,
ADD_URL,
&domain,
ServiceError::HostnameTaken,
StatusCode::BAD_REQUEST,
)
.await;
// 3. delete domain
let del_domain_resp = test::call_service(
&mut app,
post_request!(&domain, "/api/v1/mcaptcha/domain/delete")
.cookie(cookies.clone())
.to_request(),
)
.await;
assert_eq!(del_domain_resp.status(), StatusCode::OK);
// 4. not a URL test for adding domain
domain.name = "testing".into();
bad_post_req_test(
NAME,
PASSWORD,
ADD_URL,
&domain,
ServiceError::NotAUrl,
StatusCode::BAD_REQUEST,
)
.await;
}
#[actix_rt::test]
async fn domain_verification_works() {
use crate::api::v1::tests::*;
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 || {
actix_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_PATH))
.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,12 +20,13 @@ use actix_web::{post, web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use super::is_authenticated;
use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails;
use crate::errors::*;
use crate::Data;
#[derive(Deserialize, Serialize)]
pub struct UpdateDuration {
pub token_name: String,
pub key: String,
pub duration: i32,
}
@ -36,13 +37,15 @@ pub async fn update_duration(
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let username = id.identity().unwrap();
if payload.duration > 0 {
sqlx::query!(
"UPDATE mcaptcha_config set duration = $1 WHERE
name = $2;",
"UPDATE mcaptcha_config set duration = $1
WHERE key = $2 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)",
&payload.duration,
&payload.token_name,
&payload.key,
&username,
)
.execute(&data.db)
.await?;
@ -68,17 +71,19 @@ pub struct GetDuration {
#[post("/api/v1/mcaptcha/domain/token/duration/get")]
pub async fn get_duration(
payload: web::Json<GetDuration>,
payload: web::Json<MCaptchaDetails>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let username = id.identity().unwrap();
let duration = sqlx::query_as!(
GetDurationResp,
"SELECT duration FROM mcaptcha_config WHERE
name = $1;",
&payload.token,
"SELECT duration FROM mcaptcha_config
WHERE key = $1 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $2)",
&payload.key,
&username,
)
.fetch_one(&data.db)
.await?;
@ -100,8 +105,6 @@ mod tests {
const NAME: &str = "testuserduration";
const PASSWORD: &str = "longpassworddomain";
const EMAIL: &str = "testuserduration@a.com";
const DOMAIN: &str = "http://duration.example.com";
const TOKEN_NAME: &str = "duration_routes_token";
const GET_URL: &str = "/api/v1/mcaptcha/domain/token/duration/get";
const UPDATE_URL: &str = "/api/v1/mcaptcha/domain/token/duration/update";
@ -111,24 +114,20 @@ mod tests {
}
register_and_signin(NAME, EMAIL, PASSWORD).await;
let (data, _, signin_resp) = add_token_util(NAME, PASSWORD, DOMAIN, TOKEN_NAME).await;
let (data, _, signin_resp, token_key) = add_token_util(NAME, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let mut app = get_app!(data).await;
let update = UpdateDuration {
token_name: TOKEN_NAME.into(),
key: token_key.key.clone(),
duration: 40,
};
let get = GetDuration {
token: TOKEN_NAME.into(),
};
// check default
let get_level_resp = test::call_service(
&mut app,
post_request!(&get, GET_URL)
post_request!(&token_key, GET_URL)
.cookie(cookies.clone())
.to_request(),
)
@ -149,7 +148,7 @@ mod tests {
assert_eq!(update_duration.status(), StatusCode::OK);
let get_level_resp = test::call_service(
&mut app,
post_request!(&get, GET_URL)
post_request!(&token_key, GET_URL)
.cookie(cookies.clone())
.to_request(),
)

View file

@ -21,6 +21,7 @@ use m_captcha::{defense::Level, DefenseBuilder};
use serde::{Deserialize, Serialize};
use super::is_authenticated;
use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails;
use crate::errors::*;
use crate::Data;
@ -28,12 +29,12 @@ use crate::Data;
pub struct AddLevels {
pub levels: Vec<Level>,
// name is config_name
pub name: String,
pub key: String,
}
// TODO try for non-existent token names
#[post("/api/v1/mcaptcha/domain/token/levels/add")]
#[post("/api/v1/mcaptcha/levels/add")]
pub async fn add_levels(
payload: web::Json<AddLevels>,
data: web::Data<Data>,
@ -41,6 +42,7 @@ pub async fn add_levels(
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let mut defense = DefenseBuilder::default();
let username = id.identity().unwrap();
for level in payload.levels.iter() {
defense.add_level(level.clone())?;
@ -55,10 +57,16 @@ pub async fn add_levels(
"INSERT INTO mcaptcha_levels (
difficulty_factor,
visitor_threshold,
config_id) VALUES ($1, $2, (SELECT config_id FROM mcaptcha_config WHERE name = ($3) ));",
config_id) VALUES (
$1, $2, (
SELECT config_id FROM mcaptcha_config WHERE
key = ($3) AND user_id = (
SELECT ID FROM mcaptcha_users WHERE name = $4
)));",
difficulty_factor,
visitor_threshold,
&payload.name,
&payload.key,
&username,
)
.execute(&data.db)
.await?;
@ -67,13 +75,14 @@ pub async fn add_levels(
Ok(HttpResponse::Ok())
}
#[post("/api/v1/mcaptcha/domain/token/levels/update")]
#[post("/api/v1/mcaptcha/levels/update")]
pub async fn update_levels(
payload: web::Json<AddLevels>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let username = id.identity().unwrap();
let mut defense = DefenseBuilder::default();
for level in payload.levels.iter() {
@ -88,9 +97,13 @@ pub async fn update_levels(
sqlx::query!(
"DELETE FROM mcaptcha_levels
WHERE config_id = (
SELECT config_id FROM mcaptcha_config where name = ($1)
SELECT config_id FROM mcaptcha_config where key = ($1)
AND user_id = (
SELECT ID from mcaptcha_users WHERE name = $2
)
)",
&payload.name,
&payload.key,
&username
)
.execute(&data.db)
.await?;
@ -102,10 +115,17 @@ pub async fn update_levels(
"INSERT INTO mcaptcha_levels (
difficulty_factor,
visitor_threshold,
config_id) VALUES ($1, $2, (SELECT config_id FROM mcaptcha_config WHERE name = ($3) ));",
config_id) VALUES (
$1, $2, (
SELECT config_id FROM mcaptcha_config WHERE key = ($3) AND
user_id = (
SELECT ID from mcaptcha_users WHERE name = $4
)
));",
difficulty_factor,
visitor_threshold,
&payload.name,
&payload.key,
&username,
)
.execute(&data.db)
.await?;
@ -114,23 +134,26 @@ pub async fn update_levels(
Ok(HttpResponse::Ok())
}
#[post("/api/v1/mcaptcha/domain/token/levels/delete")]
#[post("/api/v1/mcaptcha/levels/delete")]
pub async fn delete_levels(
payload: web::Json<AddLevels>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let username = id.identity().unwrap();
for level in payload.levels.iter() {
let difficulty_factor = level.difficulty_factor as i32;
sqlx::query!(
"DELETE FROM mcaptcha_levels WHERE
config_id = (
SELECT config_id FROM mcaptcha_config WHERE name = ($1)
SELECT config_id FROM mcaptcha_config WHERE key = $1 AND
user_id = (SELECT ID from mcaptcha_users WHERE name = $3)
) AND difficulty_factor = ($2);",
&payload.name,
&payload.key,
difficulty_factor,
&username
)
.execute(&data.db)
.await?;
@ -139,20 +162,16 @@ pub async fn delete_levels(
Ok(HttpResponse::Ok())
}
#[derive(Deserialize, Serialize)]
pub struct GetLevels {
pub token: String,
}
#[post("/api/v1/mcaptcha/domain/token/levels/get")]
#[post("/api/v1/mcaptcha/levels/get")]
pub async fn get_levels(
payload: web::Json<GetLevels>,
payload: web::Json<MCaptchaDetails>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let username = id.identity().unwrap();
let levels = get_levels_util(&payload.token, &data).await?;
let levels = get_levels_util(&payload.key, &username, &data).await?;
Ok(HttpResponse::Ok().json(levels))
}
@ -168,14 +187,16 @@ pub struct I32Levels {
visitor_threshold: i32,
}
async fn get_levels_util(name: &str, data: &Data) -> ServiceResult<Vec<I32Levels>> {
async fn get_levels_util(key: &str, username: &str, data: &Data) -> ServiceResult<Vec<I32Levels>> {
let levels = sqlx::query_as!(
I32Levels,
"SELECT difficulty_factor, visitor_threshold FROM mcaptcha_levels WHERE
config_id = (
SELECT config_id FROM mcaptcha_config WHERE name = ($1)
SELECT config_id FROM mcaptcha_config WHERE key = ($1)
AND user_id = (SELECT ID from mcaptcha_users WHERE name = $2)
);",
name
key,
&username
)
.fetch_all(&data.db)
.await?;
@ -198,12 +219,10 @@ mod tests {
const NAME: &str = "testuserlevelroutes";
const PASSWORD: &str = "longpassworddomain";
const EMAIL: &str = "testuserlevelrouts@a.com";
const DOMAIN: &str = "http://level.example.com";
const TOKEN_NAME: &str = "level_routes_work";
const ADD_URL: &str = "/api/v1/mcaptcha/domain/token/levels/add";
const UPDATE_URL: &str = "/api/v1/mcaptcha/domain/token/levels/update";
const DEL_URL: &str = "/api/v1/mcaptcha/domain/token/levels/delete";
const GET_URL: &str = "/api/v1/mcaptcha/domain/token/levels/get";
const ADD_URL: &str = "/api/v1/mcaptcha/levels/add";
const UPDATE_URL: &str = "/api/v1/mcaptcha/levels/update";
const DEL_URL: &str = "/api/v1/mcaptcha/levels/delete";
const GET_URL: &str = "/api/v1/mcaptcha/levels/get";
let l1 = Level {
difficulty_factor: 50,
@ -214,14 +233,6 @@ mod tests {
visitor_threshold: 500,
};
let levels = vec![l1, l2];
let add_level = AddLevels {
levels: levels.clone(),
name: TOKEN_NAME.into(),
};
let get_level = GetLevels {
token: TOKEN_NAME.into(),
};
{
let data = Data::new().await;
@ -229,10 +240,15 @@ mod tests {
}
register_and_signin(NAME, EMAIL, PASSWORD).await;
let (data, _, signin_resp) = add_token_util(NAME, PASSWORD, DOMAIN, TOKEN_NAME).await;
let (data, _, signin_resp, key) = add_token_util(NAME, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let mut app = get_app!(data).await;
let add_level = AddLevels {
levels: levels.clone(),
key: key.key.clone(),
};
// 1. add level
let add_token_resp = test::call_service(
&mut app,
@ -246,7 +262,7 @@ mod tests {
// 2. get level
let get_level_resp = test::call_service(
&mut app,
post_request!(&get_level, GET_URL)
post_request!(&key, GET_URL)
.cookie(cookies.clone())
.to_request(),
)
@ -268,7 +284,7 @@ mod tests {
let levels = vec![l1, l2];
let add_level = AddLevels {
levels: levels.clone(),
name: TOKEN_NAME.into(),
key: key.key.clone(),
};
let add_token_resp = test::call_service(
&mut app,
@ -280,7 +296,7 @@ mod tests {
assert_eq!(add_token_resp.status(), StatusCode::OK);
let get_level_resp = test::call_service(
&mut app,
post_request!(&get_level, GET_URL)
post_request!(&key, GET_URL)
.cookie(cookies.clone())
.to_request(),
)
@ -301,7 +317,7 @@ mod tests {
let levels = vec![l1, l2];
let add_level = AddLevels {
levels: levels.clone(),
name: TOKEN_NAME.into(),
key: key.key.clone(),
};
let add_token_resp = test::call_service(
&mut app,
@ -313,7 +329,7 @@ mod tests {
assert_eq!(add_token_resp.status(), StatusCode::OK);
let get_level_resp = test::call_service(
&mut app,
post_request!(&get_level, GET_URL)
post_request!(&key, GET_URL)
.cookie(cookies.clone())
.to_request(),
)

View file

@ -18,7 +18,6 @@
use actix_identity::Identity;
use actix_web::{post, web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use url::Url;
use super::{get_random, is_authenticated};
use crate::errors::*;
@ -26,68 +25,58 @@ use crate::Data;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MCaptchaID {
pub name: String,
pub domain: String,
pub name: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MCaptchaDetails {
pub name: String,
pub name: Option<String>,
pub key: String,
}
#[post("/api/v1/mcaptcha/domain/token/add")]
pub async fn add_mcaptcha(
payload: web::Json<MCaptchaID>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
#[post("/api/v1/mcaptcha/add")]
pub async fn add_mcaptcha(data: web::Data<Data>, id: Identity) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let username = id.identity().unwrap();
let key = get_random(32);
let url = Url::parse(&payload.domain)?;
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_verified WHERE name = ($3)))",
&payload.name,
(key, user_id)
VALUES ($1, (SELECT ID FROM mcaptcha_users WHERE name = $2))",
&key,
&host,
&username,
)
.execute(&data.db)
.await;
match res {
Err(e) => Err(dup_error(e, ServiceError::TokenNameTaken)),
Err(e) => {
println!("{}", &e);
Err(dup_error(e, ServiceError::TokenNameTaken))
}
Ok(_) => {
let resp = MCaptchaDetails {
key,
name: payload.into_inner().name,
};
let resp = MCaptchaDetails { key, name: None };
Ok(HttpResponse::Ok().json(resp))
}
}
}
#[post("/api/v1/mcaptcha/domain/token/update")]
#[post("/api/v1/mcaptcha/update/key")]
pub async fn update_token(
payload: web::Json<MCaptchaID>,
payload: web::Json<MCaptchaDetails>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
use std::borrow::Cow;
is_authenticated(&id)?;
let url = Url::parse(&payload.domain)?;
let username = id.identity().unwrap();
let mut key;
let host = url.host_str().ok_or(ServiceError::NotAUrl)?;
loop {
key = get_random(32);
let res = update_token_helper(&key, &payload.name, &host, &data).await;
let res = update_token_helper(&key, &payload.key, &username, &data).await;
if res.is_ok() {
break;
} else {
@ -111,37 +100,36 @@ pub async fn update_token(
async fn update_token_helper(
key: &str,
name: &str,
host: &str,
old_key: &str,
username: &str,
data: &Data,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE mcaptcha_config SET key = $1
WHERE name = $2 AND domain_name = $3",
WHERE key = $2 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)",
&key,
&name,
&host,
&old_key,
&username,
)
.execute(&data.db)
.await?;
Ok(())
}
#[post("/api/v1/mcaptcha/domain/token/get")]
#[post("/api/v1/mcaptcha/get")]
pub async fn get_token(
payload: web::Json<MCaptchaID>,
payload: web::Json<MCaptchaDetails>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let url = Url::parse(&payload.domain)?;
let host = url.host_str().ok_or(ServiceError::NotAUrl)?;
let username = id.identity().unwrap();
let res = match sqlx::query_as!(
MCaptchaDetails,
"SELECT key, name from mcaptcha_config WHERE name = $1 AND domain_name = $2",
&payload.name,
&host,
"SELECT key, name from mcaptcha_config
WHERE key = ($1) AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $2) ",
&payload.key,
&username,
)
.fetch_one(&data.db)
.await
@ -157,16 +145,20 @@ pub async fn get_token(
Ok(HttpResponse::Ok().json(res))
}
#[post("/api/v1/mcaptcha/domain/token/delete")]
#[post("/api/v1/mcaptcha/delete")]
pub async fn delete_mcaptcha(
payload: web::Json<MCaptchaID>,
payload: web::Json<MCaptchaDetails>,
data: web::Data<Data>,
id: Identity,
) -> ServiceResult<impl Responder> {
is_authenticated(&id)?;
let username = id.identity().unwrap();
sqlx::query!(
"DELETE FROM mcaptcha_config WHERE name = ($1)",
&payload.name,
"DELETE FROM mcaptcha_config
WHERE key = ($1) AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $2) ",
&payload.key,
&username,
)
.execute(&data.db)
.await?;
@ -197,10 +189,8 @@ mod tests {
const NAME: &str = "testusermcaptcha";
const PASSWORD: &str = "longpassworddomain";
const EMAIL: &str = "testusermcaptcha@a.com";
const DOMAIN: &str = "http://mcaptcha.example.com";
const TOKEN_NAME: &str = "add_mcaptcha_works_token";
const ADD_URL: &str = "/api/v1/mcaptcha/domain/token/add";
const DEL_URL: &str = "/api/v1/mcaptcha/domain/token/delete";
const ADD_URL: &str = "/api/v1/mcaptcha/add";
const DEL_URL: &str = "/api/v1/mcaptcha/delete";
{
let data = Data::new().await;
@ -209,42 +199,18 @@ mod tests {
// 1. add mcaptcha token
register_and_signin(NAME, EMAIL, PASSWORD).await;
let (data, _, signin_resp) = add_token_util(NAME, PASSWORD, DOMAIN, TOKEN_NAME).await;
let (data, _, signin_resp, token_key) = add_token_util(NAME, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let mut app = get_app!(data).await;
let mut domain = MCaptchaID {
domain: DOMAIN.into(),
name: TOKEN_NAME.into(),
};
// 2. add duplicate mcaptha
bad_post_req_test(
NAME,
PASSWORD,
ADD_URL,
&domain,
ServiceError::TokenNameTaken,
StatusCode::BAD_REQUEST,
)
.await;
// 4. not a URL test for adding domain
domain.domain = "testing".into();
bad_post_req_test(
NAME,
PASSWORD,
ADD_URL,
&domain,
ServiceError::NotAUrl,
StatusCode::BAD_REQUEST,
)
.await;
// let mut domain = MCaptchaID {
// name: TOKEN_NAME.into(),
// };
// 4. delete token
let del_token = test::call_service(
&mut app,
post_request!(&domain, DEL_URL)
post_request!(&token_key, DEL_URL)
.cookie(cookies.clone())
.to_request(),
)
@ -257,10 +223,8 @@ mod tests {
const NAME: &str = "updateusermcaptcha";
const PASSWORD: &str = "longpassworddomain";
const EMAIL: &str = "testupdateusermcaptcha@a.com";
const DOMAIN: &str = "http://update-mcaptcha.example.com";
const TOKEN_NAME: &str = "get_update_mcaptcha_works_token";
const UPDATE_URL: &str = "/api/v1/mcaptcha/domain/token/update";
const GET_URL: &str = "/api/v1/mcaptcha/domain/token/get";
const UPDATE_URL: &str = "/api/v1/mcaptcha/update/key";
const GET_URL: &str = "/api/v1/mcaptcha/get";
{
let data = Data::new().await;
@ -269,18 +233,14 @@ mod tests {
// 1. add mcaptcha token
register_and_signin(NAME, EMAIL, PASSWORD).await;
let (data, _, signin_resp) = add_token_util(NAME, PASSWORD, DOMAIN, TOKEN_NAME).await;
let (data, _, signin_resp, token_key) = add_token_util(NAME, PASSWORD).await;
let cookies = get_cookie!(signin_resp);
let mut app = get_app!(data).await;
let mut domain = MCaptchaID {
domain: DOMAIN.into(),
name: TOKEN_NAME.into(),
};
// 2. update token key
let update_token_resp = test::call_service(
&mut app,
post_request!(&domain, UPDATE_URL)
post_request!(&token_key, UPDATE_URL)
.cookie(cookies.clone())
.to_request(),
)
@ -288,22 +248,25 @@ mod tests {
assert_eq!(update_token_resp.status(), StatusCode::OK);
let updated_token: MCaptchaDetails = test::read_body_json(update_token_resp).await;
// get token key with updated key
let get_token_resp = test::call_service(
&mut app,
post_request!(&domain, GET_URL)
post_request!(&updated_token, GET_URL)
.cookie(cookies.clone())
.to_request(),
)
.await;
assert_eq!(get_token_resp.status(), StatusCode::OK);
let get_token_key: MCaptchaDetails = test::read_body_json(get_token_resp).await;
// check if they match
let mut get_token_key: MCaptchaDetails = test::read_body_json(get_token_resp).await;
assert_eq!(get_token_key.key, updated_token.key);
domain.name = "https://batsense.net".into();
get_token_key.key = "nonexistent".into();
let get_nonexistent_token_resp = test::call_service(
&mut app,
post_request!(&domain, GET_URL)
post_request!(&get_token_key, GET_URL)
.cookie(cookies.clone())
.to_request(),
)

View file

@ -15,7 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
pub mod domains;
pub mod duration;
pub mod levels;
pub mod mcaptcha;

View file

@ -34,13 +34,6 @@ pub fn services(cfg: &mut ServiceConfig) {
cfg.service(auth::username_exists);
cfg.service(auth::email_exists);
// mcaptcha
// 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);
cfg.service(mcaptcha::mcaptcha::delete_mcaptcha);

View file

@ -1,77 +0,0 @@
/*
* 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::sync::mpsc;
use std::sync::{Arc, RwLock};
use actix_web::{dev::Server, middleware, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
/*
* Simple KV Server that stores a json of with schema
* `Challenge` at path /{key}/ on POST and emits on GET
*/
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Challenge {
verification_challenge: String,
}
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()
.wrap(middleware::Logger::default())
.wrap(middleware::NormalizePath::default())
.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()).unwrap();
}
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!("[kv-server] retrive: 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!("[kv-server] cache: key :{}, value: {:?}", key, payload);
let mut store = data.write().unwrap();
store.insert(key.into_inner(), payload.into_inner());
HttpResponse::Ok()
}

View file

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

View file

@ -7,6 +7,7 @@ use serde::Serialize;
use super::*;
use crate::api::v1::auth::{Login, Register};
use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails;
use crate::api::v1::services as v1_services;
use crate::data::Data;
use crate::errors::*;
@ -30,6 +31,10 @@ pub async fn delete_user(name: &str, data: &Data) {
#[macro_export]
macro_rules! post_request {
($uri:expr) => {
test::TestRequest::post().uri($uri)
};
($serializable:expr, $uri:expr) => {
test::TestRequest::post()
.uri($uri)
@ -96,79 +101,81 @@ pub async fn signin<'a>(name: &'a str, password: &str) -> (data::Data, Login, Se
(data, creds, signin_resp)
}
/// register and signin and domain
/// bypasses domain verification, use with care
pub async fn add_domain_util(
///// 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 add_domain = Domain {
// name: domain.into(),
// };
//
// let add_domain_resp = test::call_service(
// &mut app,
// 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)
//}
pub async fn add_token_util(
name: &str,
password: &str,
domain: &str,
) -> (data::Data, Login, ServiceResponse) {
use crate::api::v1::mcaptcha::domains::Domain;
use url::Url;
) -> (data::Data, Login, ServiceResponse, MCaptchaDetails) {
// use crate::api::v1::mcaptcha::mcaptcha::MCaptchaID;
const ADD_URL: &str = "/api/v1/mcaptcha/add";
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 add_domain = Domain {
name: domain.into(),
};
let add_domain_resp = test::call_service(
&mut app,
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)
}
pub async fn add_token_util(
name: &str,
password: &str,
domain: &str,
token_name: &str,
) -> (data::Data, Login, ServiceResponse) {
use crate::api::v1::mcaptcha::mcaptcha::MCaptchaID;
const ADD_URL: &str = "/api/v1/mcaptcha/domain/token/add";
let (data, creds, signin_resp) = add_domain_util(name, password, domain).await;
let cookies = get_cookie!(signin_resp);
let mut app = get_app!(data).await;
// 1. add mcaptcha token
let domain = MCaptchaID {
domain: domain.into(),
name: token_name.into(),
};
// // 1. add mcaptcha token
// let domain = MCaptchaID {
// name: token_name.into(),
// };
let add_token_resp = test::call_service(
&mut app,
post_request!(&domain, ADD_URL)
.cookie(cookies.clone())
.to_request(),
post_request!(ADD_URL).cookie(cookies.clone()).to_request(),
)
.await;
// let status = add_token_resp.status();
// let txt: ErrorToResponse = test::read_body_json(add_token_resp).await;
// println!("{:?}", txt.error);
//
assert_eq!(add_token_resp.status(), StatusCode::OK);
let token_key: MCaptchaDetails = test::read_body_json(add_token_resp).await;
(data, creds, signin_resp)
// assert_eq!(status, StatusCode::OK);
(data, creds, signin_resp, token_key)
}
/// pub duplicate test