sitekey list

This commit is contained in:
realaravinth 2021-05-04 18:34:36 +05:30
parent 3ac95e1005
commit 98719670df
No known key found for this signature in database
GPG Key ID: AD9F0F08E855ED88
15 changed files with 230 additions and 49 deletions

View File

@ -2,6 +2,6 @@ CREATE TABLE IF NOT EXISTS mcaptcha_config (
config_id SERIAL PRIMARY KEY NOT NULL, config_id SERIAL PRIMARY KEY NOT NULL,
user_id INTEGER NOT NULL references mcaptcha_users(ID) ON DELETE CASCADE, user_id INTEGER NOT NULL references mcaptcha_users(ID) ON DELETE CASCADE,
key varchar(100) NOT NULL UNIQUE, key varchar(100) NOT NULL UNIQUE,
name varchar(100) DEFAULT NULL, name varchar(100) NOT NULL,
duration integer NOT NULL DEFAULT 30 duration integer NOT NULL DEFAULT 30
); );

View File

@ -17,6 +17,7 @@
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use log::debug;
use m_captcha::{defense::Level, DefenseBuilder}; use m_captcha::{defense::Level, DefenseBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -54,6 +55,7 @@ pub mod routes {
pub struct AddLevels { pub struct AddLevels {
pub levels: Vec<Level>, pub levels: Vec<Level>,
pub duration: u32, pub duration: u32,
pub description: String,
} }
pub fn services(cfg: &mut web::ServiceConfig) { pub fn services(cfg: &mut web::ServiceConfig) {
@ -104,7 +106,11 @@ async fn add_levels(
defense.build()?; defense.build()?;
let mcaptcha_config = add_mcaptcha_util(payload.duration, &data, &id).await?; debug!("creating config");
let mcaptcha_config =
add_mcaptcha_util(payload.duration, &payload.description, &data, &id).await?;
debug!("config created");
for level in payload.levels.iter() { for level in payload.levels.iter() {
let difficulty_factor = level.difficulty_factor as i32; let difficulty_factor = level.difficulty_factor as i32;

View File

@ -83,13 +83,15 @@ pub struct MCaptchaID {
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MCaptchaDetails { pub struct MCaptchaDetails {
pub name: Option<String>, pub name: String,
pub key: String, pub key: String,
} }
// this should be called from within add levels // this should be called from within add levels
#[inline]
pub async fn add_mcaptcha_util( pub async fn add_mcaptcha_util(
duration: u32, duration: u32,
description: &str,
data: &Data, data: &Data,
id: &Identity, id: &Identity,
) -> ServiceResult<MCaptchaDetails> { ) -> ServiceResult<MCaptchaDetails> {
@ -102,12 +104,13 @@ pub async fn add_mcaptcha_util(
key = get_random(32); key = get_random(32);
let res = sqlx::query!( let res = sqlx::query!(
"INSERT INTO mcaptcha_config "INSERT INTO mcaptcha_config
(key, user_id, duration) (key, user_id, duration, name)
VALUES ($1, (SELECT ID FROM mcaptcha_users WHERE name = $2), $3)", VALUES ($1, (SELECT ID FROM mcaptcha_users WHERE name = $2), $3, $4)",
&key, &key,
&username, &username,
duration as i32 duration as i32,
description,
) )
.execute(&data.db) .execute(&data.db)
.await; .await;
@ -125,7 +128,10 @@ pub async fn add_mcaptcha_util(
Err(e) => Err(e)?, Err(e) => Err(e)?,
Ok(_) => { Ok(_) => {
resp = MCaptchaDetails { key, name: None }; resp = MCaptchaDetails {
key,
name: description.to_owned(),
};
break; break;
} }
} }
@ -133,10 +139,12 @@ pub async fn add_mcaptcha_util(
Ok(resp) Ok(resp)
} }
// this should be called from within add levels // TODO deprecate this
async fn add_mcaptcha(data: web::Data<Data>, id: Identity) -> ServiceResult<impl Responder> { async fn add_mcaptcha(data: web::Data<Data>, id: Identity) -> ServiceResult<impl Responder> {
let duration = 30; let duration = 30;
let resp = add_mcaptcha_util(duration, &data, &id).await?; let description = "dummy";
let resp = add_mcaptcha_util(duration, description, &data, &id).await?;
Ok(HttpResponse::Ok().json(resp)) Ok(HttpResponse::Ok().json(resp))
} }

View File

@ -24,9 +24,7 @@ use actix_web::{
HttpResponse, HttpResponse,
}; };
use argon2_creds::errors::CredsError; use argon2_creds::errors::CredsError;
//use awc::error::SendRequestError;
use derive_more::{Display, Error}; use derive_more::{Display, Error};
use lazy_static::lazy_static;
use m_captcha::errors::CaptchaError; use m_captcha::errors::CaptchaError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::ParseError; use url::ParseError;

View File

@ -19,25 +19,92 @@ use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use sailfish::TemplateOnce; use sailfish::TemplateOnce;
//use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails; use crate::api::v1::mcaptcha::mcaptcha::MCaptchaDetails;
use crate::errors::*; use crate::errors::*;
use crate::Data; use crate::Data;
#[derive(TemplateOnce, Clone)] #[derive(TemplateOnce, Clone)]
#[template(path = "panel/site-keys/index.html")] #[template(path = "panel/site-keys/index.html")]
pub struct IndexPage; pub struct IndexPage {
sitekeys: SiteKeys,
}
const PAGE: &str = "SiteKeys"; const PAGE: &str = "SiteKeys";
impl Default for IndexPage { impl IndexPage {
fn default() -> Self { fn new(sitekeys: SiteKeys) -> Self {
IndexPage IndexPage { sitekeys }
} }
} }
pub async fn list_sitekeys(data: web::Data<Data>, id: Identity) -> PageResult<impl Responder> { pub async fn list_sitekeys(data: web::Data<Data>, id: Identity) -> PageResult<impl Responder> {
let body = IndexPage::default().render_once().unwrap(); let username = id.identity().unwrap();
let res = sqlx::query_as!(
MCaptchaDetails,
"SELECT key, name from mcaptcha_config WHERE
user_id = (SELECT ID FROM mcaptcha_users WHERE name = $1) ",
&username,
)
.fetch_all(&data.db)
.await?;
let body = IndexPage::new(res).render_once().unwrap();
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(body)) .body(body))
} }
type SiteKeys = Vec<MCaptchaDetails>;
#[cfg(test)]
mod test {
use actix_web::http::StatusCode;
use actix_web::test;
use actix_web::web::Bytes;
use crate::tests::*;
use crate::*;
#[actix_rt::test]
async fn list_sitekeys_work() {
const NAME: &str = "listsitekeyuser";
const PASSWORD: &str = "longpassworddomain";
const EMAIL: &str = "listsitekeyuser@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 mut app = get_app!(data).await;
let list_sitekey_resp = test::call_service(
&mut app,
test::TestRequest::get()
.uri(PAGES.panel.sitekey.list)
.cookie(cookies.clone())
.to_request(),
)
.await;
assert_eq!(list_sitekey_resp.status(), StatusCode::OK);
let body: Bytes = test::read_body(list_sitekey_resp).await;
let body = String::from_utf8(body.to_vec()).unwrap();
// Bytes::from(key.key)
// .iter()
// .for_each(|e| assert!(body.contains(e)));
//
// Bytes::from(key.name)
// .iter()
// .for_each(|e| assert!(body.contains(e)));
assert!(body.contains(&key.key));
assert!(body.contains(&key.name));
}
}

View File

@ -22,13 +22,15 @@ pub mod routes {
pub struct Sitekey { pub struct Sitekey {
pub list: &'static str, pub list: &'static str,
pub add: &'static str, pub add: &'static str,
pub view: &'static str,
} }
impl Sitekey { impl Sitekey {
pub const fn new() -> Self { pub const fn new() -> Self {
Sitekey { Sitekey {
list: "/sitekey", list: "/sitekey/list",
add: "/sitekey/add", add: "/sitekey/add",
view: "/sitekey/{key}/view",
} }
} }
} }

View File

@ -57,6 +57,7 @@ macro_rules! get_app {
)) ))
.configure(crate::api::v1::pow::services) .configure(crate::api::v1::pow::services)
.configure(crate::api::v1::services) .configure(crate::api::v1::services)
.configure(crate::pages::services)
.data($data.clone()), .data($data.clone()),
) )
}; };
@ -189,6 +190,7 @@ pub async fn add_levels_util(
let add_level = AddLevels { let add_level = AddLevels {
levels: levels.clone(), levels: levels.clone(),
duration: 30, duration: 30,
description: "dummy".into(),
}; };
// 1. add level // 1. add level

View File

@ -24,3 +24,4 @@ $secondary-backdrop: #2b2c30;
$light-grey: rgba(0, 0, 0, 0.125); $light-grey: rgba(0, 0, 0, 0.125);
$white: #fff; $white: #fff;
$form-content-width: 90%; $form-content-width: 90%;
$black-text: #000;

View File

@ -0,0 +1,40 @@
/*
* 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/>.
*/
@import '../vars';
@mixin box-title {
padding-left: 10px;
font-size: 1rem;
padding: 0.75rem 1.25rem;
box-sizing: border-box;
text-align: left;
width: 100%;
border-bottom: 0.1px solid $light-grey;
}
@mixin box {
display: flex;
flex-direction: column;
width: 90%;
justify-content: center;
align-items: center;
box-sizing: content-box;
background-color: $white;
margin: auto;
padding-bottom: 30px;
}

View File

@ -1,12 +1,9 @@
<. include!("../components/headers.html"); .> <. include!("../components/headers.html"); .>
<main> <div class="inner-container">
<div class="inner-container"> <div class="error-box">
<div class="error-box"> <h1 class="error-title"><.= title .></h1>
<h1 class="error-title"><.= title .></h1> <p class="error-message"><.= message .></p>
<p class="error-message"><.= message .></p>
</div>
</div> </div>
<!-- end of container --> </div>
</main> <!-- end of container -->
<. include!("../components/footers.html"); .> <. include!("../components/footers.html"); .>

View File

@ -17,27 +17,14 @@
@import '../reset'; @import '../reset';
@import '../vars'; @import '../vars';
@import '../components/box';
.error-box { .error-box {
display: flex; @include box;
flex-direction: column;
width: 90%;
justify-content: center;
align-items: center;
box-sizing: content-box;
background-color: $white;
margin: auto;
padding-bottom: 30px;
} }
.error-title { .error-title {
padding-left: 10px; @include box-title;
font-size: 1rem;
padding: 0.75rem 1.25rem;
box-sizing: border-box;
text-align: left;
width: 100%;
border-bottom: 0.1px solid $light-grey;
} }
.error-message { .error-message {

View File

@ -24,14 +24,14 @@ import * as addSiteKey from './panel/add-site-key/';
import VIEWS from './views/v1/routes'; import VIEWS from './views/v1/routes';
import './auth/forms.scss'; import './auth/forms.scss';
import './panel/main.scss'; import './panel/main.scss';
import './panel/header/sidebar/main.scss'; import './panel/header/sidebar/main.scss';
import './panel/taskbar/main.scss'; import './panel/taskbar/main.scss';
import './panel/help-banner/main.scss'; import './panel/help-banner/main.scss';
import './panel/add-site-key/main.scss'; import './panel/add-site-key/main.scss';
import './panel/site-keys/main.scss';
import './errors/main.scss';
const router = new Router(); const router = new Router();

View File

@ -53,6 +53,7 @@ const validateDescription = (e: Event) => {
const val = inputElement.value; const val = inputElement.value;
const filed = 'Description'; const filed = 'Description';
isBlankString(val, filed, e); isBlankString(val, filed, e);
return val;
}; };
const validateDuration = (e: Event) => { const validateDuration = (e: Event) => {
@ -71,7 +72,7 @@ const validateDuration = (e: Event) => {
const submit = async (e: Event) => { const submit = async (e: Event) => {
e.preventDefault(); e.preventDefault();
validateDescription(e); const description = validateDescription(e);
const duration = validateDuration(e); const duration = validateDuration(e);
const formUrl = getFormUrl(FORM); const formUrl = getFormUrl(FORM);
@ -82,6 +83,7 @@ const submit = async (e: Event) => {
const payload = { const payload = {
levels: levels, levels: levels,
duration, duration,
description,
}; };
console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`); console.debug(`[form submition] json payload: ${JSON.stringify(payload)}`);

View File

@ -1,5 +1,5 @@
<. include!("../../components/headers.html"); .> <. include!("../header/index.html"); <. include!("../../components/headers.html"); .> <.
.> include!("../header/index.html"); .>
<main> <main>
<. include!("../taskbar/index.html"); .> <. <. include!("../taskbar/index.html"); .> <.
@ -7,7 +7,23 @@
<!-- Main content container --> <!-- Main content container -->
<div class="inner-container"> <div class="inner-container">
<!-- Main menu/ important actions roaster --> <!-- Main menu/ important actions roaster -->
<ul class="sitekey-list__box">
<h1 class="sitekey-list__title">Your Sitekeys</h1>
<. for sitekey in sitekeys.iter() { .>
<a href="/sitekey/<.= sitekey.key .>/view" class="sitekey-list__item-container">
<li class="sitekey-list__item">
<span class="sitekey-list__name">
<.= sitekey.name .>
</span>
<span class="sitekey-list__key">
<.= sitekey.key .>
</span>
</li>
</a>
<. } .>
</ul>
</div> </div>
<!-- end of container --> <!-- end of container -->
</main> </main>

View File

@ -0,0 +1,55 @@
/*
* 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/>.
*/
@import '../../reset';
@import '../../vars';
@import '../../components/box';
.sitekey-list__box {
@include box;
padding-bottom: 0px;
}
.sitekey-list__title {
@include box-title;
}
.sitekey-list__item-container {
display: block;
width: 100%;
box-sizing: border-box;
border-bottom: 0.1px solid $light-grey;
padding: 20px;
color: $black-text;
}
.sitekey-list__item {
display: flex;
width: 100%;
}
.sitekey-list__item-container:hover {
background-color: $light-grey;
}
.sitekey-list__name {
flex: 3;
}
.sitekey-list__key {
flex: 1;
}