ezidam: apps: form, create new app

This commit is contained in:
Philippe Loctaux 2023-03-31 23:57:58 +02:00
parent d2e1792f3c
commit f911fc665c
6 changed files with 175 additions and 14 deletions

View file

@ -38,7 +38,7 @@ impl AdminMenu {
MainItem { MainItem {
id: AdminMenu::Apps.id(), id: AdminMenu::Apps.id(),
label: "Applications", label: "Applications",
link: uri!(routes::admin::apps::admin_apps).to_string(), link: uri!(routes::admin::apps::admin_apps_list).to_string(),
icon: Icon::Apps.svg, icon: Icon::Apps.svg,
sub: None, sub: None,
}, },

View file

@ -18,7 +18,8 @@ pub enum Page {
AdminDashboard(AdminDashboard), AdminDashboard(AdminDashboard),
AdminSettingsBranding(AdminSettingsBranding), AdminSettingsBranding(AdminSettingsBranding),
AdminSettingsSecurity(AdminSettingsSecurity), AdminSettingsSecurity(AdminSettingsSecurity),
AdminApps(AdminApps), AdminAppsList(AdminAppsList),
AdminAppsNew(AdminAppsNew),
} }
impl Page { impl Page {
@ -33,7 +34,8 @@ impl Page {
Page::AdminDashboard(_) => "pages/admin/dashboard", Page::AdminDashboard(_) => "pages/admin/dashboard",
Page::AdminSettingsBranding(_) => "pages/admin/settings/branding", Page::AdminSettingsBranding(_) => "pages/admin/settings/branding",
Page::AdminSettingsSecurity(_) => "pages/admin/settings/security", Page::AdminSettingsSecurity(_) => "pages/admin/settings/security",
Page::AdminApps(_) => "pages/admin/apps/list", Page::AdminAppsList(_) => "pages/admin/apps/list",
Page::AdminAppsNew(_) => "pages/admin/apps/new",
} }
} }
@ -48,7 +50,8 @@ impl Page {
Page::AdminDashboard(_) => "Admin dashboard", Page::AdminDashboard(_) => "Admin dashboard",
Page::AdminSettingsBranding(_) => "Server branding", Page::AdminSettingsBranding(_) => "Server branding",
Page::AdminSettingsSecurity(_) => "Server security", Page::AdminSettingsSecurity(_) => "Server security",
Page::AdminApps(_) => "Applications", Page::AdminAppsList(_) => "Applications",
Page::AdminAppsNew(_) => "New application",
} }
} }
@ -65,7 +68,8 @@ impl Page {
Page::AdminDashboard(_) => Some(AdminMenu::Dashboard.into()), Page::AdminDashboard(_) => Some(AdminMenu::Dashboard.into()),
Page::AdminSettingsBranding(_) => Some(AdminMenu::Settings.into()), Page::AdminSettingsBranding(_) => Some(AdminMenu::Settings.into()),
Page::AdminSettingsSecurity(_) => Some(AdminMenu::Settings.into()), Page::AdminSettingsSecurity(_) => Some(AdminMenu::Settings.into()),
Page::AdminApps(_) => Some(AdminMenu::Apps.into()), Page::AdminAppsList(_) => Some(AdminMenu::Apps.into()),
Page::AdminAppsNew(_) => Some(AdminMenu::Apps.into()),
} }
} }
@ -80,7 +84,8 @@ impl Page {
Page::AdminDashboard(dashboard) => Box::new(dashboard), Page::AdminDashboard(dashboard) => Box::new(dashboard),
Page::AdminSettingsBranding(branding) => Box::new(branding), Page::AdminSettingsBranding(branding) => Box::new(branding),
Page::AdminSettingsSecurity(security) => Box::new(security), Page::AdminSettingsSecurity(security) => Box::new(security),
Page::AdminApps(apps) => Box::new(apps), Page::AdminAppsList(list) => Box::new(list),
Page::AdminAppsNew(new) => Box::new(new),
} }
} }
} }

View file

@ -14,7 +14,9 @@ pub fn routes() -> Vec<Route> {
settings_update_branding, settings_update_branding,
settings_security, settings_security,
settings_security_form, settings_security_form,
admin_apps, admin_apps_list,
admin_apps_new,
admin_apps_new_form,
] ]
} }
@ -49,8 +51,15 @@ pub mod content {
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
#[derive(Clone)] #[derive(Clone)]
pub struct AdminApps { pub struct AdminAppsList {
pub user: JwtClaims, pub user: JwtClaims,
pub apps: Vec<App>, pub apps: Vec<App>,
} }
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
#[derive(Clone)]
pub struct AdminAppsNew {
pub user: JwtClaims,
}
} }

View file

