delete captcha option and sudo page

This commit is contained in:
realaravinth 2021-07-20 15:22:15 +05:30
parent 00768cce34
commit db941d51b7
No known key found for this signature in database
GPG key ID: AD9F0F08E855ED88
17 changed files with 320 additions and 34 deletions

View file

@ -17,6 +17,7 @@
pub mod login;
pub mod register;
pub mod sudo;
pub fn services(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(login::login);

85
src/pages/auth/sudo.rs Normal file
View file

@ -0,0 +1,85 @@
/*
* 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::fmt::Display;
use sailfish::TemplateOnce;
#[derive(Clone, TemplateOnce)]
#[template(path = "auth/sudo/index.html")]
pub struct SudoPage<'a> {
url: &'a str,
data: Option<String>,
}
pub const PAGE: &str = "Confirm Access";
impl<'a> SudoPage<'a> {
//pub fn new(url: &'a str, data: Option<Vec<(&'a str, &'a str)>>) -> Self {
pub fn new<K, V>(url: &'a str, data: Option<Vec<(K, V)>>) -> Self
where
K: Display,
V: Display,
{
let data = if let Some(data) = data {
if !data.is_empty() {
let mut s = String::new();
for (k, v) in data.iter() {
s.push_str(&format!(" data-{}={}", k, v));
}
Some(s)
} else {
None
}
} else {
None
};
Self { url, data }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sudo_page_works() {
let data = vec![
("firefox", "mozilla"),
("chrome", "google"),
("servo", "mozilla"),
];
assert!(SudoPage::new::<String, String>("foo", None).data.is_none());
let sudo = SudoPage::new("foo", Some(data.clone()));
data.iter().for_each(|(k, v)| {
assert!(
sudo.data.as_ref().unwrap().contains(k)
&& sudo.data.as_ref().unwrap().contains(v)
)
});
let data_str = " data-firefox=mozilla data-chrome=google data-servo=mozilla";
assert_eq!(sudo.data.as_ref().unwrap(), data_str);
assert!(SudoPage::new::<String, String>("foo", Some(Vec::default()))
.data
.is_none());
}
}

View file

@ -15,31 +15,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use actix_web::{HttpResponse, Responder};
use lazy_static::lazy_static;
use actix_web::{web, HttpResponse, Responder};
use my_codegen::get;
use sailfish::TemplateOnce;
use crate::PAGES;
#[derive(Clone, TemplateOnce)]
#[template(path = "panel/sitekey/delete/index.html")]
struct IndexPage;
const PAGE: &str = "Confirm Access";
impl Default for IndexPage {
fn default() -> Self {
IndexPage
}
}
lazy_static! {
static ref INDEX: String = IndexPage::default().render_once().unwrap();
}
use crate::pages::auth::sudo::SudoPage;
use crate::{PAGES, V1_API_ROUTES};
#[get(path = "PAGES.panel.sitekey.delete", wrap = "crate::CheckLogin")]
pub async fn delete_sitekey() -> impl Responder {
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))
.render_once()
.unwrap();
HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(&*INDEX)
.body(&page)
}

1
static/cache/img/svg/trash.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-trash-2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 448 B

View file

@ -25,6 +25,16 @@ import createError from '../../../components/error/index';
//import '../forms.scss';
export const getPassword = () => {
const passwordElement = <HTMLInputElement>document.getElementById('password');
if (passwordElement === null) {
console.debug('Password is null');
return;
}
return passwordElement.value;
};
const login = async (e: Event) => {
e.preventDefault();
const loginElement = <HTMLInputElement>document.getElementById('login');
@ -36,13 +46,7 @@ const login = async (e: Event) => {
const login = loginElement.value;
isBlankString(login, 'username', e);
const passwordElement = <HTMLInputElement>document.getElementById('password');
if (passwordElement === null) {
console.debug('Password is null');
return;
}
const password = passwordElement.value;
const password = getPassword();
const payload = {
login,

View file

@ -0,0 +1,31 @@
/*
* 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 form from './index';
it('sudo form works', () => {
try {
form();
} catch (e) {
expect(e.message).toBe("Couldn't form element, is the component loaded?");
}
const element = document.createElement('form');
element.id = 'form';
document.body.appendChild(element);
expect(form()).toBe(element);
});

View file

@ -1,12 +1,12 @@
<. include!("../../../components/headers/index.html"); .>
<. include!("../../components/headers/index.html"); .>
<div class="tmp-layout">
<main class="auth-main">
<div class="auth-inner-container">
<. include!("../../../auth/logo.html"); .>
<. include!("../logo.html"); .>
<form
class="sitekey-form"
method="POST"
action="<.= crate::V1_API_ROUTES.auth.login .>"
action="<.= url .>"
id="form"
>
<h1 class="form__title">
@ -21,9 +21,10 @@
id="password"
required
/>
<. include!("../../../components/showPassword/index.html"); .>
<. include!("../../components/showPassword/index.html"); .>
</label>
<input type="submit" class="sitekey-form__submit" value="Confirm access" />
</form>
</div>
<. include!("../../../components/footers.html"); .>
<. include!("../../components/additional-data/index.html"); .>
<. include!("../../components/footers.html"); .>

View file

@ -0,0 +1,34 @@
/*
* 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/>.
*/
const form = () => {
let element = null;
const ID = 'form';
if (element === null) {
element = <HTMLFormElement>document.getElementById(ID);
if (element === undefined) {
throw new Error("Couldn't form element, is the component loaded?");
} else {
return element;
}
} else {
element;
}
};
export default form;

View file

@ -0,0 +1,33 @@
/*
* 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 additionalData from './index';
it('sudo form works', () => {
try {
additionalData();
} catch (e) {
expect(e.message).toBe(
"Couldn't retrieve additional data element, is the component loaded?",
);
}
const element = document.createElement('div');
element.id = 'additional-data';
document.body.appendChild(element);
expect(additionalData()).toBe(element);
});

View file

@ -0,0 +1,3 @@
<. if data.is_some() && !data.as_ref().unwrap().is_empty() { .>
<div id="additional-data" <.= data.unwrap() .>></div>
<. } .>

View file

@ -0,0 +1,36 @@
/*
* 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/>.
*/
const additionalData = () => {
let element = null;
const ID = 'additional-data';
if (element === null) {
element = <HTMLElement>document.getElementById(ID);
if (element === undefined) {
throw new Error(
"Couldn't retrieve additional data element, is the component loaded?",
);
} else {
return element;
}
} else {
element;
}
};
export default additionalData;

View file

@ -50,3 +50,8 @@ $footer-font-size: 14px;
.details__link {
color: $blue-link;
}
.sitekey-form__delete {
filter: invert(12%) sepia(70%) saturate(6818%) hue-rotate(341deg)
brightness(82%) contrast(111%);
}

View file

@ -10,7 +10,7 @@
<link
rel="stylesheet"
media="screen and (max-width: 768px)"
media="screen and (max-width: 961px)"
type="text/css"
href="<.= &*crate::MOBILE_CSS .>"
/>

View file

@ -22,6 +22,7 @@ import * as register from './auth/register/ts/';
import * as panel from './panel/ts/index';
import * as addSiteKey from './panel/sitekey/add/ts';
import * as editSitekey from './panel/sitekey/edit/';
import * as deleteSitekey from './panel/sitekey/delete/';
import * as listSitekeys from './panel/sitekey/list/ts';
import * as notidications from './panel/notifications/ts';
import {MODE} from './logger';
@ -55,6 +56,7 @@ router.register(VIEWS.notifications, notidications.index);
router.register(VIEWS.listSitekey, listSitekeys.index);
router.register(VIEWS.addSiteKey, addSiteKey.index);
router.register(VIEWS.editSitekey('[A-Z),a-z,0-9]+'), editSitekey.index);
router.register(VIEWS.deleteSitekey('[A-Z),a-z,0-9]+'), deleteSitekey.index);
try {
router.route();

View file

@ -0,0 +1,51 @@
/*
* 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 {getPassword} from '../../../auth/login/ts/';
import form from '../../../auth/sudo/';
import additionalData from '../../../components/additional-data';
import getFormUrl from '../../../utils/getFormUrl';
import genJsonPayload from '../../../utils/genJsonPayload';
import createError from '../../../components/error';
import VIEWS from '../../../views/v1/routes';
const submit = async (e: Event) => {
e.preventDefault();
const password = getPassword();
const key = additionalData().dataset.sitekey;
const payload = {
password,
key,
};
const formUrl = getFormUrl(form());
const res = await fetch(formUrl, genJsonPayload(payload));
if (res.ok) {
window.location.assign(VIEWS.listSitekey);
} else {
const err = await res.json();
createError(err.error);
}
};
export const index = () => {
form().addEventListener('submit', submit, true);
};

View file

@ -21,8 +21,16 @@
</a>
<. if READONLY { .>
<. let key = "."; .>
<. include!("./__edit-sitekey-icon.html"); .>
<. include!("./__edit-sitekey-icon.html"); .>
<a href="./delete/">
<. } else { .>
<a href="../delete/">
<. } .>
<img class="sitekey-form__delete"
src="<.= crate::FILES.get("./static/cache/img/svg/trash.svg").unwrap() .>"
alt="Delete sitekey"
/>
</a>
</h1>
<label class="sitekey-form__label" for="description">
Description

View file

@ -25,6 +25,7 @@ const ROUTES = {
listSitekey: '/sitekeys/',
viewSitekey: (key: string) => `/sitekey/${key}/`,
editSitekey: (key: string) => `/sitekey/${key}/edit/`,
deleteSitekey: (key: string) => `/sitekey/${key}/delete/`,
addSiteKey: '/sitekeys/add',
};