ezidam: admin settings: added security page: logout everyone

This commit is contained in:
Philippe Loctaux 2023-03-26 23:36:36 +02:00
parent d3e88b1f7b
commit 8ab88ae4e1
5 changed files with 177 additions and 5 deletions

View file

@ -37,10 +37,16 @@ impl AdminMenu {
label: "Server settings",
link: uri!(routes::admin::settings::settings_branding).to_string(),
icon: icons::SETTINGS,
sub: Some(vec![SubItem {
sub: Some(vec![
SubItem {
label: "Branding",
link: uri!(routes::admin::settings::settings_branding).to_string(),
}]),
},
SubItem {
label: "Security",
link: uri!(routes::admin::settings::settings_security).to_string(),
},
]),
},
]
}

View file

@ -16,6 +16,7 @@ pub enum Page {
Redirect(Redirect),
AdminDashboard(AdminDashboard),
AdminSettingsBranding(AdminSettingsBranding),
AdminSettingsSecurity(AdminSettingsSecurity),
}
impl Page {
@ -29,6 +30,7 @@ impl Page {
Page::Redirect(_) => "pages/oauth/redirect",
Page::AdminDashboard(_) => "pages/admin/dashboard",
Page::AdminSettingsBranding(_) => "pages/admin/settings_branding",
Page::AdminSettingsSecurity(_) => "pages/admin/settings_security",
}
}
@ -42,6 +44,7 @@ impl Page {
Page::Redirect(_) => "Redirecting",
Page::AdminDashboard(_) => "Admin dashboard",
Page::AdminSettingsBranding(_) => "Server branding",
Page::AdminSettingsSecurity(_) => "Server security",
}
}
@ -57,6 +60,7 @@ impl Page {
Page::Redirect(_) => None,
Page::AdminDashboard(_) => Some(AdminMenu::Dashboard.into()),
Page::AdminSettingsBranding(_) => Some(AdminMenu::Settings.into()),
Page::AdminSettingsSecurity(_) => Some(AdminMenu::Settings.into()),
}
}
@ -70,6 +74,7 @@ impl Page {
Page::Redirect(redirect) => Box::new(redirect),
Page::AdminDashboard(dashboard) => Box::new(dashboard),
Page::AdminSettingsBranding(branding) => Box::new(branding),
Page::AdminSettingsSecurity(security) => Box::new(security),
}
}
}

View file

@ -6,7 +6,13 @@ pub mod dashboard;
pub mod settings;
pub fn routes() -> Vec<Route> {
routes![admin_dashboard, settings_branding, settings_update_branding]
routes![
admin_dashboard,
settings_branding,
settings_update_branding,
settings_security,
settings_security_form,
]
}
pub mod content {
@ -27,4 +33,11 @@ pub mod content {
pub user: JwtClaims,
pub business_name: String,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
#[derive(Clone)]
pub struct AdminSettingsSecurity {
pub user: JwtClaims,
}
}

View file

@ -1,7 +1,13 @@
use crate::routes::prelude::*;
use crate::tokens::{generate_jwt, generate_refresh_token};
use apps::App;
use refresh_tokens::RefreshToken;
use rocket::fs::TempFile;
use rocket::http::CookieJar;
use rocket::{get, post};
use settings::Settings;
use std::net::IpAddr;
use users::User;
#[get("/admin/settings/branding")]
pub async fn settings_branding(mut db: Connection<Database>, admin: JwtAdmin) -> Result<Page> {
@ -59,3 +65,85 @@ pub async fn settings_update_branding(
Ok(Redirect::to(uri!(settings_branding)))
}
#[get("/admin/settings/security")]
pub async fn settings_security(admin: JwtAdmin) -> Result<Page> {
Ok(Page::AdminSettingsSecurity(
super::content::AdminSettingsSecurity { user: admin.0 },
))
}
#[derive(Debug, FromForm)]
pub struct SecurityForm {
pub logout_everyone: Option<bool>,
}
#[post("/admin/settings/security", data = "<form>")]
pub async fn settings_security_form(
mut db: Connection<Database>,
admin: JwtAdmin,
form: Form<SecurityForm>,
ip_address: IpAddr,
cookie_jar: &CookieJar<'_>,
) -> Result<Redirect> {
let mut transaction = db.begin().await?;
if let Some(logout_everyone) = form.logout_everyone {
if logout_everyone {
// Generate key id
let key_id = task::spawn_blocking(id::KeyID::default).await?;
// Generate key
let key_id_for_generation = key_id.clone();
let (private_key, public_key) =
task::spawn_blocking(move || jwt::generate(&key_id_for_generation)).await??;
// Insert keys in database
jwt::database::save_new_keys(&mut transaction, &key_id, &private_key, &public_key)
.await?;
// Revoke all keys except new one
jwt::database::revoke_all_except_one(&mut transaction, &key_id).await?;
// Get app
let app = App::get_one_by_id(&mut transaction, "ezidam")
.await?
.ok_or_else(|| Error::not_found("Could not find application"))?;
// Get user info
let user = User::get_by_login(&mut transaction, &admin.0.subject)
.await?
.ok_or_else(|| Error::not_found("Could not find user"))?;
// Revoke all refresh tokens
RefreshToken::revoke_all(&mut transaction).await?;
// Generate refresh token
generate_refresh_token(&mut transaction, ip_address, user.id(), cookie_jar)
.await
.map_err(Error::internal_server_error)?;
// Get base url
let settings = Settings::get(&mut transaction).await?;
let home_page = settings
.url()
.map(String::from)
.ok_or_else(|| Error::bad_request("Server url is not set"))?;
// Generate jwt
generate_jwt(
&mut transaction,
&private_key,
&home_page,
&app.id().0,
&user,
cookie_jar,
)
.await
.map_err(Error::internal_server_error)?;
}
}
transaction.commit().await?;
Ok(Redirect::to(uri!(settings_security)))
}

View file

@ -0,0 +1,60 @@
{% extends "shell" %}
{% block content %}
<!-- Page header -->
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">
Server settings
</h2>
</div>
</div>
</div>
</div>
<!-- Page body -->
<div class="page-body">
<div class="container-xl">
<div class="card">
<div class="row g-0">
<div class="col-3 d-none d-md-block border-end">
<div class="card-body">
<div class="list-group list-group-transparent">
<a href="./branding"
class="list-group-item list-group-item-action d-flex align-items-center">Branding</a>
<a href="./security"
class="list-group-item list-group-item-action d-flex align-items-center active">Security</a>
</div>
</div>
</div>
<div class="col d-flex flex-column">
<div class="card-body">
<h2 class="mb-4">Security</h2>
<h3 class="card-title">Logout everyone</h3>
<p class="card-subtitle">
This will force all logged-in users to log in again.<br>
This action will become effective immediately.
</p>
<p>This might take some time, but should not be long.</p>
<div class="row align-items-center">
<!-- Logout everyone -->
<div class="col-auto">
<form action="" method="post">
<button class="btn btn-danger" type="submit" name="logout_everyone" value="true">
Logout everyone
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}