refactor captcha.rs and levels.rs and rm duration routes
This commit is contained in:
parent
cf4a0f9b73
commit
73ce2d1cb1
22 changed files with 860 additions and 886 deletions
|
@ -26,7 +26,7 @@ use crate::AppData;
|
|||
path = "crate::V1_API_ROUTES.account.delete",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn delete_account(
|
||||
pub async fn delete_account(
|
||||
id: Identity,
|
||||
payload: web::Json<Password>,
|
||||
data: AppData,
|
||||
|
|
153
src/api/v1/mcaptcha/create.rs
Normal file
153
src/api/v1/mcaptcha/create.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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 std::borrow::Cow;
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use libmcaptcha::defense::Level;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::get_random;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreateCaptcha {
|
||||
pub levels: Vec<Level>,
|
||||
pub duration: u32,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct MCaptchaDetails {
|
||||
pub name: String,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
// TODO redo mcaptcha table to include levels as json field
|
||||
// so that the whole thing can be added/udpaed in a single stroke
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.captcha.create",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
pub async fn create(
|
||||
payload: web::Json<CreateCaptcha>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let mcaptcha_config = runner::create(&payload, &data, &username).await?;
|
||||
Ok(HttpResponse::Ok().json(mcaptcha_config))
|
||||
}
|
||||
|
||||
pub mod runner {
|
||||
use futures::future::try_join_all;
|
||||
use libmcaptcha::DefenseBuilder;
|
||||
use log::debug;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub async fn create(
|
||||
payload: &CreateCaptcha,
|
||||
data: &AppData,
|
||||
username: &str,
|
||||
) -> ServiceResult<MCaptchaDetails> {
|
||||
let mut defense = DefenseBuilder::default();
|
||||
for level in payload.levels.iter() {
|
||||
defense.add_level(*level)?;
|
||||
}
|
||||
|
||||
defense.build()?;
|
||||
|
||||
debug!("creating config");
|
||||
let mcaptcha_config =
|
||||
// add_mcaptcha_util(payload.duration, &payload.description, &data, username).await?;
|
||||
|
||||
{
|
||||
let mut key;
|
||||
|
||||
let resp;
|
||||
|
||||
loop {
|
||||
key = get_random(32);
|
||||
|
||||
let res = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_config
|
||||
(key, user_id, duration, name)
|
||||
VALUES ($1, (SELECT ID FROM mcaptcha_users WHERE name = $2), $3, $4)",
|
||||
&key,
|
||||
&username,
|
||||
payload.duration as i32,
|
||||
&payload.description,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Err(sqlx::Error::Database(err)) => {
|
||||
if err.code() == Some(Cow::from("23505"))
|
||||
&& err.message().contains("mcaptcha_config_key_key")
|
||||
{
|
||||
continue;
|
||||
} else {
|
||||
return Err(sqlx::Error::Database(err).into());
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
|
||||
Ok(_) => {
|
||||
resp = MCaptchaDetails {
|
||||
key,
|
||||
name: payload.description.to_owned(),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
resp
|
||||
};
|
||||
|
||||
debug!("config created");
|
||||
|
||||
let mut futs = Vec::with_capacity(payload.levels.len());
|
||||
|
||||
for level in payload.levels.iter() {
|
||||
let difficulty_factor = level.difficulty_factor as i32;
|
||||
let visitor_threshold = level.visitor_threshold as i32;
|
||||
let fut = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_levels (
|
||||
difficulty_factor,
|
||||
visitor_threshold,
|
||||
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,
|
||||
&mcaptcha_config.key,
|
||||
&username,
|
||||
)
|
||||
.execute(&data.db);
|
||||
futs.push(fut);
|
||||
}
|
||||
|
||||
try_join_all(futs).await?;
|
||||
Ok(mcaptcha_config)
|
||||
}
|
||||
}
|
95
src/api/v1/mcaptcha/delete.rs
Normal file
95
src/api/v1/mcaptcha/delete.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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::{web, HttpResponse, Responder};
|
||||
use libmcaptcha::master::messages::RemoveCaptcha;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct DeleteCaptcha {
|
||||
pub key: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.captcha.delete",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn delete(
|
||||
payload: web::Json<DeleteCaptcha>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
use argon2_creds::Config;
|
||||
use sqlx::Error::RowNotFound;
|
||||
|
||||
let username = id.identity().unwrap();
|
||||
|
||||
struct PasswordID {
|
||||
password: String,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
let rec = sqlx::query_as!(
|
||||
PasswordID,
|
||||
r#"SELECT ID, password FROM mcaptcha_users WHERE name = ($1)"#,
|
||||
&username,
|
||||
)
|
||||
.fetch_one(&data.db)
|
||||
.await;
|
||||
|
||||
match rec {
|
||||
Ok(rec) => {
|
||||
if Config::verify(&rec.password, &payload.password)? {
|
||||
let payload = payload.into_inner();
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_levels
|
||||
WHERE config_id = (
|
||||
SELECT config_id FROM mcaptcha_config
|
||||
WHERE key = $1 AND user_id = $2
|
||||
);",
|
||||
&payload.key,
|
||||
&rec.id,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_config WHERE key = ($1) AND user_id = $2;",
|
||||
&payload.key,
|
||||
&rec.id,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
if let Err(err) = data.captcha.remove(RemoveCaptcha(payload.key)).await {
|
||||
log::error!(
|
||||
"Error while trying to remove captcha from cache {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
Ok(HttpResponse::Ok())
|
||||
} else {
|
||||
Err(ServiceError::WrongPassword)
|
||||
}
|
||||
}
|
||||
Err(RowNotFound) => Err(ServiceError::UsernameNotFound),
|
||||
Err(_) => Err(ServiceError::InternalServerError),
|
||||
}
|
||||
}
|
|
@ -1,180 +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::{web, HttpResponse, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::api::v1::mcaptcha::captcha::MCaptchaDetails;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
pub mod routes {
|
||||
pub struct Duration {
|
||||
pub update: &'static str,
|
||||
pub get: &'static str,
|
||||
}
|
||||
impl Duration {
|
||||
pub const fn new() -> Duration {
|
||||
Duration {
|
||||
update: "/api/v1/mcaptcha/domain/token/duration/update",
|
||||
get: "/api/v1/mcaptcha/domain/token/duration/get",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct UpdateDuration {
|
||||
pub key: String,
|
||||
pub duration: i32,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.duration.update",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn update_duration(
|
||||
payload: web::Json<UpdateDuration>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
|
||||
if payload.duration > 0 {
|
||||
sqlx::query!(
|
||||
"UPDATE mcaptcha_config set duration = $1
|
||||
WHERE key = $2 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)",
|
||||
&payload.duration,
|
||||
&payload.key,
|
||||
&username,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok())
|
||||
} else {
|
||||
// when mCaptcha/mCaptcha #2 is fixed, this wont be necessary
|
||||
Err(ServiceError::CaptchaError(
|
||||
libmcaptcha::errors::CaptchaError::CaptchaDurationZero,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct GetDurationResp {
|
||||
pub duration: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct GetDuration {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.duration.get",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn get_duration(
|
||||
payload: web::Json<MCaptchaDetails>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
|
||||
let duration = sqlx::query_as!(
|
||||
GetDurationResp,
|
||||
"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?;
|
||||
Ok(HttpResponse::Ok().json(duration))
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(get_duration);
|
||||
cfg.service(update_duration);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::tests::*;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn update_duration() {
|
||||
const NAME: &str = "testuserduration";
|
||||
const PASSWORD: &str = "longpassworddomain";
|
||||
const EMAIL: &str = "testuserduration@a.com";
|
||||
|
||||
{
|
||||
let data = Data::new().await;
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
|
||||
register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let (data, _, signin_resp, token_key) = add_levels_util(NAME, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data).await;
|
||||
|
||||
let update = UpdateDuration {
|
||||
key: token_key.key.clone(),
|
||||
duration: 40,
|
||||
};
|
||||
|
||||
// check default
|
||||
|
||||
let get_level_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&token_key, ROUTES.duration.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_level_resp.status(), StatusCode::OK);
|
||||
let res_levels: GetDurationResp = test::read_body_json(get_level_resp).await;
|
||||
assert_eq!(res_levels.duration, 30);
|
||||
|
||||
// update and check changes
|
||||
|
||||
let update_duration = test::call_service(
|
||||
&app,
|
||||
post_request!(&update, ROUTES.duration.update)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(update_duration.status(), StatusCode::OK);
|
||||
let get_level_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&token_key, ROUTES.duration.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_level_resp.status(), StatusCode::OK);
|
||||
let res_levels: GetDurationResp = test::read_body_json(get_level_resp).await;
|
||||
assert_eq!(res_levels.duration, 40);
|
||||
}
|
||||
}
|
|
@ -14,213 +14,37 @@
|
|||
* 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 std::borrow::Cow;
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use libmcaptcha::master::messages::{RemoveCaptcha, RenameBuilder};
|
||||
use libmcaptcha::{defense::Level, defense::LevelBuilder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::get_random;
|
||||
use super::levels::{add_captcha_runner, update_level_runner, AddLevels, UpdateLevels};
|
||||
use super::create::{runner::create as create_runner, CreateCaptcha};
|
||||
use super::update::{runner::update_captcha as update_captcha_runner, UpdateCaptcha};
|
||||
use crate::errors::*;
|
||||
use crate::settings::DefaultDifficultyStrategy;
|
||||
use crate::stats::fetch::{Stats, StatsUnixTimestamp};
|
||||
use crate::AppData;
|
||||
|
||||
pub mod routes {
|
||||
pub struct MCaptcha {
|
||||
pub delete: &'static str,
|
||||
pub update_key: &'static str,
|
||||
pub stats: &'static str,
|
||||
pub struct Easy {
|
||||
/// easy is using defaults
|
||||
pub create_easy: &'static str,
|
||||
pub update_easy: &'static str,
|
||||
pub create: &'static str,
|
||||
pub update: &'static str,
|
||||
}
|
||||
|
||||
impl MCaptcha {
|
||||
pub const fn new() -> MCaptcha {
|
||||
MCaptcha {
|
||||
update_key: "/api/v1/mcaptcha/update/key",
|
||||
delete: "/api/v1/mcaptcha/delete",
|
||||
stats: "/api/v1/mcaptcha/stats",
|
||||
create_easy: "/api/v1/mcaptcha/add/easy",
|
||||
update_easy: "/api/v1/mcaptcha/update/easy",
|
||||
impl Easy {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
create: "/api/v1/mcaptcha/add/easy",
|
||||
update: "/api/v1/mcaptcha/update/easy",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(update_token);
|
||||
cfg.service(delete_mcaptcha);
|
||||
cfg.service(get_stats);
|
||||
cfg.service(create_easy);
|
||||
cfg.service(update_easy);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct MCaptchaID {
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct MCaptchaDetails {
|
||||
pub name: String,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.mcaptcha.update_key",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn update_token(
|
||||
payload: web::Json<MCaptchaDetails>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let mut key;
|
||||
|
||||
loop {
|
||||
key = get_random(32);
|
||||
let res = update_token_helper(&key, &payload.key, &username, &data).await;
|
||||
if res.is_ok() {
|
||||
break;
|
||||
} else if let Err(sqlx::Error::Database(err)) = res {
|
||||
if err.code() == Some(Cow::from("23505")) {
|
||||
continue;
|
||||
} else {
|
||||
return Err(sqlx::Error::Database(err).into());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let payload = payload.into_inner();
|
||||
let rename = RenameBuilder::default()
|
||||
.name(payload.key)
|
||||
.rename_to(key.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
data.captcha.rename(rename).await?;
|
||||
|
||||
let resp = MCaptchaDetails {
|
||||
key,
|
||||
name: payload.name,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(resp))
|
||||
}
|
||||
|
||||
async fn update_token_helper(
|
||||
key: &str,
|
||||
old_key: &str,
|
||||
username: &str,
|
||||
data: &AppData,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE mcaptcha_config SET key = $1
|
||||
WHERE key = $2 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)",
|
||||
&key,
|
||||
&old_key,
|
||||
&username,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct DeleteCaptcha {
|
||||
pub key: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.mcaptcha.delete",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn delete_mcaptcha(
|
||||
payload: web::Json<DeleteCaptcha>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
use argon2_creds::Config;
|
||||
use sqlx::Error::RowNotFound;
|
||||
|
||||
let username = id.identity().unwrap();
|
||||
|
||||
struct PasswordID {
|
||||
password: String,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
let rec = sqlx::query_as!(
|
||||
PasswordID,
|
||||
r#"SELECT ID, password FROM mcaptcha_users WHERE name = ($1)"#,
|
||||
&username,
|
||||
)
|
||||
.fetch_one(&data.db)
|
||||
.await;
|
||||
|
||||
match rec {
|
||||
Ok(rec) => {
|
||||
if Config::verify(&rec.password, &payload.password)? {
|
||||
let payload = payload.into_inner();
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_levels
|
||||
WHERE config_id = (
|
||||
SELECT config_id FROM mcaptcha_config
|
||||
WHERE key = $1 AND user_id = $2
|
||||
);",
|
||||
&payload.key,
|
||||
&rec.id,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_config WHERE key = ($1) AND user_id = $2;",
|
||||
&payload.key,
|
||||
&rec.id,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
if let Err(err) = data.captcha.remove(RemoveCaptcha(payload.key)).await {
|
||||
log::error!(
|
||||
"Error while trying to remove captcha from cache {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
Ok(HttpResponse::Ok())
|
||||
} else {
|
||||
Err(ServiceError::WrongPassword)
|
||||
}
|
||||
}
|
||||
Err(RowNotFound) => Err(ServiceError::UsernameNotFound),
|
||||
Err(_) => Err(ServiceError::InternalServerError),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct StatsPayload {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.mcaptcha.stats",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn get_stats(
|
||||
payload: web::Json<StatsPayload>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let stats = Stats::new(&username, &payload.key, &data.db).await?;
|
||||
let stats = StatsUnixTimestamp::from_stats(&stats);
|
||||
Ok(HttpResponse::Ok().json(&stats))
|
||||
cfg.service(update);
|
||||
cfg.service(create);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
@ -271,10 +95,10 @@ impl TrafficPattern {
|
|||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.mcaptcha.create_easy",
|
||||
path = "crate::V1_API_ROUTES.captcha.easy.create",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn create_easy(
|
||||
async fn create(
|
||||
payload: web::Json<TrafficPattern>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
|
@ -283,7 +107,7 @@ async fn create_easy(
|
|||
let payload = payload.into_inner();
|
||||
let levels =
|
||||
payload.calculate(&crate::SETTINGS.captcha.default_difficulty_strategy)?;
|
||||
let msg = AddLevels {
|
||||
let msg = CreateCaptcha {
|
||||
levels,
|
||||
duration: crate::SETTINGS.captcha.default_difficulty_strategy.duration,
|
||||
description: payload.description,
|
||||
|
@ -294,7 +118,7 @@ async fn create_easy(
|
|||
None => None,
|
||||
};
|
||||
|
||||
let mcaptcha_config = add_captcha_runner(&msg, &data, &username).await?;
|
||||
let mcaptcha_config = create_runner(&msg, &data, &username).await?;
|
||||
sqlx::query!(
|
||||
"INSERT INTO mcaptcha_sitekey_user_provided_avg_traffic (
|
||||
config_id,
|
||||
|
@ -327,10 +151,10 @@ pub struct UpdateTrafficPattern {
|
|||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.mcaptcha.update_easy",
|
||||
path = "crate::V1_API_ROUTES.captcha.easy.update",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn update_easy(
|
||||
async fn update(
|
||||
payload: web::Json<UpdateTrafficPattern>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
|
@ -341,14 +165,14 @@ async fn update_easy(
|
|||
.pattern
|
||||
.calculate(&crate::SETTINGS.captcha.default_difficulty_strategy)?;
|
||||
|
||||
let msg = UpdateLevels {
|
||||
let msg = UpdateCaptcha {
|
||||
levels,
|
||||
duration: crate::SETTINGS.captcha.default_difficulty_strategy.duration,
|
||||
description: payload.pattern.description,
|
||||
key: payload.key,
|
||||
};
|
||||
|
||||
update_level_runner(&msg, &data, &username).await?;
|
||||
update_captcha_runner(&msg, &data, &username).await?;
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_sitekey_user_provided_avg_traffic
|
||||
|
@ -403,63 +227,11 @@ mod tests {
|
|||
use actix_web::test;
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::mcaptcha::create::MCaptchaDetails;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::tests::*;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn update_and_get_mcaptcha_works() {
|
||||
const NAME: &str = "updateusermcaptcha";
|
||||
const PASSWORD: &str = "longpassworddomain";
|
||||
const EMAIL: &str = "testupdateusermcaptcha@a.com";
|
||||
|
||||
{
|
||||
let data = Data::new().await;
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
|
||||
// 1. add mcaptcha token
|
||||
register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let (data, _, signin_resp, token_key) = add_levels_util(NAME, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data).await;
|
||||
|
||||
// 2. update token key
|
||||
let update_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&token_key, ROUTES.mcaptcha.update_key)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(update_token_resp.status(), StatusCode::OK);
|
||||
let updated_token: MCaptchaDetails =
|
||||
test::read_body_json(update_token_resp).await;
|
||||
|
||||
// get levels with udpated key
|
||||
let get_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&updated_token, ROUTES.levels.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
// if updated key doesn't exist in databse, a non 200 result will bereturned
|
||||
assert_eq!(get_token_resp.status(), StatusCode::OK);
|
||||
|
||||
// get stats
|
||||
let paylod = StatsPayload { key: token_key.key };
|
||||
let get_statis_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&paylod, ROUTES.mcaptcha.stats)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
// if updated key doesn't exist in databse, a non 200 result will bereturned
|
||||
assert_eq!(get_statis_resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod isoloated_test {
|
||||
use super::{LevelBuilder, TrafficPattern};
|
||||
|
@ -566,7 +338,7 @@ mod tests {
|
|||
|
||||
let add_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, ROUTES.mcaptcha.create_easy)
|
||||
post_request!(&payload, ROUTES.captcha.easy.create)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
|
@ -576,7 +348,7 @@ mod tests {
|
|||
|
||||
let get_level_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&token_key, ROUTES.levels.get)
|
||||
post_request!(&token_key, ROUTES.captcha.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
|
@ -606,7 +378,7 @@ mod tests {
|
|||
|
||||
let update_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&payload, ROUTES.mcaptcha.update_easy)
|
||||
post_request!(&payload, ROUTES.captcha.easy.update)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
|
@ -615,7 +387,7 @@ mod tests {
|
|||
|
||||
let get_level_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&token_key, ROUTES.levels.get)
|
||||
post_request!(&token_key, ROUTES.captcha.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
76
src/api/v1/mcaptcha/get.rs
Normal file
76
src/api/v1/mcaptcha/get.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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::{web, HttpResponse, Responder};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::create::MCaptchaDetails;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.captcha.get",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
pub async fn get_captcha(
|
||||
payload: web::Json<MCaptchaDetails>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let levels = runner::get_captcha(&payload.key, &username, &data).await?;
|
||||
Ok(HttpResponse::Ok().json(levels))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Levels {
|
||||
levels: I32Levels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct I32Levels {
|
||||
pub difficulty_factor: i32,
|
||||
pub visitor_threshold: i32,
|
||||
}
|
||||
|
||||
pub mod runner {
|
||||
use super::*;
|
||||
|
||||
// TODO get metadata from mcaptcha_config table
|
||||
pub async fn get_captcha(
|
||||
key: &str,
|
||||
username: &str,
|
||||
data: &AppData,
|
||||
) -> 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 key = ($1)
|
||||
AND user_id = (SELECT ID from mcaptcha_users WHERE name = $2)
|
||||
)
|
||||
ORDER BY difficulty_factor ASC;",
|
||||
key,
|
||||
&username
|
||||
)
|
||||
.fetch_all(&data.db)
|
||||
.await?;
|
||||
|
||||
Ok(levels)
|
||||
}
|
||||
}
|
|
@ -1,420 +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 std::borrow::Cow;
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use futures::future::try_join_all;
|
||||
use libmcaptcha::{defense::Level, master::messages::RemoveCaptcha, DefenseBuilder};
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::captcha::MCaptchaDetails;
|
||||
use super::get_random;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
pub mod routes {
|
||||
|
||||
pub struct Levels {
|
||||
pub add: &'static str,
|
||||
pub get: &'static str,
|
||||
pub update: &'static str,
|
||||
}
|
||||
|
||||
impl Levels {
|
||||
pub const fn new() -> Levels {
|
||||
let add = "/api/v1/mcaptcha/add";
|
||||
let update = "/api/v1/mcaptcha/update";
|
||||
let get = "/api/v1/mcaptcha/get";
|
||||
Levels { add, get, update }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AddLevels {
|
||||
pub levels: Vec<Level>,
|
||||
pub duration: u32,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(add_levels);
|
||||
cfg.service(update_levels);
|
||||
cfg.service(get_levels);
|
||||
}
|
||||
|
||||
// TODO redo mcaptcha table to include levels as json field
|
||||
// so that the whole thing can be added/udpaed in a single stroke
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.levels.add", wrap = "crate::CheckLogin")]
|
||||
async fn add_levels(
|
||||
payload: web::Json<AddLevels>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let mcaptcha_config = add_captcha_runner(&payload, &data, &username).await?;
|
||||
Ok(HttpResponse::Ok().json(mcaptcha_config))
|
||||
}
|
||||
|
||||
pub async fn add_captcha_runner(
|
||||
payload: &AddLevels,
|
||||
data: &AppData,
|
||||
username: &str,
|
||||
) -> ServiceResult<MCaptchaDetails> {
|
||||
let mut defense = DefenseBuilder::default();
|
||||
for level in payload.levels.iter() {
|
||||
defense.add_level(*level)?;
|
||||
}
|
||||
|
||||
defense.build()?;
|
||||
|
||||
debug!("creating config");
|
||||
let mcaptcha_config =
|
||||
// add_mcaptcha_util(payload.duration, &payload.description, &data, username).await?;
|
||||
|
||||
{
|
||||
let mut key;
|
||||
|
||||
let resp;
|
||||
|
||||
loop {
|
||||
key = get_random(32);
|
||||
|
||||
let res = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_config
|
||||
(key, user_id, duration, name)
|
||||
VALUES ($1, (SELECT ID FROM mcaptcha_users WHERE name = $2), $3, $4)",
|
||||
&key,
|
||||
&username,
|
||||
payload.duration as i32,
|
||||
&payload.description,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Err(sqlx::Error::Database(err)) => {
|
||||
if err.code() == Some(Cow::from("23505"))
|
||||
&& err.message().contains("mcaptcha_config_key_key")
|
||||
{
|
||||
continue;
|
||||
} else {
|
||||
return Err(sqlx::Error::Database(err).into());
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
|
||||
Ok(_) => {
|
||||
resp = MCaptchaDetails {
|
||||
key,
|
||||
name: payload.description.to_owned(),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
resp
|
||||
};
|
||||
|
||||
debug!("config created");
|
||||
|
||||
let mut futs = Vec::with_capacity(payload.levels.len());
|
||||
|
||||
for level in payload.levels.iter() {
|
||||
let difficulty_factor = level.difficulty_factor as i32;
|
||||
let visitor_threshold = level.visitor_threshold as i32;
|
||||
let fut = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_levels (
|
||||
difficulty_factor,
|
||||
visitor_threshold,
|
||||
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,
|
||||
&mcaptcha_config.key,
|
||||
&username,
|
||||
)
|
||||
.execute(&data.db);
|
||||
futs.push(fut);
|
||||
}
|
||||
|
||||
try_join_all(futs).await?;
|
||||
Ok(mcaptcha_config)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UpdateLevels {
|
||||
pub levels: Vec<Level>,
|
||||
pub duration: u32,
|
||||
pub description: String,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.levels.update",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
async fn update_levels(
|
||||
payload: web::Json<UpdateLevels>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
update_level_runner(&payload, &data, &username).await?;
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
pub async fn update_level_runner(
|
||||
payload: &UpdateLevels,
|
||||
data: &AppData,
|
||||
username: &str,
|
||||
) -> ServiceResult<()> {
|
||||
let mut defense = DefenseBuilder::default();
|
||||
|
||||
for level in payload.levels.iter() {
|
||||
defense.add_level(*level)?;
|
||||
}
|
||||
|
||||
// I feel this is necessary as both difficulty factor _and_ visitor threshold of a
|
||||
// level could change so doing this would not require us to send level_id to client
|
||||
// still, needs to be benchmarked
|
||||
defense.build()?;
|
||||
|
||||
let mut futs = Vec::with_capacity(payload.levels.len() + 2);
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_levels
|
||||
WHERE config_id = (
|
||||
SELECT config_id FROM mcaptcha_config where key = ($1)
|
||||
AND user_id = (
|
||||
SELECT ID from mcaptcha_users WHERE name = $2
|
||||
)
|
||||
)",
|
||||
&payload.key,
|
||||
&username
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
|
||||
let update_fut = sqlx::query!(
|
||||
"UPDATE mcaptcha_config SET name = $1, duration = $2
|
||||
WHERE user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)
|
||||
AND key = $4",
|
||||
&payload.description,
|
||||
payload.duration as i32,
|
||||
&username,
|
||||
&payload.key,
|
||||
)
|
||||
.execute(&data.db); //.await?;
|
||||
|
||||
futs.push(update_fut);
|
||||
|
||||
for level in payload.levels.iter() {
|
||||
let difficulty_factor = level.difficulty_factor as i32;
|
||||
let visitor_threshold = level.visitor_threshold as i32;
|
||||
let fut = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_levels (
|
||||
difficulty_factor,
|
||||
visitor_threshold,
|
||||
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.key,
|
||||
&username,
|
||||
)
|
||||
.execute(&data.db); //.await?;
|
||||
futs.push(fut);
|
||||
}
|
||||
|
||||
try_join_all(futs).await?;
|
||||
if let Err(ServiceError::CaptchaError(e)) = data
|
||||
.captcha
|
||||
.remove(RemoveCaptcha(payload.key.clone()))
|
||||
.await
|
||||
{
|
||||
log::error!(
|
||||
"Deleting captcha key {} while updating it, error: {:?}",
|
||||
&payload.key,
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[my_codegen::post(path = "crate::V1_API_ROUTES.levels.get", wrap = "crate::CheckLogin")]
|
||||
async fn get_levels(
|
||||
payload: web::Json<MCaptchaDetails>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
|
||||
let levels = get_levels_util(&payload.key, &username, &data).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(levels))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Levels {
|
||||
levels: I32Levels,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct I32Levels {
|
||||
pub difficulty_factor: i32,
|
||||
pub visitor_threshold: i32,
|
||||
}
|
||||
|
||||
async fn get_levels_util(
|
||||
key: &str,
|
||||
username: &str,
|
||||
data: &AppData,
|
||||
) -> 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 key = ($1)
|
||||
AND user_id = (SELECT ID from mcaptcha_users WHERE name = $2)
|
||||
)
|
||||
ORDER BY difficulty_factor ASC;",
|
||||
key,
|
||||
&username
|
||||
)
|
||||
.fetch_all(&data.db)
|
||||
.await?;
|
||||
|
||||
Ok(levels)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use super::*;
|
||||
use crate::api::v1::mcaptcha::captcha::DeleteCaptcha;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::data::Data;
|
||||
use crate::tests::*;
|
||||
use crate::*;
|
||||
|
||||
const L1: Level = Level {
|
||||
difficulty_factor: 100,
|
||||
visitor_threshold: 10,
|
||||
};
|
||||
const L2: Level = Level {
|
||||
difficulty_factor: 1000,
|
||||
visitor_threshold: 1000,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn level_routes_work() {
|
||||
const NAME: &str = "testuserlevelroutes";
|
||||
const PASSWORD: &str = "longpassworddomain";
|
||||
const EMAIL: &str = "testuserlevelrouts@a.com";
|
||||
|
||||
{
|
||||
let data = Data::new().await;
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
|
||||
register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let (data, _, signin_resp, key) = add_levels_util(NAME, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data).await;
|
||||
|
||||
// 2. get level
|
||||
let add_level = get_level_data();
|
||||
let get_level_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&key, ROUTES.levels.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_level_resp.status(), StatusCode::OK);
|
||||
let res_levels: Vec<Level> = test::read_body_json(get_level_resp).await;
|
||||
assert_eq!(res_levels, add_level.levels);
|
||||
|
||||
// 3. update level
|
||||
let levels = vec![L1, L2];
|
||||
let update_level = UpdateLevels {
|
||||
key: key.key.clone(),
|
||||
levels: levels.clone(),
|
||||
description: add_level.description,
|
||||
duration: add_level.duration,
|
||||
};
|
||||
|
||||
let add_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&update_level, ROUTES.levels.update)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(add_token_resp.status(), StatusCode::OK);
|
||||
|
||||
let get_level_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&key, ROUTES.levels.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_level_resp.status(), StatusCode::OK);
|
||||
let res_levels: Vec<Level> = test::read_body_json(get_level_resp).await;
|
||||
assert_eq!(res_levels, levels);
|
||||
|
||||
// 4. delete captcha
|
||||
let mut delete_payload = DeleteCaptcha {
|
||||
key: key.key,
|
||||
password: format!("worongpass{}", PASSWORD),
|
||||
};
|
||||
|
||||
bad_post_req_test(
|
||||
NAME,
|
||||
PASSWORD,
|
||||
ROUTES.mcaptcha.delete,
|
||||
&delete_payload,
|
||||
ServiceError::WrongPassword,
|
||||
)
|
||||
.await;
|
||||
|
||||
delete_payload.password = PASSWORD.into();
|
||||
|
||||
let del_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&delete_payload, ROUTES.mcaptcha.delete)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(del_resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
|
@ -15,9 +15,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pub mod captcha;
|
||||
pub mod duration;
|
||||
pub mod levels;
|
||||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod easy;
|
||||
pub mod get;
|
||||
pub mod stats;
|
||||
#[cfg(test)]
|
||||
pub mod test;
|
||||
pub mod update;
|
||||
|
||||
pub fn get_random(len: usize) -> String {
|
||||
use std::iter;
|
||||
|
@ -34,7 +39,40 @@ pub fn get_random(len: usize) -> String {
|
|||
}
|
||||
|
||||
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
duration::services(cfg);
|
||||
levels::services(cfg);
|
||||
captcha::services(cfg);
|
||||
easy::services(cfg);
|
||||
cfg.service(stats::get);
|
||||
cfg.service(create::create);
|
||||
cfg.service(get::get_captcha);
|
||||
cfg.service(update::update_key);
|
||||
cfg.service(update::update_captcha);
|
||||
cfg.service(delete::delete);
|
||||
}
|
||||
|
||||
pub mod routes {
|
||||
use super::easy::routes::Easy;
|
||||
use super::stats::routes::Stats;
|
||||
|
||||
pub struct Captcha {
|
||||
pub create: &'static str,
|
||||
pub update: &'static str,
|
||||
pub get: &'static str,
|
||||
pub delete: &'static str,
|
||||
pub update_key: &'static str,
|
||||
pub easy: Easy,
|
||||
pub stats: Stats,
|
||||
}
|
||||
|
||||
impl Captcha {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
create: "/api/v1/mcaptcha/create",
|
||||
update: "/api/v1/mcaptcha/update",
|
||||
get: "/api/v1/mcaptcha/get",
|
||||
update_key: "/api/v1/mcaptcha/update/key",
|
||||
delete: "/api/v1/mcaptcha/delete",
|
||||
easy: Easy::new(),
|
||||
stats: Stats::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
56
src/api/v1/mcaptcha/stats.rs
Normal file
56
src/api/v1/mcaptcha/stats.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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::{web, HttpResponse, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::errors::*;
|
||||
use crate::stats::fetch::{Stats, StatsUnixTimestamp};
|
||||
use crate::AppData;
|
||||
|
||||
pub mod routes {
|
||||
pub struct Stats {
|
||||
pub get: &'static str,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
get: "/api/v1/mcaptcha/stats",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct StatsPayload {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.captcha.stats.get",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
pub async fn get(
|
||||
payload: web::Json<StatsPayload>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let stats = Stats::new(&username, &payload.key, &data.db).await?;
|
||||
let stats = StatsUnixTimestamp::from_stats(&stats);
|
||||
Ok(HttpResponse::Ok().json(&stats))
|
||||
}
|
124
src/api/v1/mcaptcha/test.rs
Normal file
124
src/api/v1/mcaptcha/test.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use crate::api::v1::mcaptcha::delete::DeleteCaptcha;
|
||||
use libmcaptcha::defense::Level;
|
||||
|
||||
use crate::api::v1::mcaptcha::update::UpdateCaptcha;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::data::Data;
|
||||
use crate::errors::*;
|
||||
use crate::tests::*;
|
||||
use crate::*;
|
||||
|
||||
const L1: Level = Level {
|
||||
difficulty_factor: 100,
|
||||
visitor_threshold: 10,
|
||||
};
|
||||
const L2: Level = Level {
|
||||
difficulty_factor: 1000,
|
||||
visitor_threshold: 1000,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn level_routes_work() {
|
||||
const NAME: &str = "testuserlevelroutes";
|
||||
const PASSWORD: &str = "longpassworddomain";
|
||||
const EMAIL: &str = "testuserlevelrouts@a.com";
|
||||
|
||||
{
|
||||
let data = Data::new().await;
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
|
||||
register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
// create captcha
|
||||
let (data, _, signin_resp, key) = add_levels_util(NAME, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data).await;
|
||||
|
||||
// 2. get captcha
|
||||
let add_level = get_level_data();
|
||||
let get_level_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&key, ROUTES.captcha.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_level_resp.status(), StatusCode::OK);
|
||||
let res_levels: Vec<Level> = test::read_body_json(get_level_resp).await;
|
||||
assert_eq!(res_levels, add_level.levels);
|
||||
|
||||
// 3. update captcha
|
||||
let levels = vec![L1, L2];
|
||||
let update_level = UpdateCaptcha {
|
||||
key: key.key.clone(),
|
||||
levels: levels.clone(),
|
||||
description: add_level.description,
|
||||
duration: add_level.duration,
|
||||
};
|
||||
|
||||
let add_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&update_level, ROUTES.captcha.update)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(add_token_resp.status(), StatusCode::OK);
|
||||
|
||||
let get_level_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&key, ROUTES.captcha.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(get_level_resp.status(), StatusCode::OK);
|
||||
let res_levels: Vec<Level> = test::read_body_json(get_level_resp).await;
|
||||
assert_eq!(res_levels, levels);
|
||||
|
||||
// 4. delete captcha
|
||||
let mut delete_payload = DeleteCaptcha {
|
||||
key: key.key,
|
||||
password: format!("worongpass{}", PASSWORD),
|
||||
};
|
||||
|
||||
bad_post_req_test(
|
||||
NAME,
|
||||
PASSWORD,
|
||||
ROUTES.captcha.delete,
|
||||
&delete_payload,
|
||||
ServiceError::WrongPassword,
|
||||
)
|
||||
.await;
|
||||
|
||||
delete_payload.password = PASSWORD.into();
|
||||
|
||||
let del_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&delete_payload, ROUTES.captcha.delete)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(del_resp.status(), StatusCode::OK);
|
||||
}
|
264
src/api/v1/mcaptcha/update.rs
Normal file
264
src/api/v1/mcaptcha/update.rs
Normal file
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* 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 std::borrow::Cow;
|
||||
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use libmcaptcha::defense::Level;
|
||||
use libmcaptcha::master::messages::RenameBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::create::MCaptchaDetails;
|
||||
use super::get_random;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.captcha.update_key",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
pub async fn update_key(
|
||||
payload: web::Json<MCaptchaDetails>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
let mut key;
|
||||
|
||||
loop {
|
||||
key = get_random(32);
|
||||
let res = runner::update_key(&key, &payload.key, &username, &data).await;
|
||||
if res.is_ok() {
|
||||
break;
|
||||
} else if let Err(sqlx::Error::Database(err)) = res {
|
||||
if err.code() == Some(Cow::from("23505")) {
|
||||
continue;
|
||||
} else {
|
||||
return Err(sqlx::Error::Database(err).into());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let payload = payload.into_inner();
|
||||
let rename = RenameBuilder::default()
|
||||
.name(payload.key)
|
||||
.rename_to(key.clone())
|
||||
.build()
|
||||
.unwrap();
|
||||
data.captcha.rename(rename).await?;
|
||||
|
||||
let resp = MCaptchaDetails {
|
||||
key,
|
||||
name: payload.name,
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(resp))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UpdateCaptcha {
|
||||
pub levels: Vec<Level>,
|
||||
pub duration: u32,
|
||||
pub description: String,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[my_codegen::post(
|
||||
path = "crate::V1_API_ROUTES.captcha.update",
|
||||
wrap = "crate::CheckLogin"
|
||||
)]
|
||||
pub async fn update_captcha(
|
||||
payload: web::Json<UpdateCaptcha>,
|
||||
data: AppData,
|
||||
id: Identity,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
let username = id.identity().unwrap();
|
||||
runner::update_captcha(&payload, &data, &username).await?;
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
pub mod runner {
|
||||
use futures::future::try_join_all;
|
||||
use libmcaptcha::{master::messages::RemoveCaptcha, DefenseBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub async fn update_key(
|
||||
key: &str,
|
||||
old_key: &str,
|
||||
username: &str,
|
||||
data: &AppData,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE mcaptcha_config SET key = $1
|
||||
WHERE key = $2 AND user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)",
|
||||
&key,
|
||||
&old_key,
|
||||
&username,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_captcha(
|
||||
payload: &UpdateCaptcha,
|
||||
data: &AppData,
|
||||
username: &str,
|
||||
) -> ServiceResult<()> {
|
||||
let mut defense = DefenseBuilder::default();
|
||||
|
||||
for level in payload.levels.iter() {
|
||||
defense.add_level(*level)?;
|
||||
}
|
||||
|
||||
// I feel this is necessary as both difficulty factor _and_ visitor threshold of a
|
||||
// level could change so doing this would not require us to send level_id to client
|
||||
// still, needs to be benchmarked
|
||||
defense.build()?;
|
||||
|
||||
let mut futs = Vec::with_capacity(payload.levels.len() + 2);
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_levels
|
||||
WHERE config_id = (
|
||||
SELECT config_id FROM mcaptcha_config where key = ($1)
|
||||
AND user_id = (
|
||||
SELECT ID from mcaptcha_users WHERE name = $2
|
||||
)
|
||||
)",
|
||||
&payload.key,
|
||||
&username
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
|
||||
let update_fut = sqlx::query!(
|
||||
"UPDATE mcaptcha_config SET name = $1, duration = $2
|
||||
WHERE user_id = (SELECT ID FROM mcaptcha_users WHERE name = $3)
|
||||
AND key = $4",
|
||||
&payload.description,
|
||||
payload.duration as i32,
|
||||
&username,
|
||||
&payload.key,
|
||||
)
|
||||
.execute(&data.db); //.await?;
|
||||
|
||||
futs.push(update_fut);
|
||||
|
||||
for level in payload.levels.iter() {
|
||||
let difficulty_factor = level.difficulty_factor as i32;
|
||||
let visitor_threshold = level.visitor_threshold as i32;
|
||||
let fut = sqlx::query!(
|
||||
"INSERT INTO mcaptcha_levels (
|
||||
difficulty_factor,
|
||||
visitor_threshold,
|
||||
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.key,
|
||||
&username,
|
||||
)
|
||||
.execute(&data.db); //.await?;
|
||||
futs.push(fut);
|
||||
}
|
||||
|
||||
try_join_all(futs).await?;
|
||||
if let Err(ServiceError::CaptchaError(e)) = data
|
||||
.captcha
|
||||
.remove(RemoveCaptcha(payload.key.clone()))
|
||||
.await
|
||||
{
|
||||
log::error!(
|
||||
"Deleting captcha key {} while updating it, error: {:?}",
|
||||
&payload.key,
|
||||
e
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use crate::api::v1::mcaptcha::create::MCaptchaDetails;
|
||||
use crate::api::v1::mcaptcha::stats::StatsPayload;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::tests::*;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn update_and_get_mcaptcha_works() {
|
||||
const NAME: &str = "updateusermcaptcha";
|
||||
const PASSWORD: &str = "longpassworddomain";
|
||||
const EMAIL: &str = "testupdateusermcaptcha@a.com";
|
||||
|
||||
{
|
||||
let data = Data::new().await;
|
||||
delete_user(NAME, &data).await;
|
||||
}
|
||||
|
||||
// 1. add mcaptcha token
|
||||
register_and_signin(NAME, EMAIL, PASSWORD).await;
|
||||
let (data, _, signin_resp, token_key) = add_levels_util(NAME, PASSWORD).await;
|
||||
let cookies = get_cookie!(signin_resp);
|
||||
let app = get_app!(data).await;
|
||||
|
||||
// 2. update token key
|
||||
let update_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&token_key, ROUTES.captcha.update_key)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(update_token_resp.status(), StatusCode::OK);
|
||||
let updated_token: MCaptchaDetails =
|
||||
test::read_body_json(update_token_resp).await;
|
||||
|
||||
// get levels with udpated key
|
||||
let get_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&updated_token, ROUTES.captcha.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
// if updated key doesn't exist in databse, a non 200 result will bereturned
|
||||
assert_eq!(get_token_resp.status(), StatusCode::OK);
|
||||
|
||||
// get stats
|
||||
let paylod = StatsPayload { key: token_key.key };
|
||||
let get_statis_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&paylod, ROUTES.captcha.stats.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
.await;
|
||||
// if updated key doesn't exist in databse, a non 200 result will bereturned
|
||||
assert_eq!(get_statis_resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ use libmcaptcha::{
|
|||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::GetDurationResp;
|
||||
use super::I32Levels;
|
||||
use crate::errors::*;
|
||||
use crate::stats::record::record_fetch;
|
||||
|
@ -96,9 +95,13 @@ async fn init_mcaptcha(data: &AppData, key: &str) -> ServiceResult<()> {
|
|||
&key,
|
||||
)
|
||||
.fetch_all(&data.db);
|
||||
|
||||
struct DurationResp {
|
||||
duration: i32,
|
||||
}
|
||||
// get duration
|
||||
let duration_fut = sqlx::query_as!(
|
||||
GetDurationResp,
|
||||
DurationResp,
|
||||
"SELECT duration FROM mcaptcha_config
|
||||
WHERE key = $1",
|
||||
&key,
|
||||
|
|
|
@ -21,8 +21,7 @@ pub mod get_config;
|
|||
pub mod verify_pow;
|
||||
pub mod verify_token;
|
||||
|
||||
pub use super::mcaptcha::duration::GetDurationResp;
|
||||
pub use super::mcaptcha::levels::I32Levels;
|
||||
pub use super::mcaptcha::get::I32Levels;
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
let cors = actix_cors::Cors::default()
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
|
||||
use super::account::routes::Account;
|
||||
use super::auth::routes::Auth;
|
||||
use super::mcaptcha::captcha::routes::MCaptcha;
|
||||
use super::mcaptcha::duration::routes::Duration;
|
||||
use super::mcaptcha::levels::routes::Levels;
|
||||
use super::mcaptcha::routes::Captcha;
|
||||
use super::meta::routes::Meta;
|
||||
use super::notifications::routes::Notifications;
|
||||
use super::pow::routes::PoW;
|
||||
|
@ -29,9 +27,7 @@ pub const ROUTES: Routes = Routes::new();
|
|||
pub struct Routes {
|
||||
pub auth: Auth,
|
||||
pub account: Account,
|
||||
pub levels: Levels,
|
||||
pub mcaptcha: MCaptcha,
|
||||
pub duration: Duration,
|
||||
pub captcha: Captcha,
|
||||
pub meta: Meta,
|
||||
pub pow: PoW,
|
||||
pub notifications: Notifications,
|
||||
|
@ -42,9 +38,7 @@ impl Routes {
|
|||
Routes {
|
||||
auth: Auth::new(),
|
||||
account: Account::new(),
|
||||
levels: Levels::new(),
|
||||
mcaptcha: MCaptcha::new(),
|
||||
duration: Duration::new(),
|
||||
captcha: Captcha::new(),
|
||||
meta: Meta::new(),
|
||||
pow: PoW::new(),
|
||||
notifications: Notifications::new(),
|
||||
|
|
|
@ -120,7 +120,7 @@ mod tests {
|
|||
|
||||
let resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&token_key, crate::V1_API_ROUTES.levels.get)
|
||||
post_request!(&token_key, crate::V1_API_ROUTES.captcha.get)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
|
@ -133,7 +133,7 @@ mod tests {
|
|||
|
||||
let resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&token_key, crate::V1_API_ROUTES.levels.get)
|
||||
post_request!(&token_key, crate::V1_API_ROUTES.captcha.create)
|
||||
.cookie(cookies)
|
||||
.to_request(),
|
||||
)
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::{PAGES, V1_API_ROUTES};
|
|||
pub async fn delete_sitekey(path: web::Path<String>) -> impl Responder {
|
||||
let key = path.into_inner();
|
||||
let data = vec![("sitekey", key)];
|
||||
let page = SudoPage::new(V1_API_ROUTES.mcaptcha.delete, Some(data))
|
||||
let page = SudoPage::new(V1_API_ROUTES.captcha.delete, Some(data))
|
||||
.render_once()
|
||||
.unwrap();
|
||||
HttpResponse::Ok()
|
||||
|
|
|
@ -19,7 +19,7 @@ use actix_identity::Identity;
|
|||
use actix_web::{HttpResponse, Responder};
|
||||
use sailfish::TemplateOnce;
|
||||
|
||||
use crate::api::v1::mcaptcha::captcha::MCaptchaDetails;
|
||||
use crate::api::v1::mcaptcha::create::MCaptchaDetails;
|
||||
use crate::errors::*;
|
||||
use crate::AppData;
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ use serde::Serialize;
|
|||
|
||||
use super::*;
|
||||
use crate::api::v1::auth::runners::{Login, Register};
|
||||
use crate::api::v1::mcaptcha::captcha::MCaptchaDetails;
|
||||
use crate::api::v1::mcaptcha::levels::AddLevels;
|
||||
use crate::api::v1::mcaptcha::create::CreateCaptcha;
|
||||
use crate::api::v1::mcaptcha::create::MCaptchaDetails;
|
||||
use crate::api::v1::ROUTES;
|
||||
use crate::data::Data;
|
||||
use crate::errors::*;
|
||||
|
@ -159,10 +159,10 @@ pub const L2: Level = Level {
|
|||
visitor_threshold: 500,
|
||||
};
|
||||
|
||||
pub fn get_level_data() -> AddLevels {
|
||||
pub fn get_level_data() -> CreateCaptcha {
|
||||
let levels = vec![L1, L2];
|
||||
|
||||
AddLevels {
|
||||
CreateCaptcha {
|
||||
levels,
|
||||
duration: 30,
|
||||
description: "dummy".into(),
|
||||
|
@ -182,7 +182,7 @@ pub async fn add_levels_util(
|
|||
// 1. add level
|
||||
let add_token_resp = test::call_service(
|
||||
&app,
|
||||
post_request!(&add_level, ROUTES.levels.add)
|
||||
post_request!(&add_level, ROUTES.captcha.create)
|
||||
.cookie(cookies.clone())
|
||||
.to_request(),
|
||||
)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<. include!("../help-banner/index.html"); .>
|
||||
<!-- Main content container -->
|
||||
<div class="inner-container">
|
||||
<div class="sitekey-form" action="<.= crate::V1_API_ROUTES.levels.add .>" method="post">
|
||||
<div class="sitekey-form" action="<.= crate::V1_API_ROUTES.captcha.create .>" method="post">
|
||||
<h1 class="form__title">
|
||||
<.= PAGE .>
|
||||
</h1>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<form class="sitekey-form" action="<.= crate::V1_API_ROUTES.levels.add .>" method="post">
|
||||
<form class="sitekey-form" action="<.= crate::V1_API_ROUTES.captcha.create .>" method="post">
|
||||
<h1 class="form__title">
|
||||
<.= form_title .>
|
||||
</h1>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<. const URL: &str = crate::V1_API_ROUTES.levels.update; .>
|
||||
<. const URL: &str = crate::V1_API_ROUTES.captcha.update; .>
|
||||
<. const READONLY: bool = false; .>
|
||||
<. include!("../view/__form-top.html"); .>
|
||||
<. for (count, level) in levels.iter().enumerate() { .>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<. const URL: &str = crate::V1_API_ROUTES.levels.add; .>
|
||||
<. const URL: &str = crate::V1_API_ROUTES.captcha.create; .>
|
||||
<. const READONLY: bool = true; .>
|
||||
<. include!("./__form-top.html"); .>
|
||||
<. for (count, level) in levels.iter().enumerate() { .>
|
||||
|
|
Loading…
Reference in a new issue