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 {
id: AdminMenu::Apps.id(),
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,
sub: None,
},

View file

@ -18,7 +18,8 @@ pub enum Page {
AdminDashboard(AdminDashboard),
AdminSettingsBranding(AdminSettingsBranding),
AdminSettingsSecurity(AdminSettingsSecurity),
AdminApps(AdminApps),
AdminAppsList(AdminAppsList),
AdminAppsNew(AdminAppsNew),
}
impl Page {
@ -33,7 +34,8 @@ impl Page {
Page::AdminDashboard(_) => "pages/admin/dashboard",
Page::AdminSettingsBranding(_) => "pages/admin/settings/branding",
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::AdminSettingsBranding(_) => "Server branding",
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::AdminSettingsBranding(_) => 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::AdminSettingsBranding(branding) => Box::new(branding),
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_security,
settings_security_form,
admin_apps,
admin_apps_list,
admin_apps_new,
admin_apps_new_form,
]
}
@ -49,8 +51,15 @@ pub mod content {
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
#[derive(Clone)]
pub struct AdminApps {
pub struct AdminAppsList {
pub user: JwtClaims,
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 apps::App;
use rocket::get;
use hash::{Secret, SecretString};
use rocket::{get, post};
use url::Url;
#[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?;
// Remove ezidam from list
apps.retain(|app| *app.id() != AppID("ezidam".into()));
Ok(Page::AdminApps(super::content::AdminApps {
let page = Page::AdminAppsList(super::content::AdminAppsList {
user: admin.0,
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 %}
{% 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="container-xl">
<div class="row align-items-center">
@ -34,6 +34,14 @@
<!-- Page body -->
<div class="page-body">
<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 %}
<div class="card">
<div id="table-default" class="table-responsive">

View file

@ -9,7 +9,7 @@
Admin dashboard
</div>
<h2 class="page-title">
New application
Applications
</h2>
</div>
</div>
@ -17,7 +17,87 @@
</div>
<!-- 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>
{% endblock content %}