ezidam: apps: view, update, new secret
This commit is contained in:
parent
4b99905ee0
commit
2caf584cb7
12 changed files with 407 additions and 9 deletions
|
|
@ -78,4 +78,29 @@ impl App {
|
||||||
.await?
|
.await?
|
||||||
.map(Self::from))
|
.map(Self::from))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
conn: impl SqliteExecutor<'_>,
|
||||||
|
id: &AppID,
|
||||||
|
label: &str,
|
||||||
|
redirect_uri: &Url,
|
||||||
|
is_confidential: bool,
|
||||||
|
) -> Result<Option<()>, Error> {
|
||||||
|
Ok(DatabaseApps::update(
|
||||||
|
conn,
|
||||||
|
id.as_ref(),
|
||||||
|
label,
|
||||||
|
redirect_uri.as_str(),
|
||||||
|
is_confidential,
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_secret(
|
||||||
|
&self,
|
||||||
|
conn: impl SqliteExecutor<'_>,
|
||||||
|
secret: &Secret,
|
||||||
|
) -> Result<Option<()>, Error> {
|
||||||
|
Ok(DatabaseApps::new_secret(conn, self.id.as_ref(), secret.hash()).await?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
crates/database/queries/apps/new_secret.sql
Normal file
5
crates/database/queries/apps/new_secret.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
update apps
|
||||||
|
|
||||||
|
set secret = ?
|
||||||
|
|
||||||
|
where id is ?
|
||||||
7
crates/database/queries/apps/update.sql
Normal file
7
crates/database/queries/apps/update.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
update apps
|
||||||
|
|
||||||
|
set label = ?,
|
||||||
|
redirect_uri = ?,
|
||||||
|
is_confidential = ?
|
||||||
|
|
||||||
|
where id is ?
|
||||||
|
|
@ -80,6 +80,26 @@
|
||||||
},
|
},
|
||||||
"query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n label,\n redirect_uri,\n secret,\n is_confidential as \"is_confidential: bool\",\n is_archived as \"is_archived: bool\"\nfrom apps\n\nwhere is_archived is 0\norder by created_at desc"
|
"query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n label,\n redirect_uri,\n secret,\n is_confidential as \"is_confidential: bool\",\n is_archived as \"is_archived: bool\"\nfrom apps\n\nwhere is_archived is 0\norder by created_at desc"
|
||||||
},
|
},
|
||||||
|
"184d704e75f00513082dd2c6cc3ae5c3f58b57b222ba4333216b5c50c3c58c71": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "update apps\n\nset label = ?,\n redirect_uri = ?,\n is_confidential = ?\n\nwhere id is ?"
|
||||||
|
},
|
||||||
|
"1e2edc8cf28832344dbfa0878ac01361b6f97c552d6af8477da12cddb03d4865": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "update apps\n\nset secret = ?\n\nwhere id is ?"
|
||||||
|
},
|
||||||
"3c8e31ffa5cbfd4dded8a272777cb320fb51fd2e53ed25054d24e9801df0c358": {
|
"3c8e31ffa5cbfd4dded8a272777cb320fb51fd2e53ed25054d24e9801df0c358": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
|
|
|
||||||
|
|
@ -102,4 +102,38 @@ impl Apps {
|
||||||
.await
|
.await
|
||||||
.map_err(handle_error)
|
.map_err(handle_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
conn: impl SqliteExecutor<'_>,
|
||||||
|
id: &str,
|
||||||
|
label: &str,
|
||||||
|
redirect_uri: &str,
|
||||||
|
is_confidential: bool,
|
||||||
|
) -> Result<Option<()>, Error> {
|
||||||
|
let query: SqliteQueryResult = sqlx::query_file!(
|
||||||
|
"queries/apps/update.sql",
|
||||||
|
label,
|
||||||
|
redirect_uri,
|
||||||
|
is_confidential,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(conn)
|
||||||
|
.await
|
||||||
|
.map_err(handle_error)?;
|
||||||
|
|
||||||
|
Ok((query.rows_affected() == 1).then_some(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_secret(
|
||||||
|
conn: impl SqliteExecutor<'_>,
|
||||||
|
id: &str,
|
||||||
|
secret: &str,
|
||||||
|
) -> Result<Option<()>, Error> {
|
||||||
|
let query: SqliteQueryResult = sqlx::query_file!("queries/apps/new_secret.sql", secret, id)
|
||||||
|
.execute(conn)
|
||||||
|
.await
|
||||||
|
.map_err(handle_error)?;
|
||||||
|
|
||||||
|
Ok((query.rows_affected() == 1).then_some(()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
use id::UserID;
|
use id::{AppID, Error, UserID};
|
||||||
|
use rocket::http::impl_from_uri_param_identity;
|
||||||
|
use rocket::http::uri::fmt::{Formatter, Path, UriDisplay};
|
||||||
use rocket::request::FromParam;
|
use rocket::request::FromParam;
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
|
@ -8,9 +11,32 @@ use std::str::FromStr;
|
||||||
pub struct RocketUserID(pub UserID);
|
pub struct RocketUserID(pub UserID);
|
||||||
|
|
||||||
impl<'r> FromParam<'r> for RocketUserID {
|
impl<'r> FromParam<'r> for RocketUserID {
|
||||||
type Error = id::Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||||
UserID::from_str(param).map(RocketUserID)
|
UserID::from_str(param).map(RocketUserID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct RocketAppID(pub AppID);
|
||||||
|
|
||||||
|
impl<'r> FromParam<'r> for RocketAppID {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||||
|
if param == "ezidam" {
|
||||||
|
return Err(Error::Invalid("App"));
|
||||||
|
}
|
||||||
|
AppID::from_str(param).map(RocketAppID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UriDisplay<Path> for RocketAppID {
|
||||||
|
fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result {
|
||||||
|
UriDisplay::fmt(&self.0 .0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_from_uri_param_identity!([Path] RocketAppID);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ pub enum Page {
|
||||||
AdminSettingsSecurity(AdminSettingsSecurity),
|
AdminSettingsSecurity(AdminSettingsSecurity),
|
||||||
AdminAppsList(AdminAppsList),
|
AdminAppsList(AdminAppsList),
|
||||||
AdminAppsNew(AdminAppsNew),
|
AdminAppsNew(AdminAppsNew),
|
||||||
|
AdminAppsView(AdminAppsView),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
|
|
@ -36,6 +37,7 @@ impl Page {
|
||||||
Page::AdminSettingsSecurity(_) => "pages/admin/settings/security",
|
Page::AdminSettingsSecurity(_) => "pages/admin/settings/security",
|
||||||
Page::AdminAppsList(_) => "pages/admin/apps/list",
|
Page::AdminAppsList(_) => "pages/admin/apps/list",
|
||||||
Page::AdminAppsNew(_) => "pages/admin/apps/new",
|
Page::AdminAppsNew(_) => "pages/admin/apps/new",
|
||||||
|
Page::AdminAppsView(_) => "pages/admin/apps/view",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,6 +54,7 @@ impl Page {
|
||||||
Page::AdminSettingsSecurity(_) => "Server security",
|
Page::AdminSettingsSecurity(_) => "Server security",
|
||||||
Page::AdminAppsList(_) => "Applications",
|
Page::AdminAppsList(_) => "Applications",
|
||||||
Page::AdminAppsNew(_) => "New application",
|
Page::AdminAppsNew(_) => "New application",
|
||||||
|
Page::AdminAppsView(_) => "Application info",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,6 +73,7 @@ impl Page {
|
||||||
Page::AdminSettingsSecurity(_) => Some(AdminMenu::Settings.into()),
|
Page::AdminSettingsSecurity(_) => Some(AdminMenu::Settings.into()),
|
||||||
Page::AdminAppsList(_) => Some(AdminMenu::Apps.into()),
|
Page::AdminAppsList(_) => Some(AdminMenu::Apps.into()),
|
||||||
Page::AdminAppsNew(_) => Some(AdminMenu::Apps.into()),
|
Page::AdminAppsNew(_) => Some(AdminMenu::Apps.into()),
|
||||||
|
Page::AdminAppsView(_) => Some(AdminMenu::Apps.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,6 +90,7 @@ impl Page {
|
||||||
Page::AdminSettingsSecurity(security) => Box::new(security),
|
Page::AdminSettingsSecurity(security) => Box::new(security),
|
||||||
Page::AdminAppsList(list) => Box::new(list),
|
Page::AdminAppsList(list) => Box::new(list),
|
||||||
Page::AdminAppsNew(new) => Box::new(new),
|
Page::AdminAppsNew(new) => Box::new(new),
|
||||||
|
Page::AdminAppsView(view) => Box::new(view),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ pub fn routes() -> Vec<Route> {
|
||||||
admin_apps_list,
|
admin_apps_list,
|
||||||
admin_apps_new,
|
admin_apps_new,
|
||||||
admin_apps_new_form,
|
admin_apps_new_form,
|
||||||
|
admin_apps_view,
|
||||||
|
admin_apps_view_form,
|
||||||
|
admin_apps_new_secret,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,4 +65,12 @@ pub mod content {
|
||||||
pub struct AdminAppsNew {
|
pub struct AdminAppsNew {
|
||||||
pub user: JwtClaims,
|
pub user: JwtClaims,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AdminAppsView {
|
||||||
|
pub user: JwtClaims,
|
||||||
|
pub app: App,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ pub async fn admin_apps_new(admin: JwtAdmin) -> Result<Page> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, FromForm)]
|
#[derive(Debug, FromForm)]
|
||||||
pub struct NewApp<'r> {
|
pub struct AppForm<'r> {
|
||||||
pub label: &'r str,
|
pub label: &'r str,
|
||||||
pub redirect_uri: &'r str,
|
pub redirect_uri: &'r str,
|
||||||
pub is_confidential: bool,
|
pub is_confidential: bool,
|
||||||
|
|
@ -43,7 +43,7 @@ pub struct NewApp<'r> {
|
||||||
pub async fn admin_apps_new_form(
|
pub async fn admin_apps_new_form(
|
||||||
mut db: Connection<Database>,
|
mut db: Connection<Database>,
|
||||||
_admin: JwtAdmin,
|
_admin: JwtAdmin,
|
||||||
form: Form<NewApp<'_>>,
|
form: Form<AppForm<'_>>,
|
||||||
) -> Result<Flash<Redirect>> {
|
) -> Result<Flash<Redirect>> {
|
||||||
// Generate app id
|
// Generate app id
|
||||||
let app_id = task::spawn_blocking(AppID::default).await?;
|
let app_id = task::spawn_blocking(AppID::default).await?;
|
||||||
|
|
@ -73,3 +73,96 @@ pub async fn admin_apps_new_form(
|
||||||
format!("Application \"{}\" has been created", form.label),
|
format!("Application \"{}\" has been created", form.label),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/admin/apps/<id>")]
|
||||||
|
pub async fn admin_apps_view(
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
id: RocketAppID,
|
||||||
|
admin: JwtAdmin,
|
||||||
|
flash: Option<FlashMessage<'_>>,
|
||||||
|
) -> Result<Template> {
|
||||||
|
let app_id = id.0;
|
||||||
|
let app = App::get_one_by_id(&mut *db, app_id.as_ref())
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| Error::not_found(app_id.to_string()))?;
|
||||||
|
|
||||||
|
let page = Page::AdminAppsView(super::content::AdminAppsView { user: admin.0, app });
|
||||||
|
|
||||||
|
Ok(flash
|
||||||
|
.map(|flash| Page::with_flash(page.clone(), flash))
|
||||||
|
.unwrap_or_else(|| page.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/admin/apps/<id>", data = "<form>")]
|
||||||
|
pub async fn admin_apps_view_form(
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
id: RocketAppID,
|
||||||
|
_admin: JwtAdmin,
|
||||||
|
form: Form<AppForm<'_>>,
|
||||||
|
) -> Result<Flash<Redirect>> {
|
||||||
|
// Parse redirect uri
|
||||||
|
let redirect_uri = Url::parse(form.redirect_uri)?;
|
||||||
|
|
||||||
|
// Update app
|
||||||
|
App::update(
|
||||||
|
&mut *db,
|
||||||
|
&id.0,
|
||||||
|
form.label,
|
||||||
|
&redirect_uri,
|
||||||
|
form.is_confidential,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Flash::new(
|
||||||
|
Redirect::to(uri!(admin_apps_list)),
|
||||||
|
FlashKind::Success,
|
||||||
|
format!("Application \"{}\" has been updated", form.label),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct NewSecretForm {
|
||||||
|
pub regenerate_secret: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/admin/apps/<id>/secret", data = "<form>")]
|
||||||
|
pub async fn admin_apps_new_secret(
|
||||||
|
mut db: Connection<Database>,
|
||||||
|
id: RocketAppID,
|
||||||
|
_admin: JwtAdmin,
|
||||||
|
form: Form<NewSecretForm>,
|
||||||
|
) -> Result<Flash<Redirect>> {
|
||||||
|
if !form.regenerate_secret {
|
||||||
|
return Err(Error::bad_request("Not generating a new secret"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut transaction = db.begin().await?;
|
||||||
|
|
||||||
|
// Get app
|
||||||
|
let app_id = &id.0;
|
||||||
|
let app = App::get_one_by_id(&mut transaction, app_id.as_ref())
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| Error::not_found(app_id.to_string()))?;
|
||||||
|
|
||||||
|
// Generate secret
|
||||||
|
let app_secret = task::spawn_blocking(SecretString::default).await?;
|
||||||
|
let app_secret_string = app_secret.to_string();
|
||||||
|
|
||||||
|
// Hash secret
|
||||||
|
let app_secret_hash = task::spawn_blocking(move || Secret::new(app_secret)).await??;
|
||||||
|
|
||||||
|
// Save new secret
|
||||||
|
app.new_secret(&mut transaction, &app_secret_hash).await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(Flash::new(
|
||||||
|
Redirect::to(uri!(admin_apps_view(id))),
|
||||||
|
FlashKind::Success,
|
||||||
|
format!(
|
||||||
|
"Secret for application \"{}\" has been updated. It will only be shown once!\
|
||||||
|
<div class=\"mt-1 user-select-all\">{app_secret_string}</div>",
|
||||||
|
app.label(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,9 @@
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="table-tbody">
|
|
||||||
|
|
||||||
<!-- Table content -->
|
<!-- Table content -->
|
||||||
|
<tbody class="table-tbody">
|
||||||
{% for app in apps %}
|
{% for app in apps %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="sort-name">{{ app.label }}</td>
|
<td class="sort-name">{{ app.label }}</td>
|
||||||
|
|
@ -81,18 +81,18 @@
|
||||||
<span class="badge bg-success me-1"></span> Active
|
<span class="badge bg-success me-1"></span> Active
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="sort-creation-date" data-date="{{ app.created_at | date(timestamp=true) }}">
|
<td class="sort-creation-date" data-date='{{ app.created_at | date(format="%s") }}'>
|
||||||
{{ app.created_at | date() }}
|
{{ app.created_at | date() }}
|
||||||
</td>
|
</td>
|
||||||
<td class="sort-id">{{ app.id }}</td>
|
<td class="sort-id">{{ app.id }}</td>
|
||||||
<td class="sort-redirect-uri">{{ app.redirect_uri }}</td>
|
<td class="sort-redirect-uri">{{ app.redirect_uri }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#">Details</a>
|
<a href="apps/{{ app.id }}">Details</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
sortClass: 'table-sort',
|
sortClass: 'table-sort',
|
||||||
listClass: 'table-tbody',
|
listClass: 'table-tbody',
|
||||||
valueNames: ['sort-name', 'sort-status',
|
valueNames: ['sort-name', 'sort-status',
|
||||||
{attr: 'data-date', name: 'sort-date'},
|
{attr: 'data-date', name: 'sort-creation-date'},
|
||||||
'sort-id', 'sort-redirect-uri',
|
'sort-id', 'sort-redirect-uri',
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
||||||
166
crates/ezidam/templates/pages/admin/apps/view.html.tera
Normal file
166
crates/ezidam/templates/pages/admin/apps/view.html.tera
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
{% extends "shell" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="page-header d-print-none">
|
||||||
|
<div class="container-xl">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="page-pretitle">
|
||||||
|
Admin dashboard
|
||||||
|
</div>
|
||||||
|
<h2 class="page-title">
|
||||||
|
Applications
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Page body -->
|
||||||
|
<div class="page-body">
|
||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
{% if flash %}
|
||||||
|
<div class="alert alert-{{flash.0}}" role="alert">
|
||||||
|
<h4 class="alert-title">{{ flash.1 | safe }}</h4>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Application IDs</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<!-- App id -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">App ID</label>
|
||||||
|
<input type="text" value="{{ app.id }}" class="form-control" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- App secret -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">App secret</label>
|
||||||
|
<input type="password" class="form-control" value="super_secure_useless_app_secret" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Regenerate secret -->
|
||||||
|
<div>
|
||||||
|
<form action="{{ app.id }}/secret" method="post">
|
||||||
|
<div class="mb-2">Lost your secret?</div>
|
||||||
|
<button type="submit" name="regenerate_secret" value="true" class="btn btn-danger">
|
||||||
|
Regenerate secret
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Edit Application</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="" method="post">
|
||||||
|
<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" value="{{ app.label }}" placeholder="Enter name"
|
||||||
|
class="form-control"
|
||||||
|
required>
|
||||||
|
</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" value="{{ app.redirect_uri }}"
|
||||||
|
placeholder="Enter redirect URI"
|
||||||
|
class="form-control" required>
|
||||||
|
</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">
|
||||||
|
{% if app.is_confidential == true %}
|
||||||
|
<input name="is_confidential" type="radio" value="true"
|
||||||
|
class="form-selectgroup-input"
|
||||||
|
required checked>
|
||||||
|
{% else %}
|
||||||
|
<input name="is_confidential" type="radio" value="true"
|
||||||
|
class="form-selectgroup-input"
|
||||||
|
required>
|
||||||
|
{% endif %}
|
||||||
|
<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">
|
||||||
|
{% if app.is_confidential == false %}
|
||||||
|
<input name="is_confidential" type="radio" value="false"
|
||||||
|
class="form-selectgroup-input"
|
||||||
|
required checked>
|
||||||
|
{% else %}
|
||||||
|
<input name="is_confidential" type="radio" value="false"
|
||||||
|
class="form-selectgroup-input"
|
||||||
|
required>
|
||||||
|
{% endif %}
|
||||||
|
<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">Save</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block libs_js %}
|
||||||
|
{% endblock lib_js %}
|
||||||
|
|
||||||
|
{% block additional_js %}
|
||||||
|
{% endblock additional_js %}
|
||||||
|
|
@ -2,6 +2,7 @@ use crate::error::Error;
|
||||||
use crate::hash::{hash, Hash};
|
use crate::hash::{hash, Hash};
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use nanoid_dictionary::ALPHANUMERIC;
|
use nanoid_dictionary::ALPHANUMERIC;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
// Struct to generate the secret
|
// Struct to generate the secret
|
||||||
pub struct SecretString(String);
|
pub struct SecretString(String);
|
||||||
|
|
@ -20,6 +21,11 @@ impl AsRef<str> for SecretString {
|
||||||
self.0.as_ref()
|
self.0.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Display for SecretString {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Secret(Hash);
|
pub struct Secret(Hash);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue