232 lines
11 KiB
Rust
232 lines
11 KiB
Rust
//! Per-vault options, mirroring XCA's database settings.
|
|
use serde_json::json;
|
|
use sycamore::prelude::*;
|
|
use wasm_bindgen_futures::spawn_local;
|
|
|
|
use crate::api;
|
|
use crate::components::crypto::{key_parameter_input, key_type_select, signature_hash_input};
|
|
use crate::components::nav::{app_nav, spinner, whoami};
|
|
use crate::components::ui::{page_header, toast_error};
|
|
use crate::model::{Me, OptionsPage};
|
|
|
|
pub fn SettingsPage(vault_id: String) -> View {
|
|
let id: &'static str = Box::leak(vault_id.clone().into_boxed_str());
|
|
let me = create_signal(None::<Me>);
|
|
let options = create_signal(None::<OptionsPage>);
|
|
let error = create_signal(None::<String>);
|
|
let info = create_signal(None::<String>);
|
|
let loading = create_signal(true);
|
|
let saving = create_signal(false);
|
|
let version = create_signal(0i64);
|
|
|
|
let default_hash = create_signal("sha256".to_string());
|
|
let serial_len = create_signal("64".to_string());
|
|
let explicit_dn = create_signal("C,ST,L,O,OU,CN,emailAddress".to_string());
|
|
let mandatory_dn = create_signal(String::new());
|
|
let disable_netscape = create_signal(false);
|
|
let hide_unusable = create_signal(false);
|
|
let default_key_type = create_signal("rsa".to_string());
|
|
let default_key_size = create_signal("2048".to_string());
|
|
let default_key_curve = create_signal("secp256r1".to_string());
|
|
let default_validity = create_signal("365".to_string());
|
|
let default_validity_unit = create_signal("days".to_string());
|
|
|
|
spawn_local(async move {
|
|
if let Ok(user) = api::me().await {
|
|
me.set(Some(user));
|
|
}
|
|
if let Ok(page) = api::options(id).await {
|
|
options.set(Some(page));
|
|
}
|
|
match api::vault_settings(&id).await {
|
|
Ok(value) => {
|
|
version.set(value.get("version").and_then(|v| v.as_i64()).unwrap_or(0));
|
|
if let Some(settings) = value.get("settings") {
|
|
if let Some(v) = settings.get("default_hash").and_then(|v| v.as_str()) {
|
|
default_hash.set(v.to_string());
|
|
}
|
|
if let Some(v) = settings.get("serial_len_bits").and_then(|v| v.as_i64()) {
|
|
serial_len.set(v.to_string());
|
|
}
|
|
if let Some(items) = settings.get("explicit_dn").and_then(|v| v.as_array()) {
|
|
explicit_dn.set(items.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>().join(","));
|
|
}
|
|
if let Some(items) = settings.get("mandatory_dn").and_then(|v| v.as_array()) {
|
|
mandatory_dn.set(items.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>().join(","));
|
|
}
|
|
disable_netscape.set(settings.get("disable_netscape").and_then(|v| v.as_bool()).unwrap_or(false));
|
|
hide_unusable.set(settings.get("hide_unusable").and_then(|v| v.as_bool()).unwrap_or(false));
|
|
if let Some(key) = settings.get("default_key") {
|
|
if let Some(v) = key.get("type").and_then(|v| v.as_str()) {
|
|
default_key_type.set(v.to_string());
|
|
}
|
|
if let Some(v) = key.get("size").and_then(|v| v.as_i64()) {
|
|
default_key_size.set(v.to_string());
|
|
}
|
|
if let Some(v) = key.get("curve").and_then(|v| v.as_str()) {
|
|
default_key_curve.set(v.to_string());
|
|
}
|
|
}
|
|
if let Some(validity) = settings.get("default_validity") {
|
|
if let Some(v) = validity.get("amount").and_then(|v| v.as_i64()) {
|
|
default_validity.set(v.to_string());
|
|
}
|
|
if let Some(v) = validity.get("unit").and_then(|v| v.as_str()) {
|
|
default_validity_unit.set(v.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Err(message) => error.set(Some(message)),
|
|
}
|
|
loading.set(false);
|
|
});
|
|
|
|
let save = {
|
|
let id = id.clone();
|
|
move |_| {
|
|
let id = id.clone();
|
|
let settings = json!({
|
|
"default_hash": default_hash.get_clone(),
|
|
"serial_len_bits": serial_len.get_clone().parse::<i64>().unwrap_or(64),
|
|
"explicit_dn": explicit_dn.get_clone().split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect::<Vec<_>>(),
|
|
"mandatory_dn": mandatory_dn.get_clone().split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect::<Vec<_>>(),
|
|
"disable_netscape": disable_netscape.get(),
|
|
"hide_unusable": hide_unusable.get(),
|
|
"default_key": {
|
|
"type": default_key_type.get_clone(),
|
|
"size": default_key_size.get_clone().parse::<i64>().unwrap_or(2048),
|
|
"curve": if default_key_type.get_clone() == "ec" {
|
|
json!(default_key_curve.get_clone())
|
|
} else {
|
|
serde_json::Value::Null
|
|
},
|
|
},
|
|
"default_validity": {
|
|
"amount": default_validity.get_clone().parse::<i64>().unwrap_or(365),
|
|
"unit": default_validity_unit.get_clone(),
|
|
},
|
|
});
|
|
saving.set(true);
|
|
spawn_local(async move {
|
|
match api::update_vault_settings(&id, &settings, version.get()).await {
|
|
Ok(value) => {
|
|
version.set(value.get("version").and_then(|v| v.as_i64()).unwrap_or(version.get()));
|
|
info.set(Some("Settings saved.".to_string()));
|
|
}
|
|
Err(message) if message == api::LOGIN_REQUIRED => {}
|
|
Err(message) => error.set(Some(message)),
|
|
}
|
|
saving.set(false);
|
|
});
|
|
}
|
|
};
|
|
|
|
let content = move || {
|
|
if loading.get() {
|
|
return spinner();
|
|
}
|
|
view! {
|
|
(page_header(
|
|
"Vault settings",
|
|
"These options are stored with the vault and mirror XCA's database preferences.",
|
|
view! { a(class="btn btn-outline-light", href=format!("/vaults/{id}")) { "Back" } },
|
|
))
|
|
(if let Some(message) = info.get_clone() {
|
|
view! { div(class="alert alert-success") { (message) } }
|
|
} else {
|
|
view! {}
|
|
})
|
|
div(class="card") {
|
|
div(class="card-body") {
|
|
div(class="form-section") {
|
|
div(class="form-section-title") { "Cryptography" }
|
|
div(class="row g-3") {
|
|
div(class="col-md-4") {
|
|
(signature_hash_input(
|
|
"Default signature hash",
|
|
options,
|
|
|| None,
|
|
default_hash,
|
|
))
|
|
}
|
|
div(class="col-md-4") {
|
|
label(class="form-label") { "Serial number length (bits)" }
|
|
input(class="form-control", bind:value=serial_len)
|
|
}
|
|
div(class="col-md-4") {
|
|
label(class="form-label") { "Default validity" }
|
|
div(class="input-group") {
|
|
input(class="form-control", bind:value=default_validity)
|
|
select(class="form-select", bind:value=default_validity_unit) {
|
|
option(value="days") { "Days" }
|
|
option(value="months") { "Months" }
|
|
option(value="years") { "Years" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
div(class="form-section") {
|
|
div(class="form-section-title") { "Distinguished name" }
|
|
div(class="row g-3") {
|
|
div(class="col-md-6") {
|
|
label(class="form-label") { "Explicit subject entries" }
|
|
input(class="form-control", bind:value=explicit_dn)
|
|
div(class="form-text") { "Comma-separated short names, e.g. C,ST,L,O,OU,CN,emailAddress." }
|
|
}
|
|
div(class="col-md-6") {
|
|
label(class="form-label") { "Mandatory subject entries" }
|
|
input(class="form-control", bind:value=mandatory_dn)
|
|
}
|
|
}
|
|
}
|
|
div(class="form-section") {
|
|
div(class="form-section-title") { "Editor defaults" }
|
|
div(class="row g-3") {
|
|
div(class="col-md-4") {
|
|
label(class="form-label") { "Default key type" }
|
|
(key_type_select(options, default_key_type))
|
|
}
|
|
div(class="col-md-4") {
|
|
(key_parameter_input(
|
|
options,
|
|
default_key_type,
|
|
default_key_size,
|
|
default_key_curve,
|
|
))
|
|
}
|
|
div(class="col-md-4 d-flex align-items-end") {
|
|
button(class="btn btn-primary", disabled=saving.get(), on:click=save) {
|
|
(if saving.get() { "Saving…" } else { "Save settings" })
|
|
}
|
|
}
|
|
}
|
|
div(class="form-check mt-3") {
|
|
input(class="form-check-input", r#type="checkbox", bind:checked=disable_netscape)
|
|
label(class="form-check-label") { "Disable legacy Netscape extensions" }
|
|
}
|
|
div(class="form-check") {
|
|
input(class="form-check-input", r#type="checkbox", bind:checked=hide_unusable)
|
|
label(class="form-check-label") { "Hide expired and revoked certificates by default" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
let nav = move || match me.get_clone() {
|
|
Some(user) => whoami(&user),
|
|
None => view! {},
|
|
};
|
|
|
|
view! {
|
|
(app_nav("/", Some(id), nav))
|
|
main(class="container page-shell py-4") {
|
|
(toast_error(error))
|
|
(content)
|
|
}
|
|
}
|
|
}
|