delete captcha option and sudo page
This commit is contained in:
parent
00768cce34
commit
db941d51b7
17 changed files with 320 additions and 34 deletions
|
@ -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
85
src/pages/auth/sudo.rs
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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
1
static/cache/img/svg/trash.svg
vendored
Normal 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 |
|
@ -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,
|
||||
|
|
31
templates/auth/sudo/getForm.test.ts
Normal file
31
templates/auth/sudo/getForm.test.ts
Normal 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);
|
||||
});
|
|
@ -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"); .>
|
34
templates/auth/sudo/index.ts
Normal file
34
templates/auth/sudo/index.ts
Normal 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;
|
|
@ -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);
|
||||
});
|
3
templates/components/additional-data/index.html
Normal file
3
templates/components/additional-data/index.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<. if data.is_some() && !data.as_ref().unwrap().is_empty() { .>
|
||||
<div id="additional-data" <.= data.unwrap() .>></div>
|
||||
<. } .>
|
36
templates/components/additional-data/index.ts
Normal file
36
templates/components/additional-data/index.ts
Normal 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;
|
|
@ -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%);
|
||||
}
|
||||
|
|
|
@ -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 .>"
|
||||
/>
|
||||
|
|
|
@ -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();
|
||||
|
|
51
templates/panel/sitekey/delete/index.ts
Normal file
51
templates/panel/sitekey/delete/index.ts
Normal 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);
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue