Cache buster buster (#3091)

This commit is contained in:
James Hodgkinson 2024-10-15 11:54:46 +10:00 committed by GitHub
parent 6b48054a2e
commit c8b3b6214c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 339 additions and 223 deletions

1
Cargo.lock generated
View file

@ -3249,6 +3249,7 @@ dependencies = [
"base64 0.22.1",
"gix",
"serde",
"sha2",
"toml",
]

View file

@ -20,9 +20,10 @@ test = false
doctest = false
[dependencies]
serde = { workspace = true, features = ["derive"] }
toml = { workspace = true }
base64 = { workspace = true }
serde = { workspace = true, features = ["derive"] }
sha2 = { workspace = true }
toml = { workspace = true }
[build-dependencies]
base64 = { workspace = true }

View file

@ -1,5 +1,7 @@
use base64::prelude::BASE64_STANDARD;
use base64::{engine::general_purpose, Engine as _};
use serde::Deserialize;
use sha2::Digest;
use std::env;
// 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=KANIDM_PKG_COMMIT_REV");
let version = env!("CARGO_PKG_VERSION");
if let Some(commit_rev) = option_env!("KANIDM_PKG_COMMIT_REV") {
println!(
"cargo:rustc-env=KANIDM_PKG_VERSION={} {}",
version, commit_rev
);
} else {
println!("cargo:rustc-env=KANIDM_PKG_VERSION={}", version);
let kanidm_pkg_version = match option_env!("KANIDM_PKG_COMMIT_REV") {
Some(commit_rev) => format!("{} {}", env!("CARGO_PKG_VERSION"), commit_rev),
None => env!("CARGO_PKG_VERSION").to_string(),
};
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");
if version_pre == "dev" {
println!("cargo:rustc-env=KANIDM_PRE_RELEASE=1");

View file

@ -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
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),
None => "".to_string(),
};

View file

@ -7,6 +7,8 @@
# - set up a test oauth2 rp (https://kanidm.com)
# - prompt to reset testuser's creds online
set -e
if [ -n "${BUILD_MODE}" ]; then
BUILD_MODE="--${BUILD_MODE}"
else

View file

@ -256,8 +256,13 @@ impl ServerConfig {
let ignorable_build_fields = [
"KANIDM_CPU_FLAGS",
"KANIDM_CPU_FLAGS",
"KANIDM_DEFAULT_CONFIG_PATH",
"KANIDM_DEFAULT_CONFIG_PATH",
"KANIDM_DEFAULT_UNIX_SHELL_PATH",
"KANIDM_DEFAULT_UNIX_SHELL_PATH",
"KANIDM_HTMX_UI_PKG_PATH",
"KANIDM_PKG_VERSION_HASH",
"KANIDM_PKG_VERSION",
"KANIDM_PRE_RELEASE",
"KANIDM_PROFILE_NAME",

View 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()
}

View file

@ -43,7 +43,7 @@ pub struct 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 {
let filetype = match &self.filetype {
Some(val) => {

View file

@ -1,6 +1,6 @@
mod apidocs;
pub(crate) mod cache_buster;
pub(crate) mod errors;
mod extractors;
mod generic;
mod javascript;
@ -243,7 +243,7 @@ pub async fn create_https_server(
"frame-ancestors 'none'; ",
"img-src 'self' data:; ",
"worker-src 'none'; ",
"script-src 'self' 'unsafe-eval'{};"
"script-src 'self' 'unsafe-eval'{};",
),
js_checksums
);

View file

@ -76,12 +76,11 @@ pub(crate) async fn ui_handler_generic(
jsfiles.push(jsfile.clone().as_tag())
};
let jstags = jsfiles.join("\n");
let body = format!(
let body: String = format!(
include_str!("ui_html.html"),
domain_info.display_name(),
jstags,
jstags = jsfiles.join("\n"),
cache_buster_key = crate::https::cache_buster::get_cache_buster_key(),
display_name = domain_info.display_name()
);
let mut res = Response::new(body);

View file

@ -5,35 +5,41 @@
<meta charset="utf-8" />
<meta name="theme-color" content="white" />
<meta name="viewport" content="width=device-width" />
<title>{}</title>
<link rel="icon" href="/pkg/img/favicon.png" />
<title>{display_name}</title>
<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"
href="/pkg/img/logo-180.png" />
href="/pkg/img/logo-180.png?v={cache_buster_key}" />
<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"
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"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" />
<link rel="stylesheet" href="/pkg/style.css" />
<link rel="stylesheet"
href="/pkg/style.css?v={cache_buster_key}" />
{}
{jstags}
</head>
<body class="flex-column d-flex h-100">
<main class="flex-shrink-0 form-signin">
<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" />
<h3>Kanidm is loading, please wait... </h3>
</center>
</main>
<footer class="footer mt-auto py-3 bg-light text-end">
<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>
</footer>
</body>

View file

@ -641,14 +641,14 @@ pub fn cert_generate_core(config: &Configuration) {
let (tls_key_path, tls_chain_path) = match &config.tls_config {
Some(tls_config) => (tls_config.key.as_path(), tls_config.chain.as_path()),
None => {
error!("Unable to find tls configuration");
error!("Unable to find TLS configuration");
std::process::exit(1);
}
};
if tls_key_path.exists() && tls_chain_path.exists() {
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;
}

View file

@ -870,7 +870,7 @@ async fn repl_acceptor(
// Handle *reloads*
/*
_ = reload.recv() => {
info!("initiate tls reload");
info!("Initiating TLS reload");
continue
}
*/

View file

@ -10,12 +10,16 @@
<div class="col-md-3">
<div class="card text-center">
(% 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">
(% 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 %)
<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 %)
</a>
<label for="(( name ))">(( display_name ))</label>

View file

@ -7,18 +7,16 @@
<title>(% block title %)(( title )) - Kanidm(% endblock %)</title>
<link rel="icon" href="/pkg/img/favicon.png" />
<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" />
(% include "base_icons.html" %)
<link rel="stylesheet" href="/pkg/external/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"/>
<script src="/pkg/external/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"></script>
<link rel="stylesheet" href="/pkg/style.css" />
<link rel="stylesheet"
href="/pkg/external/bootstrap.min.css?v=((crate::https::cache_buster::get_cache_buster_key()))"
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 %)
</head>
@ -26,7 +24,8 @@
(% block body %)(% endblock %)
<footer class="footer mt-auto py-3 bg-light text-end">
<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>
</footer>
</body>

View file

@ -4,22 +4,23 @@
<meta charset="utf-8" />
<meta name="theme-color" content="white" />
<meta name="viewport" content="width=device-width" />
<meta name="htmx-config" content='{ "includeIndicatorStyles": false }' />
<title>(% block title %)(( title )) - Kanidm(% endblock %)</title>
<link rel="icon" href="/pkg/img/favicon.png" />
<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" />
(% include "base_icons.html" %)
<link rel="stylesheet" href="/pkg/external/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"/>
<script src="/pkg/external/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"></script>
<script src="/pkg/external/htmx.min.1.9.12.js" integrity="sha384-ujb1lZYygJmzgSwoxRggbCHcjc0rB2XoQrxeTUQyRjrOnlCoYta87iKBWq3EsdM2"></script>
<link rel="stylesheet" href="/pkg/style.css" />
<link rel="stylesheet"
href="/pkg/external/bootstrap.min.css?v=((crate::https::cache_buster::get_cache_buster_key()))"
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>
<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 %)
</head>
@ -28,9 +29,9 @@
(% block body %)(% endblock %)
<footer class="footer mt-auto py-3 bg-light text-end">
<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>
</footer>
</body>
</html>

View 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()))" />

View file

@ -4,20 +4,26 @@
(% 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 -->
<script src="/pkg/external/cred_update.js"></script>
<script src="/pkg/external/base64.js" async></script>
<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 %)
(% block body %)
<main class="flex-shrink-0 container form-signin" id="cred-reset-form">
<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>
<h3>(( domain_info.display_name() ))</h3>
</center>
<form class="mb-3">
<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
id="token"
name="token"
@ -27,23 +33,27 @@
class='form-control is-invalid'
(% else %)
class='form-control'
(% endif %)
>
(% endif %)>
(% if wrong_code %)
<div id="unknown-reset-token-validation-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>
<div id="unknown-reset-token-validation-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>
</div>
(% endif %)
</div>
</form>
<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
</button>
<button class="btn btn-primary"
hx-get="/ui/reset"
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"
hx-swap="outerHTML"
type="submit">
Submit
</button>

View file

@ -1,23 +1,31 @@
<script type="module" src="/pkg/modules/cred_update.mjs" async></script>
<script src="/pkg/external/base64.js" async></script>
<script type="module"
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>
(% match ext_cred_portal %)
(% when CUExtPortal::None %)
(% when CUExtPortal::Hidden %)
<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
available.</p>
(% when CUExtPortal::Some(url) %)
<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
available.</p>
<a href="(( url ))">Visit the external account portal</a>
(% endmatch %)
(% if warnings.len() > 0 %)
<hr class="my-4">
(% for warning in warnings %)
(% let is_danger = [CURegWarning::WebauthnAttestationUnsatisfiable, CURegWarning::Unsatisfiable].contains(warning) %)
(% let is_danger = [CURegWarning::WebauthnAttestationUnsatisfiable,
CURegWarning::Unsatisfiable].contains(warning) %)
(% if is_danger %)
<div class='alert alert-danger' role="alert">
(% else %)
@ -26,7 +34,9 @@
(% match warning %)
(% 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
add TOTP or remove your password in favour of passkeys to
submit.
(% when CURegWarning::PasskeyRequired %)
Passkeys are required for your account.
(% when CURegWarning::AttestedPasskeyRequired %)
@ -34,9 +44,11 @@
(% when CURegWarning::AttestedResidentKeyRequired %)
Attested Resident Keys are required for your account.
(% when CURegWarning::WebauthnAttestationUnsatisfiable %)
A webauthn attestation 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
An account policy conflict has occurred and you will not be able
to save your credentials
(% endmatch %)
(% if is_danger %)
@ -51,7 +63,9 @@
(% match attested_passkeys_state %)
(% when CUCredState::Modifiable %)
(% 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"
hx-post="/ui/reset/add_passkey" hx-vals='{"class": "Attested"}'
hx-target="#credentialUpdateDynamicSection">
Add Attested Passkey
</button>
(% when CUCredState::DeleteOnly %)
@ -68,20 +82,24 @@
(% 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"
<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">
<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>
<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 %)
(% if passkeys.len() > 0 %)
(% include "credentials_update_passkeys.html" %)
@ -94,25 +112,33 @@
(% let primary_state = primary_state %)
(% 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"
aria-live="assertive" aria-atomic="true">
<div class="toast-body">
<span class="d-flex align-items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-floppy2-fill" viewBox="0 0 16 16">
<svg xmlns="http://www.w3.org/2000/svg" width="16"
height="16" fill="currentColor"
class="bi bi-floppy2-fill" viewBox="0 0 16 16">
<path d="M12 2h-2v3h2z" />
<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"/>
<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" />
</svg>
<b class="px-1">Careful</b>- save when you're done:
</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-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"
type="submit"
hx-post="/ui/api/cu_commit"
hx-boost="false"
(% if !warnings.is_empty() %)disabled(% endif %)
>Save Changes</button>
(% if !warnings.is_empty() %)disabled(% endif
%)>Save Changes</button>
</span>
</div>
</div>

View file

@ -9,9 +9,12 @@
<main id="main" class="flex-shrink-0 form-signin">
<center>
(% if domain_custom_image %)
<img src="/ui/images/domain" alt="Kanidm" class="kanidm_logo"/>
<img src="/ui/images/domain"
alt="Kanidm" class="kanidm_logo" />
(% else %)
<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" />
(% endif %)
<h3>Kanidm</h3>
</center>

View file

@ -5,13 +5,19 @@
(( chal|safe ))
</script>
<script src="/pkg/external/base64.js" async></script>
<script src="/pkg/pkhtml.js" defer></script>
<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 %)
<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 %)
<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 %)
(% endblock %)

View file

@ -1,22 +1,33 @@
<nav class="(( crate::https::ui::CSS_NAVBAR_NAV ))">
<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-->
<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">
<img src="/pkg/img/logo-square.svg" alt="Toggle navigation" class="navbar-toggler-img" />
<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">
<img
src="/pkg/img/logo-square.svg?v=((crate::https::cache_buster::get_cache_buster_key()))"
alt="Toggle navigation" class="navbar-toggler-img" />
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="(( crate::https::ui::CSS_NAVBAR_LINKS_UL ))">
<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 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 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>
</ul>
</div>

View file

@ -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-content">
<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 class="modal-body text-center">
<p>Error Code: (( error_message ))</p>

View file

@ -7,7 +7,9 @@
<div class="modal-body text-center">
Are you sure you'd like to log out?
<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 class="modal-footer">
<a href="/ui/logout" hx-boost="false">

View file

@ -1,19 +1,26 @@
(% 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 %)">
<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>
(% endmacro %)
<main class="container-xxl pb-5">
<div class="d-flex flex-sm-row flex-column">
<div class="list-group side-menu flex-shrink-0">
(% call side_menu_item("Profile", "/ui/profile", ProfileMenuItems::UserProfile, "person") %)
(% call side_menu_item("SSH Keys", "/ui/ssh_keys", ProfileMenuItems::SshKeys, "key") %)
(% call side_menu_item("Profile", "/ui/profile",
ProfileMenuItems::UserProfile, "person") %)
(% call side_menu_item("SSH Keys", "/ui/ssh_keys",
ProfileMenuItems::SshKeys, "key") %)
(% 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 %)
(% 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 id="settings-window" class="flex-grow-1 ps-sm-4 pt-sm-0 pt-4">
<div class="(( crate::https::ui::CSS_PAGE_HEADER ))">

View file

@ -1,4 +1,4 @@
use jsonschema::JSONSchema;
use jsonschema::Validator;
use serde::{Deserialize, Serialize};
use tracing::info;
@ -29,7 +29,7 @@ async fn check_that_the_swagger_api_loads(rsclient: kanidm_client::KanidmClient)
.unwrap();
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));
let result = compiled.validate(&instance);
if let Err(errors) = result {