mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Cache buster buster (#3091)
This commit is contained in:
parent
6b48054a2e
commit
c8b3b6214c
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3249,6 +3249,7 @@ dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"gix",
|
"gix",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sha2",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,10 @@ test = false
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
toml = { workspace = true }
|
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
sha2 = { workspace = true }
|
||||||
|
toml = { workspace = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use base64::prelude::BASE64_STANDARD;
|
||||||
use base64::{engine::general_purpose, Engine as _};
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use sha2::Digest;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
// To debug why a rebuild is requested.
|
// To debug why a rebuild is requested.
|
||||||
|
@ -83,16 +85,22 @@ pub fn apply_profile() {
|
||||||
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
|
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
|
||||||
println!("cargo:rerun-if-env-changed=KANIDM_PKG_COMMIT_REV");
|
println!("cargo:rerun-if-env-changed=KANIDM_PKG_COMMIT_REV");
|
||||||
|
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
let kanidm_pkg_version = match option_env!("KANIDM_PKG_COMMIT_REV") {
|
||||||
if let Some(commit_rev) = option_env!("KANIDM_PKG_COMMIT_REV") {
|
Some(commit_rev) => format!("{} {}", env!("CARGO_PKG_VERSION"), commit_rev),
|
||||||
println!(
|
None => env!("CARGO_PKG_VERSION").to_string(),
|
||||||
"cargo:rustc-env=KANIDM_PKG_VERSION={} {}",
|
|
||||||
version, commit_rev
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
println!("cargo:rustc-env=KANIDM_PKG_VERSION={}", version);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
println!("cargo:rustc-env=KANIDM_PKG_VERSION={}", kanidm_pkg_version);
|
||||||
|
|
||||||
|
// KANIDM_PKG_VERSION_HASH is used for cache busting in the web UI
|
||||||
|
let mut kanidm_pkg_version_hash = sha2::Sha256::new();
|
||||||
|
kanidm_pkg_version_hash.update(kanidm_pkg_version.as_bytes());
|
||||||
|
let kanidm_pkg_version_hash = &BASE64_STANDARD.encode(kanidm_pkg_version_hash.finalize())[..8];
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=KANIDM_PKG_VERSION_HASH={}",
|
||||||
|
kanidm_pkg_version_hash
|
||||||
|
);
|
||||||
|
|
||||||
let version_pre = env!("CARGO_PKG_VERSION_PRE");
|
let version_pre = env!("CARGO_PKG_VERSION_PRE");
|
||||||
if version_pre == "dev" {
|
if version_pre == "dev" {
|
||||||
println!("cargo:rustc-env=KANIDM_PRE_RELEASE=1");
|
println!("cargo:rustc-env=KANIDM_PRE_RELEASE=1");
|
||||||
|
|
|
@ -68,7 +68,7 @@ pub fn start_logging_pipeline(
|
||||||
);
|
);
|
||||||
|
|
||||||
// this env var gets set at build time, if we can pull it, add it to the metadata
|
// this env var gets set at build time, if we can pull it, add it to the metadata
|
||||||
let git_rev = match option_env!("KANIDM_KANIDM_PKG_COMMIT_REV") {
|
let git_rev = match option_env!("KANIDM_PKG_COMMIT_REV") {
|
||||||
Some(rev) => format!("-{}", rev),
|
Some(rev) => format!("-{}", rev),
|
||||||
None => "".to_string(),
|
None => "".to_string(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
# - set up a test oauth2 rp (https://kanidm.com)
|
# - set up a test oauth2 rp (https://kanidm.com)
|
||||||
# - prompt to reset testuser's creds online
|
# - prompt to reset testuser's creds online
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
if [ -n "${BUILD_MODE}" ]; then
|
if [ -n "${BUILD_MODE}" ]; then
|
||||||
BUILD_MODE="--${BUILD_MODE}"
|
BUILD_MODE="--${BUILD_MODE}"
|
||||||
else
|
else
|
||||||
|
|
|
@ -256,8 +256,13 @@ impl ServerConfig {
|
||||||
|
|
||||||
let ignorable_build_fields = [
|
let ignorable_build_fields = [
|
||||||
"KANIDM_CPU_FLAGS",
|
"KANIDM_CPU_FLAGS",
|
||||||
|
"KANIDM_CPU_FLAGS",
|
||||||
|
"KANIDM_DEFAULT_CONFIG_PATH",
|
||||||
"KANIDM_DEFAULT_CONFIG_PATH",
|
"KANIDM_DEFAULT_CONFIG_PATH",
|
||||||
"KANIDM_DEFAULT_UNIX_SHELL_PATH",
|
"KANIDM_DEFAULT_UNIX_SHELL_PATH",
|
||||||
|
"KANIDM_DEFAULT_UNIX_SHELL_PATH",
|
||||||
|
"KANIDM_HTMX_UI_PKG_PATH",
|
||||||
|
"KANIDM_PKG_VERSION_HASH",
|
||||||
"KANIDM_PKG_VERSION",
|
"KANIDM_PKG_VERSION",
|
||||||
"KANIDM_PRE_RELEASE",
|
"KANIDM_PRE_RELEASE",
|
||||||
"KANIDM_PROFILE_NAME",
|
"KANIDM_PROFILE_NAME",
|
||||||
|
|
11
server/core/src/https/cache_buster.rs
Normal file
11
server/core/src/https/cache_buster.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//! Used for appending cache-busting query parameters to URLs.
|
||||||
|
//!
|
||||||
|
|
||||||
|
#[allow(dead_code)] // Because it's used in templates
|
||||||
|
/// Gets the git rev from the KANIDM_PKG_COMMIT_REV variable else drops back to the version, to allow for cache-busting parameters in URLs
|
||||||
|
#[inline]
|
||||||
|
pub fn get_cache_buster_key() -> String {
|
||||||
|
option_env!("KANIDM_PKG_VERSION_HASH") // this comes from the profiles crate at build time
|
||||||
|
.unwrap_or(env!("CARGO_PKG_VERSION"))
|
||||||
|
.to_string()
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ pub struct JavaScriptFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JavaScriptFile {
|
impl JavaScriptFile {
|
||||||
/// returns a `<script>` or `<meta>` HTML tag
|
/// returns a `<script>` or `<meta>` HTML tag, includes the hash as a query value as a cache-busting mechanism
|
||||||
pub fn as_tag(&self) -> String {
|
pub fn as_tag(&self) -> String {
|
||||||
let filetype = match &self.filetype {
|
let filetype = match &self.filetype {
|
||||||
Some(val) => {
|
Some(val) => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod apidocs;
|
mod apidocs;
|
||||||
|
pub(crate) mod cache_buster;
|
||||||
pub(crate) mod errors;
|
pub(crate) mod errors;
|
||||||
|
|
||||||
mod extractors;
|
mod extractors;
|
||||||
mod generic;
|
mod generic;
|
||||||
mod javascript;
|
mod javascript;
|
||||||
|
@ -243,7 +243,7 @@ pub async fn create_https_server(
|
||||||
"frame-ancestors 'none'; ",
|
"frame-ancestors 'none'; ",
|
||||||
"img-src 'self' data:; ",
|
"img-src 'self' data:; ",
|
||||||
"worker-src 'none'; ",
|
"worker-src 'none'; ",
|
||||||
"script-src 'self' 'unsafe-eval'{};"
|
"script-src 'self' 'unsafe-eval'{};",
|
||||||
),
|
),
|
||||||
js_checksums
|
js_checksums
|
||||||
);
|
);
|
||||||
|
|
|
@ -76,12 +76,11 @@ pub(crate) async fn ui_handler_generic(
|
||||||
jsfiles.push(jsfile.clone().as_tag())
|
jsfiles.push(jsfile.clone().as_tag())
|
||||||
};
|
};
|
||||||
|
|
||||||
let jstags = jsfiles.join("\n");
|
let body: String = format!(
|
||||||
|
|
||||||
let body = format!(
|
|
||||||
include_str!("ui_html.html"),
|
include_str!("ui_html.html"),
|
||||||
domain_info.display_name(),
|
jstags = jsfiles.join("\n"),
|
||||||
jstags,
|
cache_buster_key = crate::https::cache_buster::get_cache_buster_key(),
|
||||||
|
display_name = domain_info.display_name()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut res = Response::new(body);
|
let mut res = Response::new(body);
|
||||||
|
|
|
@ -5,35 +5,41 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="theme-color" content="white" />
|
<meta name="theme-color" content="white" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<title>{}</title>
|
<title>{display_name}</title>
|
||||||
|
|
||||||
<link rel="icon" href="/pkg/img/favicon.png" />
|
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="apple-touch-icon" href="/pkg/img/logo-256.png" />
|
|
||||||
|
<link rel="icon"
|
||||||
|
href="/pkg/img/favicon.png?v={cache_buster_key}" />
|
||||||
|
<link rel="apple-touch-icon"
|
||||||
|
href="/pkg/img/logo-256.png?v={cache_buster_key}" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180"
|
<link rel="apple-touch-icon" sizes="180x180"
|
||||||
href="/pkg/img/logo-180.png" />
|
href="/pkg/img/logo-180.png?v={cache_buster_key}" />
|
||||||
<link rel="apple-touch-icon" sizes="192x192"
|
<link rel="apple-touch-icon" sizes="192x192"
|
||||||
href="/pkg/img/logo-192.png" />
|
href="/pkg/img/logo-192.png?v={cache_buster_key}" />
|
||||||
<link rel="apple-touch-icon" sizes="512x512"
|
<link rel="apple-touch-icon" sizes="512x512"
|
||||||
href="/pkg/img/logo-square.svg" />
|
href="/pkg/img/logo-square.svg?v={cache_buster_key}" />
|
||||||
<link rel="stylesheet" href="/pkg/external/bootstrap.min.css"
|
<link rel="stylesheet" href="/pkg/external/bootstrap.min.css"
|
||||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" />
|
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" />
|
||||||
<link rel="stylesheet" href="/pkg/style.css" />
|
<link rel="stylesheet"
|
||||||
|
href="/pkg/style.css?v={cache_buster_key}" />
|
||||||
|
|
||||||
{}
|
{jstags}
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class="flex-column d-flex h-100">
|
<body class="flex-column d-flex h-100">
|
||||||
<main class="flex-shrink-0 form-signin">
|
<main class="flex-shrink-0 form-signin">
|
||||||
<center>
|
<center>
|
||||||
<img src="/pkg/img/logo-square.svg" alt="Kanidm"
|
<img
|
||||||
|
src="/pkg/img/logo-square.svg?v={cache_buster_key}"
|
||||||
|
alt="Kanidm"
|
||||||
class="kanidm_logo" />
|
class="kanidm_logo" />
|
||||||
<h3>Kanidm is loading, please wait... </h3>
|
<h3>Kanidm is loading, please wait... </h3>
|
||||||
</center>
|
</center>
|
||||||
</main>
|
</main>
|
||||||
<footer class="footer mt-auto py-3 bg-light text-end">
|
<footer class="footer mt-auto py-3 bg-light text-end">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span class="text-muted">Powered by <a href="https://kanidm.com">Kanidm</a></span>
|
<span class="text-muted">Powered by <a
|
||||||
|
href="https://kanidm.com">Kanidm</a></span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -641,14 +641,14 @@ pub fn cert_generate_core(config: &Configuration) {
|
||||||
let (tls_key_path, tls_chain_path) = match &config.tls_config {
|
let (tls_key_path, tls_chain_path) = match &config.tls_config {
|
||||||
Some(tls_config) => (tls_config.key.as_path(), tls_config.chain.as_path()),
|
Some(tls_config) => (tls_config.key.as_path(), tls_config.chain.as_path()),
|
||||||
None => {
|
None => {
|
||||||
error!("Unable to find tls configuration");
|
error!("Unable to find TLS configuration");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if tls_key_path.exists() && tls_chain_path.exists() {
|
if tls_key_path.exists() && tls_chain_path.exists() {
|
||||||
info!(
|
info!(
|
||||||
"tls key and chain already exist - remove them first if you intend to regenerate these"
|
"TLS key and chain already exist - remove them first if you intend to regenerate these"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -870,7 +870,7 @@ async fn repl_acceptor(
|
||||||
// Handle *reloads*
|
// Handle *reloads*
|
||||||
/*
|
/*
|
||||||
_ = reload.recv() => {
|
_ = reload.recv() => {
|
||||||
info!("initiate tls reload");
|
info!("Initiating TLS reload");
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
<main class="p-3 x-auto">
|
<main class="p-3 x-auto">
|
||||||
<div class="(( crate::https::ui::CSS_PAGE_HEADER ))">
|
<div class="(( crate::https::ui::CSS_PAGE_HEADER ))">
|
||||||
<h2>Applications list</h2>
|
<h2>Applications list</h2>
|
||||||
</div>
|
</div>
|
||||||
(% if apps.is_empty() %)
|
(% if apps.is_empty() %)
|
||||||
<h5>No linked applications available</h5>
|
<h5>No linked applications available</h5>
|
||||||
(% else %)
|
(% else %)
|
||||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
|
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
|
||||||
(% for app in apps %)
|
(% for app in apps %)
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card text-center">
|
<div class="card text-center">
|
||||||
(% match app %)
|
(% match app %)
|
||||||
(% when AppLink::Oauth2 with { name, display_name, redirect_url, has_image } %)
|
(% when AppLink::Oauth2 with { name, display_name, redirect_url, has_image }
|
||||||
|
%)
|
||||||
<a href="(( redirect_url ))" class="link-dark stretched-link mt-2">
|
<a href="(( redirect_url ))" class="link-dark stretched-link mt-2">
|
||||||
(% if has_image %)
|
(% if has_image %)
|
||||||
<img src="/ui/images/oauth2/(( name ))" class="oauth2-img" alt="((display_name)) icon" id="(( name ))">
|
<img src="/ui/images/oauth2/(( name ))" class="oauth2-img"
|
||||||
|
alt="((display_name)) icon" id="(( name ))">
|
||||||
(% else %)
|
(% else %)
|
||||||
<img src="/pkg/img/icon-oauth2.svg" class="oauth2-img" alt="missing-icon icon" id="(( name ))">
|
<img
|
||||||
|
src="/pkg/img/icon-oauth2.svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
class="oauth2-img" alt="missing-icon icon" id="(( name ))">
|
||||||
(% endif %)
|
(% endif %)
|
||||||
</a>
|
</a>
|
||||||
<label for="(( name ))">(( display_name ))</label>
|
<label for="(( name ))">(( display_name ))</label>
|
||||||
(% endmatch %)
|
(% endmatch %)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
(% endfor %)
|
(% endfor %)
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8" />
|
||||||
<meta name="theme-color" content="white" />
|
<meta name="theme-color" content="white" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
|
||||||
<title>(% block title %)(( title )) - Kanidm(% endblock %)</title>
|
<title>(% block title %)(( title )) - Kanidm(% endblock %)</title>
|
||||||
|
|
||||||
<link rel="icon" href="/pkg/img/favicon.png" />
|
(% include "base_icons.html" %)
|
||||||
<link rel="apple-touch-icon" href="/pkg/img/logo-256.png" />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180"
|
|
||||||
href="/pkg/img/logo-180.png" />
|
|
||||||
<link rel="apple-touch-icon" sizes="192x192"
|
|
||||||
href="/pkg/img/logo-192.png" />
|
|
||||||
<link rel="apple-touch-icon" sizes="512x512"
|
|
||||||
href="/pkg/img/logo-square.svg" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/pkg/external/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"/>
|
<link rel="stylesheet"
|
||||||
<script src="/pkg/external/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"></script>
|
href="/pkg/external/bootstrap.min.css?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
<link rel="stylesheet" href="/pkg/style.css" />
|
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" />
|
||||||
|
<script
|
||||||
|
src="/pkg/external/bootstrap.bundle.min.js?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"></script>
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="/pkg/style.css?v=((crate::https::cache_buster::get_cache_buster_key()))" />
|
||||||
|
|
||||||
(% block head %)(% endblock %)
|
(% block head %)(% endblock %)
|
||||||
</head>
|
</head>
|
||||||
|
@ -26,7 +24,8 @@
|
||||||
(% block body %)(% endblock %)
|
(% block body %)(% endblock %)
|
||||||
<footer class="footer mt-auto py-3 bg-light text-end">
|
<footer class="footer mt-auto py-3 bg-light text-end">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span class="text-muted">Powered by <a href="https://kanidm.com">Kanidm</a></span>
|
<span class="text-muted">Powered by <a
|
||||||
|
href="https://kanidm.com">Kanidm</a></span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8" />
|
||||||
<meta name="theme-color" content="white" />
|
<meta name="theme-color" content="white" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="htmx-config" content='{ "includeIndicatorStyles": false }' />
|
||||||
|
|
||||||
<title>(% block title %)(( title )) - Kanidm(% endblock %)</title>
|
<title>(% block title %)(( title )) - Kanidm(% endblock %)</title>
|
||||||
|
|
||||||
<link rel="icon" href="/pkg/img/favicon.png" />
|
(% include "base_icons.html" %)
|
||||||
<link rel="apple-touch-icon" href="/pkg/img/logo-256.png" />
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180"
|
|
||||||
href="/pkg/img/logo-180.png" />
|
|
||||||
<link rel="apple-touch-icon" sizes="192x192"
|
|
||||||
href="/pkg/img/logo-192.png" />
|
|
||||||
<link rel="apple-touch-icon" sizes="512x512"
|
|
||||||
href="/pkg/img/logo-square.svg" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/pkg/external/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"/>
|
<link rel="stylesheet"
|
||||||
<script src="/pkg/external/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"></script>
|
href="/pkg/external/bootstrap.min.css?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
<script src="/pkg/external/htmx.min.1.9.12.js" integrity="sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2"></script>
|
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" />
|
||||||
<link rel="stylesheet" href="/pkg/style.css" />
|
<script
|
||||||
|
src="/pkg/external/bootstrap.bundle.min.js?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"></script>
|
||||||
|
<script
|
||||||
|
src="/pkg/external/htmx.min.1.9.12.js?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
integrity="sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2"></script>
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="/pkg/style.css?v=((crate::https::cache_buster::get_cache_buster_key()))" />
|
||||||
|
|
||||||
(% block head %)(% endblock %)
|
(% block head %)(% endblock %)
|
||||||
</head>
|
</head>
|
||||||
|
@ -28,9 +29,9 @@
|
||||||
(% block body %)(% endblock %)
|
(% block body %)(% endblock %)
|
||||||
<footer class="footer mt-auto py-3 bg-light text-end">
|
<footer class="footer mt-auto py-3 bg-light text-end">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span class="text-muted">Powered by <a href="https://kanidm.com">Kanidm</a></span>
|
<span class="text-muted">Powered by <a
|
||||||
|
href="https://kanidm.com">Kanidm</a></span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
10
server/core/templates/base_icons.html
Normal file
10
server/core/templates/base_icons.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<link rel="icon"
|
||||||
|
href="/pkg/img/favicon.png?v=((crate::https::cache_buster::get_cache_buster_key()))" />
|
||||||
|
<link rel="apple-touch-icon"
|
||||||
|
href="/pkg/img/logo-256.png?v=((crate::https::cache_buster::get_cache_buster_key()))" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180"
|
||||||
|
href="/pkg/img/logo-180.png?v=((crate::https::cache_buster::get_cache_buster_key()))" />
|
||||||
|
<link rel="apple-touch-icon" sizes="192x192"
|
||||||
|
href="/pkg/img/logo-192.png?v=((crate::https::cache_buster::get_cache_buster_key()))" />
|
||||||
|
<link rel="apple-touch-icon" sizes="512x512"
|
||||||
|
href="/pkg/img/logo-square.svg?v=((crate::https::cache_buster::get_cache_buster_key()))" />
|
|
@ -4,47 +4,57 @@
|
||||||
|
|
||||||
(% block head %)
|
(% block head %)
|
||||||
<!-- TODO: janky preloading them here because I assumed htmx swapped new scripts in on boosted requests, we can replace navigation to cred update with a full redirect later, and clean this up then -->
|
<!-- TODO: janky preloading them here because I assumed htmx swapped new scripts in on boosted requests, we can replace navigation to cred update with a full redirect later, and clean this up then -->
|
||||||
<script src="/pkg/external/cred_update.js"></script>
|
<script
|
||||||
<script src="/pkg/external/base64.js" async></script>
|
src="/pkg/external/cred_update.js?v=((crate::https::cache_buster::get_cache_buster_key()))"></script>
|
||||||
|
<script
|
||||||
|
src="/pkg/external/base64.js?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
async></script>
|
||||||
(% endblock %)
|
(% endblock %)
|
||||||
|
|
||||||
(% block body %)
|
(% block body %)
|
||||||
<main class="flex-shrink-0 container form-signin" id="cred-reset-form">
|
<main class="flex-shrink-0 container form-signin" id="cred-reset-form">
|
||||||
<center>
|
<center>
|
||||||
<img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/>
|
<img
|
||||||
|
src="/pkg/img/logo-square.svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
alt="Kanidm" class="kanidm_logo" />
|
||||||
<h2>Credential Reset</h2>
|
<h2>Credential Reset</h2>
|
||||||
<h3>(( domain_info.display_name() ))</h3>
|
<h3>(( domain_info.display_name() ))</h3>
|
||||||
</center>
|
</center>
|
||||||
<form class="mb-3">
|
<form class="mb-3">
|
||||||
<div>
|
<div>
|
||||||
<label for="token" class="form-label">Enter your credential reset token.</label>
|
<label for="token" class="form-label">Enter your credential reset
|
||||||
|
token.</label>
|
||||||
<input
|
<input
|
||||||
id="token"
|
id="token"
|
||||||
name="token"
|
name="token"
|
||||||
autofocus
|
autofocus
|
||||||
aria-describedby="unknown-reset-token-validation-feedback"
|
aria-describedby="unknown-reset-token-validation-feedback"
|
||||||
(% if wrong_code %)
|
(% if wrong_code %)
|
||||||
class='form-control is-invalid'
|
class='form-control is-invalid'
|
||||||
(% else %)
|
(% else %)
|
||||||
class='form-control'
|
class='form-control'
|
||||||
(% endif %)
|
(% endif %)>
|
||||||
>
|
(% if wrong_code %)
|
||||||
(% if wrong_code %)
|
<div id="unknown-reset-token-validation-feedback"
|
||||||
<div id="unknown-reset-token-validation-feedback" class="invalid-feedback">
|
class="invalid-feedback">
|
||||||
<ul><li>Unknown reset token.<br>Brand-new tokens might not be synced yet, <br>wait a few minutes before trying again.</li></ul>
|
<ul><li>Unknown reset token.<br>Brand-new tokens might not be
|
||||||
|
synced yet, <br>wait a few minutes before trying
|
||||||
|
again.</li></ul>
|
||||||
</div>
|
</div>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p class="d-flex flex-row flex-wrap justify-content-between">
|
<p class="d-flex flex-row flex-wrap justify-content-between">
|
||||||
<button class="btn btn-secondary" aria-label="Return home" hx-get="/ui" hx-target="body">
|
<button class="btn btn-secondary" aria-label="Return home" hx-get="/ui"
|
||||||
|
hx-target="body">
|
||||||
Return to the home page
|
Return to the home page
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-primary"
|
<button class="btn btn-primary"
|
||||||
hx-get="/ui/reset"
|
hx-get="/ui/reset"
|
||||||
hx-include="#token"
|
hx-include="#token"
|
||||||
hx-target="#cred-reset-form" hx-select="#cred-reset-form" hx-swap="outerHTML"
|
hx-target="#cred-reset-form" hx-select="#cred-reset-form"
|
||||||
type="submit">
|
hx-swap="outerHTML"
|
||||||
|
type="submit">
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,121 +1,147 @@
|
||||||
<script type="module" src="/pkg/modules/cred_update.mjs" async></script>
|
<script type="module"
|
||||||
<script src="/pkg/external/base64.js" async></script>
|
src="/pkg/modules/cred_update.mjs?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
async></script>
|
||||||
|
<script
|
||||||
|
src="/pkg/external/base64.js?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
async></script>
|
||||||
|
|
||||||
<div class="row g-3" id="credentialUpdateDynamicSection" hx-on::before-swap="stillSwapFailureResponse(event)">
|
<div class="row g-3" id="credentialUpdateDynamicSection"
|
||||||
|
hx-on::before-swap="stillSwapFailureResponse(event)">
|
||||||
<form class="needs-validation mb-5 pb-5" novalidate>
|
<form class="needs-validation mb-5 pb-5" novalidate>
|
||||||
(% match ext_cred_portal %)
|
(% match ext_cred_portal %)
|
||||||
(% when CUExtPortal::None %)
|
(% when CUExtPortal::None %)
|
||||||
(% when CUExtPortal::Hidden %)
|
(% when CUExtPortal::Hidden %)
|
||||||
<hr class="my-4" />
|
<hr class="my-4" />
|
||||||
<p>This account is externally managed. Some features may not be available.</p>
|
<p>This account is externally managed. Some features may not be
|
||||||
(% when CUExtPortal::Some(url) %)
|
available.</p>
|
||||||
<hr class="my-4" />
|
(% when CUExtPortal::Some(url) %)
|
||||||
<p>This account is externally managed. Some features may not be available.</p>
|
<hr class="my-4" />
|
||||||
<a href="(( url ))">Visit the external account portal</a>
|
<p>This account is externally managed. Some features may not be
|
||||||
|
available.</p>
|
||||||
|
<a href="(( url ))">Visit the external account portal</a>
|
||||||
(% endmatch %)
|
(% endmatch %)
|
||||||
|
|
||||||
(% if warnings.len() > 0 %)
|
(% if warnings.len() > 0 %)
|
||||||
<hr class="my-4" >
|
<hr class="my-4">
|
||||||
(% for warning in warnings %)
|
(% for warning in warnings %)
|
||||||
(% let is_danger = [CURegWarning::WebauthnAttestationUnsatisfiable, CURegWarning::Unsatisfiable].contains(warning) %)
|
(% let is_danger = [CURegWarning::WebauthnAttestationUnsatisfiable,
|
||||||
(% if is_danger %)
|
CURegWarning::Unsatisfiable].contains(warning) %)
|
||||||
<div class='alert alert-danger' role="alert">
|
(% if is_danger %)
|
||||||
(% else %)
|
<div class='alert alert-danger' role="alert">
|
||||||
<div class='alert alert-warning' role="alert">
|
(% else %)
|
||||||
|
<div class='alert alert-warning' role="alert">
|
||||||
(% endif %)
|
(% endif %)
|
||||||
|
|
||||||
(% match warning %)
|
(% match warning %)
|
||||||
(% when CURegWarning::MfaRequired %)
|
(% when CURegWarning::MfaRequired %)
|
||||||
Multi-Factor Authentication is required for your account. Either add TOTP or remove your password in favour of passkeys to submit.
|
Multi-Factor Authentication is required for your account. Either
|
||||||
(% when CURegWarning::PasskeyRequired %)
|
add TOTP or remove your password in favour of passkeys to
|
||||||
Passkeys are required for your account.
|
submit.
|
||||||
(% when CURegWarning::AttestedPasskeyRequired %)
|
(% when CURegWarning::PasskeyRequired %)
|
||||||
Attested Passkeys are required for your account.
|
Passkeys are required for your account.
|
||||||
(% when CURegWarning::AttestedResidentKeyRequired %)
|
(% when CURegWarning::AttestedPasskeyRequired %)
|
||||||
Attested Resident Keys are required for your account.
|
Attested Passkeys are required for your account.
|
||||||
(% when CURegWarning::WebauthnAttestationUnsatisfiable %)
|
(% when CURegWarning::AttestedResidentKeyRequired %)
|
||||||
A webauthn attestation policy conflict has occurred and you will not be able to save your credentials
|
Attested Resident Keys are required for your account.
|
||||||
(% when CURegWarning::Unsatisfiable %)
|
(% when CURegWarning::WebauthnAttestationUnsatisfiable %)
|
||||||
An account policy conflict has occurred and you will not be able to save your credentials
|
A webauthn attestation policy conflict has occurred and you will
|
||||||
|
not be able to save your credentials
|
||||||
|
(% when CURegWarning::Unsatisfiable %)
|
||||||
|
An account policy conflict has occurred and you will not be able
|
||||||
|
to save your credentials
|
||||||
(% endmatch %)
|
(% endmatch %)
|
||||||
|
|
||||||
(% if is_danger %)
|
(% if is_danger %)
|
||||||
<br><br>
|
<br><br>
|
||||||
<b>Contact support IMMEDIATELY.</b>
|
<b>Contact support IMMEDIATELY.</b>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
</div>
|
</div>
|
||||||
(% endfor %)
|
(% endfor %)
|
||||||
(% endif %)
|
(% endif %)
|
||||||
|
|
||||||
<!-- Attested Passkeys -->
|
<!-- Attested Passkeys -->
|
||||||
(% match attested_passkeys_state %)
|
(% match attested_passkeys_state %)
|
||||||
(% when CUCredState::Modifiable %)
|
(% when CUCredState::Modifiable %)
|
||||||
(% include "credentials_update_attested_passkeys.html" %)
|
(% include "credentials_update_attested_passkeys.html" %)
|
||||||
<button type="button" class="btn btn-primary" hx-post="/ui/reset/add_passkey" hx-vals='{"class": "Attested"}' hx-target="#credentialUpdateDynamicSection">
|
<button type="button" class="btn btn-primary"
|
||||||
Add Attested Passkey
|
hx-post="/ui/reset/add_passkey" hx-vals='{"class": "Attested"}'
|
||||||
|
hx-target="#credentialUpdateDynamicSection">
|
||||||
|
Add Attested Passkey
|
||||||
|
</button>
|
||||||
|
(% when CUCredState::DeleteOnly %)
|
||||||
|
(% if attested_passkeys.len() > 0 %)
|
||||||
|
(% include "credentials_update_attested_passkeys.html" %)
|
||||||
|
(% endif %)
|
||||||
|
(% when CUCredState::AccessDeny %)
|
||||||
|
(% when CUCredState::PolicyDeny %)
|
||||||
|
(% endmatch %)
|
||||||
|
|
||||||
|
<!-- Passkeys -->
|
||||||
|
(% match passkeys_state %)
|
||||||
|
(% when CUCredState::Modifiable %)
|
||||||
|
(% include "credentials_update_passkeys.html" %)
|
||||||
|
<!-- Here we are modifiable so we can render the button to add passkeys -->
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-primary"
|
||||||
|
hx-post="/ui/reset/add_passkey"
|
||||||
|
hx-vals='{"class": "Any"}'
|
||||||
|
hx-target="#credentialUpdateDynamicSection">
|
||||||
|
Add Passkey
|
||||||
</button>
|
</button>
|
||||||
(% when CUCredState::DeleteOnly %)
|
<button type="button"
|
||||||
(% if attested_passkeys.len() > 0 %)
|
class="btn btn-primary dropdown-toggle dropdown-toggle-split"
|
||||||
(% include "credentials_update_attested_passkeys.html" %)
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
(% endif %)
|
<span class="visually-hidden">Toggle Dropdown</span>
|
||||||
(% when CUCredState::AccessDeny %)
|
</button>
|
||||||
(% when CUCredState::PolicyDeny %)
|
<ul class="dropdown-menu">
|
||||||
(% endmatch %)
|
<li><a class="dropdown-item" hx-post="/ui/api/cancel_mfareg"
|
||||||
|
hx-swap="none">Cancel MFA Registration
|
||||||
<!-- Passkeys -->
|
session</a></li>
|
||||||
(% match passkeys_state %)
|
</ul>
|
||||||
(% when CUCredState::Modifiable %)
|
</div>
|
||||||
(% include "credentials_update_passkeys.html" %)
|
|
||||||
<!-- Here we are modifiable so we can render the button to add passkeys -->
|
|
||||||
<div class="btn-group">
|
|
||||||
<button type="button" class="btn btn-primary" hx-post="/ui/reset/add_passkey"
|
|
||||||
hx-vals='{"class": "Any"}'
|
|
||||||
hx-target="#credentialUpdateDynamicSection">
|
|
||||||
Add Passkey
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li><a class="dropdown-item" hx-post="/ui/api/cancel_mfareg" hx-swap="none">Cancel MFA Registration session</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
(% when CUCredState::DeleteOnly %)
|
(% when CUCredState::DeleteOnly %)
|
||||||
(% if passkeys.len() > 0 %)
|
(% if passkeys.len() > 0 %)
|
||||||
(% include "credentials_update_passkeys.html" %)
|
(% include "credentials_update_passkeys.html" %)
|
||||||
(% endif %)
|
(% endif %)
|
||||||
(% when CUCredState::AccessDeny %)
|
(% when CUCredState::AccessDeny %)
|
||||||
(% when CUCredState::PolicyDeny %)
|
(% when CUCredState::PolicyDeny %)
|
||||||
(% endmatch %)
|
(% endmatch %)
|
||||||
|
|
||||||
<!-- Password, totp credentials -->
|
<!-- Password, totp credentials -->
|
||||||
(% let primary_state = primary_state %)
|
(% let primary_state = primary_state %)
|
||||||
(% include "credentials_update_primary.html" %)
|
(% include "credentials_update_primary.html" %)
|
||||||
|
|
||||||
<div id="cred-update-commit-bar" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
<div id="cred-update-commit-bar" class="toast" role="alert"
|
||||||
<div class="toast-body">
|
aria-live="assertive" aria-atomic="true">
|
||||||
<span class="d-flex align-items-center">
|
<div class="toast-body">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-floppy2-fill" viewBox="0 0 16 16">
|
<span class="d-flex align-items-center">
|
||||||
<path d="M12 2h-2v3h2z"/>
|
<svg xmlns="http://www.w3.org/2000/svg" width="16"
|
||||||
<path d="M1.5 0A1.5 1.5 0 0 0 0 1.5v13A1.5 1.5 0 0 0 1.5 16h13a1.5 1.5 0 0 0 1.5-1.5V2.914a1.5 1.5 0 0 0-.44-1.06L14.147.439A1.5 1.5 0 0 0 13.086 0zM4 6a1 1 0 0 1-1-1V1h10v4a1 1 0 0 1-1 1zM3 9h10a1 1 0 0 1 1 1v5H2v-5a1 1 0 0 1 1-1"/>
|
height="16" fill="currentColor"
|
||||||
</svg>
|
class="bi bi-floppy2-fill" viewBox="0 0 16 16">
|
||||||
<b class="px-1">Careful</b>- save when you're done:
|
<path d="M12 2h-2v3h2z" />
|
||||||
</span>
|
<path
|
||||||
<div class="mt-2 pt-2 border-top">
|
d="M1.5 0A1.5 1.5 0 0 0 0 1.5v13A1.5 1.5 0 0 0 1.5 16h13a1.5 1.5 0 0 0 1.5-1.5V2.914a1.5 1.5 0 0 0-.44-1.06L14.147.439A1.5 1.5 0 0 0 13.086 0zM4 6a1 1 0 0 1-1-1V1h10v4a1 1 0 0 1-1 1zM3 9h10a1 1 0 0 1 1 1v5H2v-5a1 1 0 0 1 1-1" />
|
||||||
<button class="btn btn-danger" hx-post="/ui/api/cu_cancel" hx-target="body">Cancel</button>
|
</svg>
|
||||||
<span class="d-inline-block" tabindex="0" data-bs-toggle="tooltip" data-bs-title="Resolve the warnings at the top.">
|
<b class="px-1">Careful</b>- save when you're done:
|
||||||
<button
|
</span>
|
||||||
|
<div class="mt-2 pt-2 border-top">
|
||||||
|
<button class="btn btn-danger"
|
||||||
|
hx-post="/ui/api/cu_cancel"
|
||||||
|
hx-target="body">Cancel</button>
|
||||||
|
<span class="d-inline-block" tabindex="0"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-title="Resolve the warnings at the top.">
|
||||||
|
<button
|
||||||
class="btn btn-success"
|
class="btn btn-success"
|
||||||
type="submit"
|
type="submit"
|
||||||
hx-post="/ui/api/cu_commit"
|
hx-post="/ui/api/cu_commit"
|
||||||
hx-boost="false"
|
hx-boost="false"
|
||||||
(% if !warnings.is_empty() %)disabled(% endif %)
|
(% if !warnings.is_empty() %)disabled(% endif
|
||||||
>Save Changes</button>
|
%)>Save Changes</button>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
|
@ -6,18 +6,21 @@
|
||||||
(% endblock %)
|
(% endblock %)
|
||||||
|
|
||||||
(% block body %)
|
(% block body %)
|
||||||
<main id="main" class="flex-shrink-0 form-signin">
|
<main id="main" class="flex-shrink-0 form-signin">
|
||||||
<center>
|
<center>
|
||||||
(% if domain_custom_image %)
|
(% if domain_custom_image %)
|
||||||
<img src="/ui/images/domain" alt="Kanidm" class="kanidm_logo"/>
|
<img src="/ui/images/domain"
|
||||||
(% else %)
|
alt="Kanidm" class="kanidm_logo" />
|
||||||
<img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/>
|
(% else %)
|
||||||
(% endif %)
|
<img
|
||||||
<h3>Kanidm</h3>
|
src="/pkg/img/logo-square.svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
</center>
|
alt="Kanidm" class="kanidm_logo" />
|
||||||
<div id="login-form-container" class="container">
|
(% endif %)
|
||||||
(% block logincontainer %)
|
<h3>Kanidm</h3>
|
||||||
(% endblock %)
|
</center>
|
||||||
</div>
|
<div id="login-form-container" class="container">
|
||||||
</main>
|
(% block logincontainer %)
|
||||||
|
(% endblock %)
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
(% endblock %)
|
(% endblock %)
|
||||||
|
|
|
@ -5,13 +5,19 @@
|
||||||
(( chal|safe ))
|
(( chal|safe ))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/pkg/external/base64.js" async></script>
|
<script
|
||||||
<script src="/pkg/pkhtml.js" defer></script>
|
src="/pkg/external/base64.js?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
async></script>
|
||||||
|
<script
|
||||||
|
src="/pkg/pkhtml.js?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
defer></script>
|
||||||
|
|
||||||
(% if passkey %)
|
(% if passkey %)
|
||||||
<button hx-disable type="button" class="btn btn-dark" id="start-passkey-button">Use Passkey</button>
|
<button hx-disable type="button" class="btn btn-dark"
|
||||||
|
id="start-passkey-button">Use Passkey</button>
|
||||||
(% else %)
|
(% else %)
|
||||||
<button type="button" class="btn btn-dark" id="start-seckey-button">Use Security Key</button>
|
<button type="button" class="btn btn-dark" id="start-seckey-button">Use Security
|
||||||
|
Key</button>
|
||||||
(% endif %)
|
(% endif %)
|
||||||
|
|
||||||
(% endblock %)
|
(% endblock %)
|
||||||
|
|
|
@ -1,22 +1,33 @@
|
||||||
<nav class="(( crate::https::ui::CSS_NAVBAR_NAV ))">
|
<nav class="(( crate::https::ui::CSS_NAVBAR_NAV ))">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="(( crate::https::ui::CSS_NAVBAR_BRAND ))" href="/ui/apps">Kanidm</a>
|
<a class="(( crate::https::ui::CSS_NAVBAR_BRAND ))"
|
||||||
|
href="/ui/apps">Kanidm</a>
|
||||||
|
|
||||||
<!-- this shows a button on mobile devices to open the menu-->
|
<!-- this shows a button on mobile devices to open the menu-->
|
||||||
<button class="navbar-toggler bg-white" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler bg-white" type="button"
|
||||||
<img src="/pkg/img/logo-square.svg" alt="Toggle navigation" class="navbar-toggler-img" />
|
data-bs-toggle="collapse" data-bs-target="#navbarCollapse"
|
||||||
|
aria-controls="navbarCollapse" aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation">
|
||||||
|
<img
|
||||||
|
src="/pkg/img/logo-square.svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
alt="Toggle navigation" class="navbar-toggler-img" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||||
<ul class="(( crate::https::ui::CSS_NAVBAR_LINKS_UL ))">
|
<ul class="(( crate::https::ui::CSS_NAVBAR_LINKS_UL ))">
|
||||||
<li class="mb-1">
|
<li class="mb-1">
|
||||||
<a class="nav-link" href="/ui/apps" hx-target="main" hx-select="main" hx-swap="outerHTML"><span data-feather="file"></span>Apps</a>
|
<a class="nav-link" href="/ui/apps" hx-target="main"
|
||||||
|
hx-select="main" hx-swap="outerHTML"><span
|
||||||
|
data-feather="file"></span>Apps</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mb-1">
|
<li class="mb-1">
|
||||||
<a class="nav-link" href="/ui/profile" hx-target="main" hx-select="main" hx-swap="outerHTML"><span data-feather="file"></span>Profile</a>
|
<a class="nav-link" href="/ui/profile" hx-target="main"
|
||||||
|
hx-select="main" hx-swap="outerHTML"><span
|
||||||
|
data-feather="file"></span>Profile</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mb-1">
|
<li class="mb-1">
|
||||||
<a class="nav-link" href="#" data-bs-toggle="modal" data-bs-target="#signoutModal">Sign out</a>
|
<a class="nav-link" href="#" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#signoutModal">Sign out</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
<div class="modal" tabindex="-1" role="dialog" id="errorModal" data-backdrop="static" data-keyboard="false" data-show="true">
|
<div class="modal" tabindex="-1" role="dialog" id="errorModal"
|
||||||
|
data-backdrop="static" data-keyboard="false" data-show="true">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title"><img src="/pkg/img/kani-warning.svg" alt="Kani holding warning sign" /> An error occurred</h5>
|
<h5 class="modal-title"><img
|
||||||
|
src="/pkg/img/kani-warning.svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
alt="Kani holding a warning sign" /> An error
|
||||||
|
occurred</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body text-center">
|
<div class="modal-body text-center">
|
||||||
<p>Error Code: (( error_message ))</p>
|
<p>Error Code: (( error_message ))</p>
|
||||||
|
@ -11,7 +15,7 @@
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a href="(( recovery_path ))" hx-boost="(( recovery_boosted ))">
|
<a href="(( recovery_path ))" hx-boost="(( recovery_boosted ))">
|
||||||
<button type="button" class="btn btn-secondary"
|
<button type="button" class="btn btn-secondary"
|
||||||
data-bs-dismiss="modal">Continue</button>
|
data-bs-dismiss="modal">Continue</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,18 +6,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body text-center">
|
<div class="modal-body text-center">
|
||||||
Are you sure you'd like to log out?
|
Are you sure you'd like to log out?
|
||||||
<br/>
|
<br />
|
||||||
<img src="/pkg/img/kani-waving.svg" alt="Kani waving goodbye" />
|
<img
|
||||||
|
src="/pkg/img/kani-waving.svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
alt="Kani waving goodbye" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a href="/ui/logout" hx-boost="false">
|
<a href="/ui/logout" hx-boost="false">
|
||||||
<button type="button" class="btn btn-success"
|
<button type="button" class="btn btn-success"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#signoutModal">Sign out</button>
|
data-bs-target="#signoutModal">Sign out</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button type="button" class="btn btn-secondary"
|
<button type="button" class="btn btn-secondary"
|
||||||
data-bs-dismiss="modal">Cancel</button>
|
data-bs-dismiss="modal">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
(% macro side_menu_item(label, href, menu_item, icon_name) %)
|
(% macro side_menu_item(label, href, menu_item, icon_name) %)
|
||||||
<a hx-select="main" hx-target="main" hx-swap="outerHTML show:false" href="(( href ))"
|
<a hx-select="main" hx-target="main" hx-swap="outerHTML show:false"
|
||||||
|
href="(( href ))"
|
||||||
class="list-group-item list-group-item-action d-flex (% if menu_active_item == menu_item %) active(% endif %)">
|
class="list-group-item list-group-item-action d-flex (% if menu_active_item == menu_item %) active(% endif %)">
|
||||||
<img class="me-3" src="/pkg/img/icons/(( icon_name )).svg" alt="">(( label ))
|
<img class="me-3"
|
||||||
|
src="/pkg/img/icons/(( icon_name )).svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
|
||||||
|
alt>(( label ))
|
||||||
</a>
|
</a>
|
||||||
(% endmacro %)
|
(% endmacro %)
|
||||||
|
|
||||||
<main class="container-xxl pb-5">
|
<main class="container-xxl pb-5">
|
||||||
<div class="d-flex flex-sm-row flex-column">
|
<div class="d-flex flex-sm-row flex-column">
|
||||||
<div class="list-group side-menu flex-shrink-0">
|
<div class="list-group side-menu flex-shrink-0">
|
||||||
(% call side_menu_item("Profile", "/ui/profile", ProfileMenuItems::UserProfile, "person") %)
|
(% call side_menu_item("Profile", "/ui/profile",
|
||||||
(% call side_menu_item("SSH Keys", "/ui/ssh_keys", ProfileMenuItems::SshKeys, "key") %)
|
ProfileMenuItems::UserProfile, "person") %)
|
||||||
|
(% call side_menu_item("SSH Keys", "/ui/ssh_keys",
|
||||||
|
ProfileMenuItems::SshKeys, "key") %)
|
||||||
(% if posix_enabled %)
|
(% if posix_enabled %)
|
||||||
(% call side_menu_item("UNIX Password", "/ui/update_credentials", ProfileMenuItems::UnixPassword, "building-lock") %)
|
(% call side_menu_item("UNIX Password", "/ui/update_credentials",
|
||||||
|
ProfileMenuItems::UnixPassword, "building-lock") %)
|
||||||
(% endif %)
|
(% endif %)
|
||||||
(% call side_menu_item("Credentials", "/ui/update_credentials", ProfileMenuItems::Credentials, "shield-lock") %)
|
(% call side_menu_item("Credentials", "/ui/update_credentials",
|
||||||
|
ProfileMenuItems::Credentials, "shield-lock") %)
|
||||||
</div>
|
</div>
|
||||||
<div id="settings-window" class="flex-grow-1 ps-sm-4 pt-sm-0 pt-4">
|
<div id="settings-window" class="flex-grow-1 ps-sm-4 pt-sm-0 pt-4">
|
||||||
<div class="(( crate::https::ui::CSS_PAGE_HEADER ))">
|
<div class="(( crate::https::ui::CSS_PAGE_HEADER ))">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use jsonschema::JSONSchema;
|
use jsonschema::Validator;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ async fn check_that_the_swagger_api_loads(rsclient: kanidm_client::KanidmClient)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let instance = serde_json::json!("foo");
|
let instance = serde_json::json!("foo");
|
||||||
let compiled = JSONSchema::compile(&schema).expect("A valid schema");
|
let compiled = Validator::new(&schema).expect("A valid schema");
|
||||||
assert!(jsonschema::is_valid(&schema, &instance));
|
assert!(jsonschema::is_valid(&schema, &instance));
|
||||||
let result = compiled.validate(&instance);
|
let result = compiled.validate(&instance);
|
||||||
if let Err(errors) = result {
|
if let Err(errors) = result {
|
||||||
|
|
Loading…
Reference in a new issue