@ -1,16 +1,75 @@
use crate::routes::prelude::*; use crate::routes::prelude::*;
use apps::App; use apps::App;
use rocket::get; use hash::{Secret, SecretString};
use rocket::{get, post};
use url::Url;
#[get("/admin/apps")] #[get("/admin/apps")]
pub async fn admin_apps(mut db: Connection<Database>, admin: JwtAdmin) -> Result<Page> { pub async fn admin_apps_list(
mut db: Connection<Database>,
admin: JwtAdmin,
flash: Option<FlashMessage<'_>>,
) -> Result<Template> {
let mut apps = App::get_all(&mut *db, None).await?; let mut apps = App::get_all(&mut *db, None).await?;
// Remove ezidam from list // Remove ezidam from list
apps.retain(|app| *app.id() != AppID("ezidam".into())); apps.retain(|app| *app.id() != AppID("ezidam".into()));
Ok(Page::AdminApps(super::content::AdminApps { let page = Page::AdminAppsList(super::content::AdminAppsList {
user: admin.0, user: admin.0,
apps, apps,
});
Ok(flash
.map(|flash| Page::with_flash(page.clone(), flash))
.unwrap_or_else(|| page.into()))
}
#[get("/admin/apps/new")]
pub async fn admin_apps_new(admin: JwtAdmin) -> Result<Page> {
Ok(Page::AdminAppsNew(super::content::AdminAppsNew {
user: admin.0,
})) }))
} }
#[derive(Debug, FromForm)]
pub struct NewApp<'r> {
pub label: &'r str,
pub redirect_uri: &'r str,
pub is_confidential: bool,
}
#[post("/admin/apps/new", data = "<form>")]
pub async fn admin_apps_new_form(
mut db: Connection<Database>,
_admin: JwtAdmin,
form: Form<NewApp<'_>>,
) -> Result<Flash<Redirect>> {
// Generate app id
let app_id = task::spawn_blocking(AppID::default).await?;
// Parse redirect uri
let redirect_uri = Url::parse(form.redirect_uri)?;
// Generate secret
let app_secret = task::spawn_blocking(SecretString::default).await?;
// Hash secret
let app_secret_hash = task::spawn_blocking(move || Secret::new(app_secret)).await??;
// Insert in database
App::insert(
&mut *db,
&app_id,
form.label,
&redirect_uri,
&app_secret_hash,
form.is_confidential,
)
.await?;
Ok(Flash::new(
Redirect::to(uri!(admin_apps_list)),
FlashKind::Success,
format!("Application \"{}\" has been created", form.label),
))
}

View file

@ -2,7 +2,7 @@
{% block content %} {% block content %}
{% set new_app_label = "New application" %} {% set new_app_label = "New application" %}
{% set new_app_link = "./new" %} {% set new_app_link = "apps/new" %}
<div class="page-header d-print-none"> <div class="page-header d-print-none">
<div class="container-xl"> <div class="container-xl">
<div class="row align-items-center"> <div class="row align-items-center">
@ -34,6 +34,14 @@
<!-- Page body --> <!-- Page body -->
<div class="page-body"> <div class="page-body">
<div class="container-xl"> <div class="container-xl">
{% if flash %}
<div class="alert alert-{{flash.0}}" role="alert">
<h4 class="alert-title">Success</h4>
<div class="text-muted">{{ flash.1 }}</div>
</div>
{% endif %}
{% if apps | length != 0 %} {% if apps | length != 0 %}
<div class="card"> <div class="card">
<div id="table-default" class="table-responsive"> <div id="table-default" class="table-responsive">

View file

@ -9,7 +9,7 @@
Admin dashboard Admin dashboard
</div> </div>
<h2 class="page-title"> <h2 class="page-title">
New application Applications
</h2> </h2>
</div> </div>
</div> </div>
@ -17,7 +17,87 @@
</div> </div>
<!-- Page body --> <!-- Page body -->
<div class="page-body"> <div class="page-body">
<div class="container-xl"></div> <div class="container-xl">
<form action="" method="post" autocomplete="off" class="card">
<div class="card-header">
<h3 class="card-title">New Application</h3>
</div>
<div class="card-body">
<!-- App name -->
<div class="mb-3">
<label class="form-label required" for="label">Application name</label>
<div>
<input name="label" id="label" type="text" placeholder="Enter name" class="form-control"
required>
<small class="form-hint">
The name of the application, it will be shown to users when authorizing access.
</small>
</div>
</div>
<!-- Redirect URI -->
<div class="mb-3">
<label class="form-label required" for="redirect_uri">Redirect URI</label>
<div>
<input name="redirect_uri" id="redirect_uri" type="url" placeholder="Enter redirect URI"
class="form-control" required>
<small class="form-hint">Make sure you have control over the provided Redirect URI.</small>
</div>
</div>
<!-- Confidential app -->
<div class="mb-3">
<label class="form-label required">Confidentiality</label>
<div class="form-selectgroup-boxes row mb-3">
<!-- Private apps -->
<div class="col-lg-6">
<label class="form-selectgroup-item">
<input name="is_confidential" type="radio" value="true" class="form-selectgroup-input"
required>
<span class="form-selectgroup-label d-flex align-items-center p-3">
<span class="me-3">
<span class="form-selectgroup-check"></span>
</span>
<span class="form-selectgroup-label-content">
<span class="form-selectgroup-title strong mb-1">Private application</span>
<span class="d-block text-muted">
For applications with a backend, where the application secret can be securely stored.
</span>
</span>
</span>
</label>
</div>
<!-- Public apps -->
<div class="col-lg-6">
<label class="form-selectgroup-item">
<input name="is_confidential" type="radio" value="false" class="form-selectgroup-input"
required>
<span class="form-selectgroup-label d-flex align-items-center p-3">
<span class="me-3">
<span class="form-selectgroup-check"></span>
</span>
<span class="form-selectgroup-label-content">
<span class="form-selectgroup-title strong mb-1">Public application</span>
<span class="d-block text-muted">
For public applications (PWAs, desktop, mobile). The application secret can not be securely stored.
</span>
</span>
</span>
</label>
</div>
</div>
</div>
</div>
<div class="card-footer text-end">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div> </div>
{% endblock content %} {% endblock content %}