mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2024-11-26 11:25:58 +03:00
cascading delete, mcaptcha add/del and test suite
This commit is contained in:
parent
6be10af6fd
commit
096dcd32e4
8 changed files with 287 additions and 85 deletions
|
@ -1,4 +1,4 @@
|
|||
CREATE TABLE IF NOT EXISTS mcaptcha_domains (
|
||||
name VARCHAR(100) PRIMARY KEY NOT NULL UNIQUE,
|
||||
ID INTEGER references mcaptcha_users(ID) NOT NULL
|
||||
ID INTEGER references mcaptcha_users(ID) ON DELETE CASCADE NOT NULL
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
CREATE TABLE IF NOT EXISTS mcaptcha_config (
|
||||
config_id SERIAL PRIMARY KEY NOT NULL,
|
||||
ID INTEGER references mcaptcha_users(ID),
|
||||
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
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS mcaptcha_levels (
|
||||
config_id INTEGER references mcaptcha_config(config_id),
|
||||
config_id INTEGER references mcaptcha_config(config_id) ON DELETE CASCADE,
|
||||
difficulty_factor INTEGER NOT NULL,
|
||||
visitor_threshold INTEGER NOT NULL,
|
||||
level_id SERIAL PRIMARY KEY NOT NULL
|
||||
|
|
|
@ -172,13 +172,15 @@ mod tests {
|
|||
const NAME: &str = "testuser";
|
||||
const PASSWORD: &str = "longpassword";
|
||||
const EMAIL: &str = "testuser1@a.com";
|
||||
const SIGNIN: &str = "/api/v1/signin";
|
||||
const SIGNUP: &str = "/api/v1/signup";
|
||||
|
||||
let mut app = get_app!(data).await;
|
||||
|
||||
delete_user(NAME, &data).await;
|
||||
|
||||
// 1. Register and signin
|
||||
let (data, _, signin_resp) = signin_util(NAME, EMAIL, PASSWORD).await;
|
||||
let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
|
||||
// 2. check if duplicate username is allowed
|
||||
|
@ -187,49 +189,55 @@ mod tests {
|
|||
password: PASSWORD.into(),
|
||||
email: EMAIL.into(),
|
||||
};
|
||||
let duplicate_user_resp =
|
||||
test::call_service(&mut app, post_request!(&msg, "/api/v1/signup").to_request()).await;
|
||||
assert_eq!(duplicate_user_resp.status(), StatusCode::BAD_REQUEST);
|
||||
bad_post_req_test(
|
||||
NAME,
|
||||
PASSWORD,
|
||||
SIGNUP,
|
||||
&msg,
|
||||
ServiceError::UsernameTaken,
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.await;
|
||||
|
||||
// 3. sigining in with non-existent user
|
||||
let nonexistantuser = Login {
|
||||
let mut login = Login {
|
||||
username: "nonexistantuser".into(),
|
||||
password: msg.password.clone(),
|
||||
};
|
||||
let userdoesntexist = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&nonexistantuser, "/api/v1/signin").to_request(),
|
||||
bad_post_req_test(
|
||||
NAME,
|
||||
PASSWORD,
|
||||
SIGNIN,
|
||||
&login,
|
||||
ServiceError::UsernameNotFound,
|
||||
StatusCode::UNAUTHORIZED,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(userdoesntexist.status(), StatusCode::UNAUTHORIZED);
|
||||
let txt: ErrorToResponse = test::read_body_json(userdoesntexist).await;
|
||||
assert_eq!(txt.error, format!("{}", ServiceError::UsernameNotFound));
|
||||
|
||||
// 4. trying to signin with wrong password
|
||||
let wrongpassword = Login {
|
||||
username: NAME.into(),
|
||||
password: NAME.into(),
|
||||
};
|
||||
let wrongpassword_resp = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&wrongpassword, "/api/v1/signin").to_request(),
|
||||
login.username = NAME.into();
|
||||
login.password = NAME.into();
|
||||
|
||||
bad_post_req_test(
|
||||
NAME,
|
||||
PASSWORD,
|
||||
SIGNIN,
|
||||
&login,
|
||||
ServiceError::WrongPassword,
|
||||
StatusCode::UNAUTHORIZED,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(wrongpassword_resp.status(), StatusCode::UNAUTHORIZED);
|
||||
let txt: ErrorToResponse = test::read_body_json(wrongpassword_resp).await;
|
||||
assert_eq!(txt.error, format!("{}", ServiceError::WrongPassword));
|
||||
|
||||
// 5. signout
|
||||
let signout_resp = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&wrongpassword, "/api/v1/signout")
|
||||
.cookie(cookies.clone())
|
||||
test::TestRequest::post()
|
||||
.uri("/api/v1/signout")
|
||||
.cookie(cookies)
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(signout_resp.status(), StatusCode::OK);
|
||||
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -238,7 +246,12 @@ mod tests {
|
|||
const PASSWORD: &str = "longpassword2";
|
||||
const EMAIL: &str = "testuser1@a.com2";
|
||||
|
||||
let (data, creds, signin_resp) = signin_util(NAME, EMAIL, PASSWORD).await;
|
||||
{
|
||||
let data = Data::new().await;
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
|
||||
let (data, creds, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let mut app = get_app!(data).await;
|
||||
|
||||
|
@ -251,6 +264,5 @@ mod tests {
|
|||
.await;
|
||||
|
||||
assert_eq!(delete_user_resp.status(), StatusCode::OK);
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,15 +39,18 @@ pub async fn add_domain(
|
|||
let url = Url::parse(&payload.name)?;
|
||||
if let Some(host) = url.host_str() {
|
||||
let user = id.identity().unwrap();
|
||||
sqlx::query!(
|
||||
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?;
|
||||
Ok(HttpResponse::Ok())
|
||||
.await;
|
||||
match res {
|
||||
Err(e) => Err(dup_error(e, ServiceError::HostnameTaken)),
|
||||
Ok(_) => Ok(HttpResponse::Ok()),
|
||||
}
|
||||
} else {
|
||||
Err(ServiceError::NotAUrl)
|
||||
}
|
||||
|
@ -72,8 +75,9 @@ pub async fn delete_domain(
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct TokenName {
|
||||
pub struct CreateToken {
|
||||
pub name: String,
|
||||
pub domain: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
|
@ -82,34 +86,60 @@ pub struct TokenKeyPair {
|
|||
pub key: String,
|
||||
}
|
||||
|
||||
//#[post("/api/v1/mcaptcha/domain/token/add")]
|
||||
//pub async fn add_mcaptcha(
|
||||
// payload: web::Json<Domain>,
|
||||
// data: web::Data<Data>,
|
||||
// id: Identity,
|
||||
//) -> ServiceResult<impl Responder> {
|
||||
// is_authenticated(&id)?;
|
||||
// let key = get_random(32);
|
||||
// let res = sqlx::query!(
|
||||
// "INSERT INTO mcaptcha_config (name, key) VALUES ($1, $2)",
|
||||
// &payload.name,
|
||||
// &key,
|
||||
// )
|
||||
// .execute(&data.db)
|
||||
// .await;
|
||||
//
|
||||
// match res {
|
||||
// Err(e) => Err(dup_error(e, ServiceError::UsernameTaken)),
|
||||
// Ok(_) => {
|
||||
// let resp = TokenKeyPair {
|
||||
// key,
|
||||
// name: payload.name,
|
||||
// };
|
||||
//
|
||||
// Ok(HttpResponse::Ok().json(resp))
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
#[post("/api/v1/mcaptcha/domain/token/add")]
|
||||
pub async fn add_mcaptcha(
|
||||
payload: web::Json<CreateToken>,
|
||||
data: web::Data<Data>,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
is_authenticated(&id)?;
|
||||
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
|
||||
(name, key, domain_name)
|
||||
VALUES ($1, $2, (
|
||||
SELECT name FROM mcaptcha_domains WHERE name = ($3)))",
|
||||
&payload.name,
|
||||
&key,
|
||||
&host,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Err(e) => Err(dup_error(e, ServiceError::TokenNameTaken)),
|
||||
Ok(_) => {
|
||||
let resp = TokenKeyPair {
|
||||
key,
|
||||
name: payload.into_inner().name,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(resp))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ServiceError::NotAUrl)
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/v1/mcaptcha/domain/token/delete")]
|
||||
pub async fn delete_mcaptcha(
|
||||
payload: web::Json<CreateToken>,
|
||||
data: web::Data<Data>,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
is_authenticated(&id)?;
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_config WHERE name = ($1)",
|
||||
&payload.name,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
fn get_random(len: usize) -> String {
|
||||
use std::iter;
|
||||
|
@ -141,28 +171,36 @@ mod tests {
|
|||
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, _, signin_resp) = signin_util(NAME, EMAIL, PASSWORD).await;
|
||||
{
|
||||
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;
|
||||
|
||||
delete_domain_util(DOMAIN, &data).await;
|
||||
|
||||
// 1. add domain
|
||||
let domain = Domain {
|
||||
let mut 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(),
|
||||
// 2. duplicate domain
|
||||
bad_post_req_test(
|
||||
NAME,
|
||||
PASSWORD,
|
||||
ADD_URL,
|
||||
&domain,
|
||||
ServiceError::HostnameTaken,
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(add_domain_resp.status(), StatusCode::OK);
|
||||
|
||||
// 2. delete domain
|
||||
// 3. delete domain
|
||||
let del_domain_resp = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&domain, "/api/v1/mcaptcha/domain/delete")
|
||||
|
@ -171,6 +209,85 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
assert_eq!(del_domain_resp.status(), StatusCode::OK);
|
||||
delete_user(NAME, &data).await;
|
||||
|
||||
// 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 add_mcaptcha_works() {
|
||||
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";
|
||||
|
||||
{
|
||||
let data = Data::new().await;
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
|
||||
register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let (data, _, 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 mut domain = CreateToken {
|
||||
domain: DOMAIN.into(),
|
||||
name: TOKEN_NAME.into(),
|
||||
};
|
||||
let add_token_resp = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&domain, ADD_URL)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(add_token_resp.status(), StatusCode::OK);
|
||||
|
||||
// 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;
|
||||
|
||||
// 4. delete token
|
||||
let del_token = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&domain, DEL_URL)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(del_token.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,4 +31,6 @@ pub fn services(cfg: &mut ServiceConfig) {
|
|||
|
||||
cfg.service(add_domain);
|
||||
cfg.service(delete_domain);
|
||||
cfg.service(add_mcaptcha);
|
||||
cfg.service(delete_mcaptcha);
|
||||
}
|
||||
|
|
|
@ -76,6 +76,9 @@ pub enum ServiceError {
|
|||
/// when the a token name is already taken
|
||||
#[display(fmt = "token name not available")]
|
||||
TokenNameTaken,
|
||||
/// when the a host name is already taken
|
||||
#[display(fmt = "host name not available")]
|
||||
HostnameTaken,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -97,6 +100,7 @@ impl ResponseError for ServiceError {
|
|||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
fn status_code(&self) -> StatusCode {
|
||||
println!("{:?}", &self);
|
||||
match *self {
|
||||
ServiceError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ServiceError::NotAnEmail => StatusCode::BAD_REQUEST,
|
||||
|
@ -111,6 +115,7 @@ impl ResponseError for ServiceError {
|
|||
ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST,
|
||||
ServiceError::UsernameTaken => StatusCode::BAD_REQUEST,
|
||||
ServiceError::TokenNameTaken => StatusCode::BAD_REQUEST,
|
||||
ServiceError::HostnameTaken => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +167,7 @@ impl From<sqlx::Error> for ServiceError {
|
|||
pub fn dup_error(e: sqlx::Error, dup_error: ServiceError) -> ServiceError {
|
||||
use sqlx::error::Error;
|
||||
use std::borrow::Cow;
|
||||
println!("database error: {:?}", &e);
|
||||
if let Error::Database(err) = e {
|
||||
if err.code() == Some(Cow::from("23505")) {
|
||||
dup_error
|
||||
|
|
|
@ -3,11 +3,13 @@ use actix_web::{
|
|||
dev::ServiceResponse,
|
||||
http::{header, StatusCode},
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::auth::{Login, Register};
|
||||
use crate::api::v1::services as v1_services;
|
||||
use crate::data::Data;
|
||||
use crate::errors::*;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_cookie {
|
||||
|
@ -17,15 +19,13 @@ macro_rules! get_cookie {
|
|||
}
|
||||
|
||||
pub async fn delete_user(name: &str, data: &Data) {
|
||||
let _ = sqlx::query!("DELETE FROM mcaptcha_users WHERE name = ($1)", name,)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn delete_domain_util(name: &str, data: &Data) {
|
||||
let _ = sqlx::query!("DELETE FROM mcaptcha_domains WHERE name = ($1)", name,)
|
||||
let r = sqlx::query!("DELETE FROM mcaptcha_users WHERE name = ($1)", name,)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
println!();
|
||||
println!();
|
||||
println!();
|
||||
println!("Deleting user: {:?}", &r);
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
@ -51,16 +51,20 @@ macro_rules! get_app {
|
|||
}
|
||||
|
||||
/// register and signin utility
|
||||
pub async fn signin_util<'a>(
|
||||
pub async fn register_and_signin<'a>(
|
||||
name: &'a str,
|
||||
email: &str,
|
||||
password: &str,
|
||||
) -> (data::Data, Login, ServiceResponse) {
|
||||
register(name, email, password).await;
|
||||
signin(name, password).await
|
||||
}
|
||||
|
||||
/// register and signin utility
|
||||
pub async fn register<'a>(name: &'a str, email: &str, password: &str) {
|
||||
let data = Data::new().await;
|
||||
let mut app = get_app!(data).await;
|
||||
|
||||
delete_user(&name, &data).await;
|
||||
|
||||
// 1. Register
|
||||
let msg = Register {
|
||||
username: name.into(),
|
||||
|
@ -70,6 +74,12 @@ pub async fn signin_util<'a>(
|
|||
let resp =
|
||||
test::call_service(&mut app, post_request!(&msg, "/api/v1/signup").to_request()).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
/// signin util
|
||||
pub async fn signin<'a>(name: &'a str, password: &str) -> (data::Data, Login, ServiceResponse) {
|
||||
let data = Data::new().await;
|
||||
let mut app = get_app!(data).await;
|
||||
|
||||
// 2. signin
|
||||
let creds = Login {
|
||||
|
@ -85,3 +95,57 @@ pub async fn signin_util<'a>(
|
|||
|
||||
(data, creds, signin_resp)
|
||||
}
|
||||
|
||||
/// register and signin and domain
|
||||
pub async fn add_domain_util(
|
||||
name: &str,
|
||||
password: &str,
|
||||
domain: &str,
|
||||
) -> (data::Data, Login, ServiceResponse) {
|
||||
use crate::api::v1::mcaptcha::Domain;
|
||||
|
||||
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 {
|
||||
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);
|
||||
|
||||
(data, creds, signin_resp)
|
||||
}
|
||||
|
||||
/// pub duplicate test
|
||||
pub async fn bad_post_req_test<T: Serialize>(
|
||||
name: &str,
|
||||
password: &str,
|
||||
url: &str,
|
||||
payload: &T,
|
||||
dup_err: ServiceError,
|
||||
s: StatusCode,
|
||||
) {
|
||||
let (data, _, signin_resp) = signin(name, password).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let mut app = get_app!(data).await;
|
||||
|
||||
let dup_token_resp = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&payload, url)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(dup_token_resp.status(), s);
|
||||
let txt: ErrorToResponse = test::read_body_json(dup_token_resp).await;
|
||||
assert_eq!(txt.error, format!("{}", dup_err));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue