Close XCA parity gaps: advanced extensions, CA/revocation UI, imports, ZIP export, E2E
This commit is contained in:
1 parent
4f3a632f4b
commit
5eb7866f5b
27 files changed
+2054
-134
No files matched your search
@@ -4,11 +4,14 @@ use sycamore::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
|
||||
use crate::api;
|
||||
use crate::components::extension_form::{
|
||||
build_extra_extensions, extension_fields, load_extra_extensions, ExtensionSignals,
|
||||
};
|
||||
use crate::components::nav::{app_nav, whoami};
|
||||
use crate::components::ui::{page_header, toast_error};
|
||||
use crate::model::{CertificateDetail, CertificateSummary, KeySummary, Me, TemplateSummary};
|
||||
|
||||
pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
|
||||
let id: &'static str = Box::leak(vault_id.clone().into_boxed_str());
|
||||
let me = create_signal(None::<Me>);
|
||||
let keys = create_signal(Vec::<KeySummary>::new());
|
||||
@@ -59,12 +62,40 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
let ski = create_signal(true);
|
||||
let aki = create_signal(true);
|
||||
let ocsp_staple = create_signal(false);
|
||||
let crl_urls = create_signal(String::new());
|
||||
let aia_ocsp = create_signal(String::new());
|
||||
let aia_ca_issuers = create_signal(String::new());
|
||||
let nc_permitted = create_signal(String::new());
|
||||
let nc_excluded = create_signal(String::new());
|
||||
let policy_oids = create_signal(String::new());
|
||||
let policy_cps = create_signal(String::new());
|
||||
let policy_require = create_signal(String::new());
|
||||
let policy_inhibit = create_signal(String::new());
|
||||
let inhibit_any = create_signal(String::new());
|
||||
let advanced_json = create_signal(String::new());
|
||||
let ext = ExtensionSignals {
|
||||
crl_urls,
|
||||
aia_ocsp,
|
||||
aia_ca_issuers,
|
||||
nc_permitted,
|
||||
nc_excluded,
|
||||
policy_oids,
|
||||
policy_cps,
|
||||
policy_require,
|
||||
policy_inhibit,
|
||||
inhibit_any,
|
||||
advanced_json,
|
||||
};
|
||||
|
||||
let preview_text = create_signal(String::new());
|
||||
let validity_mode = create_signal("duration".to_string());
|
||||
let validity_amount = create_signal("365".to_string());
|
||||
let validity_unit = create_signal("days".to_string());
|
||||
let midnight = create_signal(false);
|
||||
let not_before_text = create_signal(String::new());
|
||||
let not_after_text = create_signal(String::new());
|
||||
let no_expiration = create_signal(false);
|
||||
|
||||
|
||||
spawn_local(async move {
|
||||
if let Ok(user) = api::me().await {
|
||||
if me.is_alive() {
|
||||
@@ -75,11 +106,134 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
keys.set(page.results);
|
||||
}
|
||||
if let Ok(page) = api::certificates(&id).await {
|
||||
signers.set(page.results.into_iter().filter(|cert| cert.is_ca && cert.has_private_key).collect());
|
||||
signers.set(
|
||||
page.results
|
||||
.into_iter()
|
||||
.filter(|cert| cert.is_ca && cert.has_private_key)
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
if let Ok(page) = api::templates(&id).await {
|
||||
templates.set(page.results);
|
||||
}
|
||||
if let Ok(value) = api::vault_settings(&id).await {
|
||||
if let Some(settings) = value.get("settings") {
|
||||
if let Some(hash) = settings.get("default_hash").and_then(|v| v.as_str()) {
|
||||
signature_hash.set(hash.to_string());
|
||||
}
|
||||
if let Some(key) = settings.get("default_key") {
|
||||
if let Some(kind) = key.get("type").and_then(|v| v.as_str()) {
|
||||
key_type.set(kind.to_string());
|
||||
}
|
||||
if let Some(size) = key.get("size").and_then(|v| v.as_i64()) {
|
||||
key_size.set(size.to_string());
|
||||
}
|
||||
}
|
||||
if let Some(validity) = settings.get("default_validity") {
|
||||
if let Some(amount) = validity.get("amount").and_then(|v| v.as_i64()) {
|
||||
validity_amount.set(amount.to_string());
|
||||
}
|
||||
if let Some(unit) = validity.get("unit").and_then(|v| v.as_str()) {
|
||||
validity_unit.set(unit.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !request_mode {
|
||||
if let Some(cert_id) = from_cert.clone() {
|
||||
if let Ok(value) = api::certificate(&id, &cert_id).await {
|
||||
name.set(format!("{} copy", value.summary.name));
|
||||
comment.set(value.summary.comment.clone());
|
||||
if let Some(key) = value.summary.key_id.clone() {
|
||||
key_id.set(key);
|
||||
}
|
||||
if let Some(spec) = value
|
||||
.cert_spec
|
||||
.get("signature_hash")
|
||||
.and_then(|v| v.as_str())
|
||||
{
|
||||
signature_hash.set(spec.to_string());
|
||||
}
|
||||
for entry in &value.subject_entries {
|
||||
match entry.name.as_str() {
|
||||
"CN" => cn.set(entry.value.clone()),
|
||||
"O" => organization.set(entry.value.clone()),
|
||||
"OU" => org_unit.set(entry.value.clone()),
|
||||
"C" => country.set(entry.value.clone()),
|
||||
"ST" => state.set(entry.value.clone()),
|
||||
"L" => locality.set(entry.value.clone()),
|
||||
"emailAddress" => email.set(entry.value.clone()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let spec = &value.extension_spec;
|
||||
load_extra_extensions(&ext, spec);
|
||||
if let Some(bc) = spec.get("basic_constraints") {
|
||||
if bc.get("ca").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
ca_mode.set("ca".to_string());
|
||||
if let Some(len) = bc.get("path_length").and_then(|v| v.as_i64()) {
|
||||
path_length.set(len.to_string());
|
||||
}
|
||||
} else if bc.get("ca").is_some() {
|
||||
ca_mode.set("end".to_string());
|
||||
}
|
||||
bc_critical
|
||||
.set(bc.get("critical").and_then(|v| v.as_bool()).unwrap_or(true));
|
||||
}
|
||||
if let Some(ku) = spec.get("key_usage") {
|
||||
let usages = string_array(ku.get("usages"));
|
||||
ku_digital.set(usages.iter().any(|s| s == "digital_signature"));
|
||||
ku_encipher.set(usages.iter().any(|s| s == "key_encipherment"));
|
||||
ku_key_agreement.set(usages.iter().any(|s| s == "key_agreement"));
|
||||
ku_cert_sign.set(usages.iter().any(|s| s == "key_cert_sign"));
|
||||
ku_crl_sign.set(usages.iter().any(|s| s == "crl_sign"));
|
||||
ku_critical
|
||||
.set(ku.get("critical").and_then(|v| v.as_bool()).unwrap_or(true));
|
||||
}
|
||||
if let Some(eku) = spec.get("extended_key_usage") {
|
||||
let usages = string_array(eku.get("usages"));
|
||||
eku_server.set(usages.iter().any(|s| s == "serverAuth"));
|
||||
eku_client.set(usages.iter().any(|s| s == "clientAuth"));
|
||||
eku_code.set(usages.iter().any(|s| s == "codeSigning"));
|
||||
eku_email.set(usages.iter().any(|s| s == "emailProtection"));
|
||||
eku_time.set(usages.iter().any(|s| s == "timeStamping"));
|
||||
eku_ocsp.set(usages.iter().any(|s| s == "OCSPSigning"));
|
||||
eku_critical.set(
|
||||
eku.get("critical")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false),
|
||||
);
|
||||
}
|
||||
if let Some(san) = spec.get("subject_alt_name") {
|
||||
let entries = san
|
||||
.get("entries")
|
||||
.and_then(|v| v.as_array())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let text = entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let kind =
|
||||
entry.get("type").and_then(|v| v.as_str()).unwrap_or("dns");
|
||||
let value =
|
||||
entry.get("value").and_then(|v| v.as_str()).unwrap_or("");
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format!("{}:{}", kind.to_uppercase(), value))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
san_text.set(text);
|
||||
}
|
||||
ski.set(spec.get("subject_key_identifier").is_some());
|
||||
aki.set(spec.get("authority_key_identifier").is_some());
|
||||
ocsp_staple.set(spec.get("tls_feature").is_some());
|
||||
no_expiration.set(value.summary.no_expiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
if request_mode {
|
||||
if let Ok(value) = api::request(&id, &request_id_for_load).await {
|
||||
name.set(format!("{} certificate", value.summary.name));
|
||||
@@ -101,7 +255,11 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
if let Some(first) = value.subject_entries.iter().find(|e| e.name == "L") {
|
||||
locality.set(first.value.clone());
|
||||
}
|
||||
if let Some(first) = value.subject_entries.iter().find(|e| e.name == "emailAddress") {
|
||||
if let Some(first) = value
|
||||
.subject_entries
|
||||
.iter()
|
||||
.find(|e| e.name == "emailAddress")
|
||||
{
|
||||
email.set(first.value.clone());
|
||||
}
|
||||
}
|
||||
@@ -120,16 +278,21 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
match api::template(&id, &template).await {
|
||||
Ok(template) => {
|
||||
if let Some(settings) = template.settings.get("extensions") {
|
||||
load_extra_extensions(&ext, settings);
|
||||
if let Some(bc) = settings.get("basic_constraints") {
|
||||
if bc.get("ca").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
ca_mode.set("ca".to_string());
|
||||
if let Some(len) = bc.get("path_length").and_then(|v| v.as_i64()) {
|
||||
if let Some(len) =
|
||||
bc.get("path_length").and_then(|v| v.as_i64())
|
||||
{
|
||||
path_length.set(len.to_string());
|
||||
}
|
||||
} else if bc.get("ca").is_some() {
|
||||
ca_mode.set("end".to_string());
|
||||
}
|
||||
bc_critical.set(bc.get("critical").and_then(|v| v.as_bool()).unwrap_or(true));
|
||||
bc_critical.set(
|
||||
bc.get("critical").and_then(|v| v.as_bool()).unwrap_or(true),
|
||||
);
|
||||
}
|
||||
if let Some(ku) = settings.get("key_usage") {
|
||||
let usages = string_array(ku.get("usages"));
|
||||
@@ -138,7 +301,9 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
ku_key_agreement.set(usages.iter().any(|s| s == "key_agreement"));
|
||||
ku_cert_sign.set(usages.iter().any(|s| s == "key_cert_sign"));
|
||||
ku_crl_sign.set(usages.iter().any(|s| s == "crl_sign"));
|
||||
ku_critical.set(ku.get("critical").and_then(|v| v.as_bool()).unwrap_or(true));
|
||||
ku_critical.set(
|
||||
ku.get("critical").and_then(|v| v.as_bool()).unwrap_or(true),
|
||||
);
|
||||
}
|
||||
if let Some(eku) = settings.get("extended_key_usage") {
|
||||
let usages = string_array(eku.get("usages"));
|
||||
@@ -148,15 +313,37 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
eku_email.set(usages.iter().any(|s| s == "emailProtection"));
|
||||
eku_time.set(usages.iter().any(|s| s == "timeStamping"));
|
||||
eku_ocsp.set(usages.iter().any(|s| s == "OCSPSigning"));
|
||||
eku_critical.set(eku.get("critical").and_then(|v| v.as_bool()).unwrap_or(false));
|
||||
eku_critical.set(
|
||||
eku.get("critical")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false),
|
||||
);
|
||||
}
|
||||
if let Some(san) = settings.get("subject_alt_name") {
|
||||
let entries = san.get("entries").and_then(|v| v.as_array()).cloned().unwrap_or_default();
|
||||
let text = entries.iter().filter_map(|entry| {
|
||||
let kind = entry.get("type").and_then(|v| v.as_str()).unwrap_or("dns");
|
||||
let value = entry.get("value").and_then(|v| v.as_str()).unwrap_or("");
|
||||
if value.is_empty() { None } else { Some(format!("{}:{}", kind.to_uppercase(), value)) }
|
||||
}).collect::<Vec<_>>().join(", ");
|
||||
let entries = san
|
||||
.get("entries")
|
||||
.and_then(|v| v.as_array())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let text = entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let kind = entry
|
||||
.get("type")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("dns");
|
||||
let value = entry
|
||||
.get("value")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format!("{}:{}", kind.to_uppercase(), value))
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
san_text.set(text);
|
||||
}
|
||||
ski.set(settings.get("subject_key_identifier").is_some());
|
||||
@@ -165,7 +352,11 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
if let Some(subject) = template.subject.as_array() {
|
||||
for entry in subject {
|
||||
let key = entry.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let value = entry.get("value").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
let value = entry
|
||||
.get("value")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
match key {
|
||||
"CN" => cn.set(value),
|
||||
"O" => organization.set(value),
|
||||
@@ -185,7 +376,12 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
if let Some(unit) = validity.get("unit").and_then(|v| v.as_str()) {
|
||||
validity_unit.set(unit.to_string());
|
||||
}
|
||||
midnight.set(validity.get("midnight").and_then(|v| v.as_bool()).unwrap_or(false));
|
||||
midnight.set(
|
||||
validity
|
||||
.get("midnight")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(message) if message == api::LOGIN_REQUIRED => {}
|
||||
@@ -227,7 +423,8 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
"path_length": path_length.get_clone().parse::<i64>().ok(),
|
||||
});
|
||||
} else if mode == "end" {
|
||||
extensions["basic_constraints"] = json!({"critical": crit, "ca": false, "path_length": null});
|
||||
extensions["basic_constraints"] =
|
||||
json!({"critical": crit, "ca": false, "path_length": null});
|
||||
}
|
||||
let usages = [
|
||||
(ku_digital.get(), "digital_signature"),
|
||||
@@ -253,7 +450,8 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
.filter(|(checked, _)| *checked)
|
||||
.map(|(_, name)| json!(name))
|
||||
.collect::<Vec<_>>();
|
||||
extensions["extended_key_usage"] = json!({"critical": eku_critical.get(), "usages": eku});
|
||||
extensions["extended_key_usage"] =
|
||||
json!({"critical": eku_critical.get(), "usages": eku});
|
||||
let san = parse_san(&san_text.get_clone());
|
||||
if !san.is_empty() {
|
||||
extensions["subject_alt_name"] = json!({"critical": false, "entries": san});
|
||||
@@ -265,7 +463,21 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
extensions["authority_key_identifier"] = json!({"critical": false});
|
||||
}
|
||||
if ocsp_staple.get() {
|
||||
extensions["tls_feature"] = json!({"critical": false, "features": ["status_request"]});
|
||||
extensions["tls_feature"] =
|
||||
json!({"critical": false, "features": ["status_request"]});
|
||||
}
|
||||
match build_extra_extensions(&ext) {
|
||||
Ok(extra) => {
|
||||
if let Some(object) = extra.as_object() {
|
||||
for (key, value) in object {
|
||||
extensions[key] = value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(message) => {
|
||||
error.set(Some(message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
let signer = signer_id.get_clone();
|
||||
let mut payload = json!({
|
||||
@@ -275,12 +487,22 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
"extensions": extensions,
|
||||
"signature_hash": signature_hash.get_clone(),
|
||||
"signer_id": if signer == "self" { serde_json::Value::Null } else { json!(signer) },
|
||||
"validity": {
|
||||
"amount": validity_amount.get_clone().parse::<i64>().unwrap_or(365),
|
||||
"unit": validity_unit.get_clone(),
|
||||
"midnight": midnight.get(),
|
||||
"validity": if no_expiration.get() {
|
||||
json!({"no_expiration": true})
|
||||
} else if validity_mode.get_clone() == "absolute" {
|
||||
json!({})
|
||||
} else {
|
||||
json!({
|
||||
"amount": validity_amount.get_clone().parse::<i64>().unwrap_or(365),
|
||||
"unit": validity_unit.get_clone(),
|
||||
"midnight": midnight.get(),
|
||||
})
|
||||
},
|
||||
});
|
||||
if validity_mode.get_clone() == "absolute" && !no_expiration.get() {
|
||||
payload["not_before"] = json!(not_before_text.get_clone());
|
||||
payload["not_after"] = json!(not_after_text.get_clone());
|
||||
}
|
||||
if !request_id.is_empty() {
|
||||
payload["request_id"] = json!(request_id);
|
||||
payload["modify_subject"] = json!(true);
|
||||
@@ -303,26 +525,158 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
match api::create_certificate(&id, &payload).await {
|
||||
Ok(cert) => {
|
||||
saving.set(false);
|
||||
sycamore_router::navigate(&format!("/vaults/{}/certificates/{}", id, cert.summary.id));
|
||||
sycamore_router::navigate(&format!(
|
||||
"/vaults/{}/certificates/{}",
|
||||
id, cert.summary.id
|
||||
));
|
||||
return;
|
||||
}
|
||||
Err(message) if message == api::LOGIN_REQUIRED => {}
|
||||
Err(message) => error.set(Some(message)),
|
||||
}
|
||||
if saving.is_alive() { saving.set(false); }
|
||||
if saving.is_alive() {
|
||||
saving.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let validate = {
|
||||
let id = id.clone();
|
||||
move |_| {
|
||||
let subject = build_subject(&[
|
||||
("C", country.get_clone()),
|
||||
("ST", state.get_clone()),
|
||||
("L", locality.get_clone()),
|
||||
("O", organization.get_clone()),
|
||||
("OU", org_unit.get_clone()),
|
||||
("CN", cn.get_clone()),
|
||||
("emailAddress", email.get_clone()),
|
||||
]);
|
||||
let mut extensions = json!({});
|
||||
let mode = ca_mode.get_clone();
|
||||
let crit = bc_critical.get();
|
||||
if mode == "ca" {
|
||||
extensions["basic_constraints"] = json!({"critical": crit, "ca": true, "path_length": path_length.get_clone().parse::<i64>().ok()});
|
||||
} else if mode == "end" {
|
||||
extensions["basic_constraints"] =
|
||||
json!({"critical": crit, "ca": false, "path_length": null});
|
||||
}
|
||||
let usages = [
|
||||
(ku_digital.get(), "digital_signature"),
|
||||
(ku_encipher.get(), "key_encipherment"),
|
||||
(ku_key_agreement.get(), "key_agreement"),
|
||||
(ku_cert_sign.get(), "key_cert_sign"),
|
||||
(ku_crl_sign.get(), "crl_sign"),
|
||||
]
|
||||
.iter()
|
||||
.filter(|(checked, _)| *checked)
|
||||
.map(|(_, name)| json!(name))
|
||||
.collect::<Vec<_>>();
|
||||
extensions["key_usage"] = json!({"critical": ku_critical.get(), "usages": usages});
|
||||
let eku = [
|
||||
(eku_server.get(), "serverAuth"),
|
||||
(eku_client.get(), "clientAuth"),
|
||||
(eku_code.get(), "codeSigning"),
|
||||
(eku_email.get(), "emailProtection"),
|
||||
(eku_time.get(), "timeStamping"),
|
||||
(eku_ocsp.get(), "OCSPSigning"),
|
||||
]
|
||||
.iter()
|
||||
.filter(|(checked, _)| *checked)
|
||||
.map(|(_, name)| json!(name))
|
||||
.collect::<Vec<_>>();
|
||||
extensions["extended_key_usage"] =
|
||||
json!({"critical": eku_critical.get(), "usages": eku});
|
||||
let san = parse_san(&san_text.get_clone());
|
||||
if !san.is_empty() {
|
||||
extensions["subject_alt_name"] = json!({"critical": false, "entries": san});
|
||||
}
|
||||
if ski.get() {
|
||||
extensions["subject_key_identifier"] = json!({"critical": false});
|
||||
}
|
||||
if aki.get() {
|
||||
extensions["authority_key_identifier"] = json!({"critical": false});
|
||||
}
|
||||
if ocsp_staple.get() {
|
||||
extensions["tls_feature"] =
|
||||
json!({"critical": false, "features": ["status_request"]});
|
||||
}
|
||||
match build_extra_extensions(&ext) {
|
||||
Ok(extra) => {
|
||||
if let Some(object) = extra.as_object() {
|
||||
for (key, value) in object {
|
||||
extensions[key] = value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(message) => {
|
||||
error.set(Some(message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
let signer = signer_id.get_clone();
|
||||
let payload = json!({
|
||||
"name": if name.get_clone().is_empty() { "preview".to_string() } else { name.get_clone() },
|
||||
"subject": subject,
|
||||
"extensions": extensions,
|
||||
"signature_hash": signature_hash.get_clone(),
|
||||
"signer_id": if signer == "self" { serde_json::Value::Null } else { json!(signer) },
|
||||
"validity": if no_expiration.get() {
|
||||
json!({"no_expiration": true})
|
||||
} else if validity_mode.get_clone() == "absolute" {
|
||||
json!({})
|
||||
} else {
|
||||
json!({"amount": validity_amount.get_clone().parse::<i64>().unwrap_or(365), "unit": validity_unit.get_clone(), "no_expiration": false})
|
||||
},
|
||||
});
|
||||
let mut payload = payload;
|
||||
if validity_mode.get_clone() == "absolute" && !no_expiration.get() {
|
||||
payload["not_before"] = json!(not_before_text.get_clone());
|
||||
payload["not_after"] = json!(not_after_text.get_clone());
|
||||
}
|
||||
let id = id.clone();
|
||||
spawn_local(async move {
|
||||
match api::preview_certificate(&id, &payload).await {
|
||||
Ok(value) => {
|
||||
let mut text = String::new();
|
||||
if let Some(rows) = value.get("extensions").and_then(|v| v.as_array()) {
|
||||
for row in rows {
|
||||
let name = row.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let value = row.get("value").and_then(|v| v.as_str()).unwrap_or("");
|
||||
text.push_str(&format!("{name}: {value}\n"));
|
||||
}
|
||||
}
|
||||
preview_text.set(text);
|
||||
}
|
||||
Err(message) if message == api::LOGIN_REQUIRED => {}
|
||||
Err(message) => error.set(Some(message)),
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let signer_options = move || {
|
||||
signers.get_clone().into_iter().map(|cert| view! {
|
||||
option(value=cert.id.clone()) { (cert.name.clone()) }
|
||||
}).collect::<Vec<View>>()
|
||||
signers
|
||||
.get_clone()
|
||||
.into_iter()
|
||||
.map(|cert| {
|
||||
view! {
|
||||
option(value=cert.id.clone()) { (cert.name.clone()) }
|
||||
}
|
||||
})
|
||||
.collect::<Vec<View>>()
|
||||
};
|
||||
let key_options = move || {
|
||||
keys.get_clone().into_iter().filter(|key| key.has_private).map(|key| view! {
|
||||
option(value=key.id.clone()) { (key.name.clone()) }
|
||||
}).collect::<Vec<View>>()
|
||||
keys.get_clone()
|
||||
.into_iter()
|
||||
.filter(|key| key.has_private)
|
||||
.map(|key| {
|
||||
view! {
|
||||
option(value=key.id.clone()) { (key.name.clone()) }
|
||||
}
|
||||
})
|
||||
.collect::<Vec<View>>()
|
||||
};
|
||||
let template_options = move || {
|
||||
templates.get_clone().into_iter().map(|template| view! {
|
||||
@@ -500,29 +854,69 @@ pub fn CertificateNewPage(vault_id: String) -> View {
|
||||
}
|
||||
}
|
||||
}
|
||||
(extension_fields(&ext))
|
||||
div(class="form-section") {
|
||||
div(class="form-section-title") { "Validity" }
|
||||
div(class="row g-3") {
|
||||
div(class="col-md-3") {
|
||||
label(class="form-label") { "Duration" }
|
||||
input(class="form-control", bind:value=validity_amount)
|
||||
}
|
||||
div(class="col-md-3") {
|
||||
label(class="form-label") { "Unit" }
|
||||
select(class="form-select", bind:value=validity_unit) {
|
||||
option(value="days") { "Days" }
|
||||
option(value="months") { "Months" }
|
||||
option(value="years") { "Years" }
|
||||
label(class="form-label") { "Mode" }
|
||||
select(class="form-select", bind:value=validity_mode) {
|
||||
option(value="duration") { "Duration" }
|
||||
option(value="absolute") { "Absolute dates" }
|
||||
}
|
||||
}
|
||||
(if validity_mode.get_clone() == "duration" {
|
||||
view! {
|
||||
div(class="col-md-3") {
|
||||
label(class="form-label") { "Duration" }
|
||||
input(class="form-control", disabled=no_expiration.get(), bind:value=validity_amount)
|
||||
}
|
||||
div(class="col-md-3") {
|
||||
label(class="form-label") { "Unit" }
|
||||
select(class="form-select", disabled=no_expiration.get(), bind:value=validity_unit) {
|
||||
option(value="days") { "Days" }
|
||||
option(value="months") { "Months" }
|
||||
option(value="years") { "Years" }
|
||||
}
|
||||
}
|
||||
div(class="col-md-3 d-flex align-items-end") {
|
||||
div(class="form-check") {
|
||||
input(class="form-check-input", r#type="checkbox", disabled=no_expiration.get(), bind:checked=midnight)
|
||||
label(class="form-check-label") { "Round to midnight" }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
view! {
|
||||
div(class="col-md-4") {
|
||||
label(class="form-label") { "Not before" }
|
||||
input(class="form-control", r#type="datetime-local", disabled=no_expiration.get(), bind:value=not_before_text)
|
||||
}
|
||||
div(class="col-md-4") {
|
||||
label(class="form-label") { "Not after" }
|
||||
input(class="form-control", r#type="datetime-local", disabled=no_expiration.get(), bind:value=not_after_text)
|
||||
}
|
||||
}
|
||||
})
|
||||
div(class="col-md-3 d-flex align-items-end") {
|
||||
div(class="form-check") {
|
||||
input(class="form-check-input", r#type="checkbox", bind:checked=midnight)
|
||||
label(class="form-check-label") { "Round to midnight" }
|
||||
input(class="form-check-input", r#type="checkbox", bind:checked=no_expiration)
|
||||
label(class="form-check-label") { "No well-defined expiration" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(if preview_text.get_clone().is_empty() {
|
||||
view! {}
|
||||
} else {
|
||||
view! {
|
||||
div(class="card mb-3") {
|
||||
div(class="card-header") { "Extension preview" }
|
||||
div(class="card-body") { pre(class="pem mb-0") { (preview_text.get_clone()) } }
|
||||
}
|
||||
}
|
||||
})
|
||||
button(class="btn btn-outline-light btn-lg me-2", on:click=validate) { "Validate extensions" }
|
||||
button(class="btn btn-primary btn-lg", disabled=saving.get(), on:click=submit) {
|
||||
(if saving.get() { "Issuing…" } else if request_mode { "Issue certificate" } else { "Create certificate" })
|
||||
}
|
||||
@@ -583,13 +977,18 @@ pub fn CertificateRenewPage(vault_id: String, cert_id: String) -> View {
|
||||
match api::renew_certificate(&id, &cert_id_value, &payload).await {
|
||||
Ok(value) => {
|
||||
saving.set(false);
|
||||
sycamore_router::navigate(&format!("/vaults/{}/certificates/{}", id, value.summary.id));
|
||||
sycamore_router::navigate(&format!(
|
||||
"/vaults/{}/certificates/{}",
|
||||
id, value.summary.id
|
||||
));
|
||||
return;
|
||||
}
|
||||
Err(message) if message == api::LOGIN_REQUIRED => {}
|
||||
Err(message) => error.set(Some(message)),
|
||||
}
|
||||
if saving.is_alive() { saving.set(false); }
|
||||
if saving.is_alive() {
|
||||
saving.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -710,7 +1109,12 @@ fn parse_san(text: &str) -> Vec<serde_json::Value> {
|
||||
fn string_array(value: Option<&serde_json::Value>) -> Vec<String> {
|
||||
value
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|items| items.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
|
||||
.map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user