Ux improvements - Allow enrolling other devices ()

This commit is contained in:
Firstyear 2023-06-24 12:24:13 +10:00 committed by GitHub
parent a0b59c6072
commit d5670d0add
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 986 additions and 960 deletions

View file

@ -147,7 +147,9 @@ impl KanidmClientBuilder {
let path = Path::new(ca_path);
let ca_meta = read_file_metadata(&path)?;
#[cfg(target_family = "unix")]
trace!("uid:gid {}:{}", ca_meta.uid(), ca_meta.gid());
#[cfg(not(debug_assertions))]
if ca_meta.uid() != 0 || ca_meta.gid() != 0 {
warn!(
"{} should be owned be root:root to prevent tampering",
@ -155,9 +157,9 @@ impl KanidmClientBuilder {
);
}
#[cfg(target_family = "unix")]
if ca_meta.mode() != 0o644 {
warn!("permissions on {} may not be secure. Should be set to 0644. This could be a security risk ...", ca_path);
trace!("mode={:o}", ca_meta.mode());
if (ca_meta.mode() & 0o7133) != 0 {
warn!("permissions on {} are NOT secure. 0644 is a secure default. Should not be setuid, executable or allow group/other writes.", ca_path);
}
}
@ -1509,9 +1511,19 @@ impl KanidmClient {
pub async fn idm_person_account_credential_update_intent(
&self,
id: &str,
ttl: Option<u32>,
) -> Result<CUIntentToken, ClientError> {
self.perform_get_request(format!("/v1/person/{}/_credential/_update_intent", id).as_str())
if let Some(ttl) = ttl {
self.perform_get_request(
format!("/v1/person/{}/_credential/_update_intent?ttl={}", id, ttl).as_str(),
)
.await
} else {
self.perform_get_request(
format!("/v1/person/{}/_credential/_update_intent", id).as_str(),
)
.await
}
}
pub async fn idm_account_credential_update_begin(

View file

@ -1088,19 +1088,6 @@ impl TotpSecret {
}
}
/*
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SetCredentialResponse {
Success,
Token(String),
TotpCheck(Uuid, TotpSecret),
TotpInvalidSha1(Uuid),
SecurityKeyCreateChallenge(Uuid, CreationChallengeResponse),
BackupCodes(Vec<String>),
}
*/
#[derive(Debug, Serialize, Deserialize)]
pub struct CUIntentToken {
pub token: String,

View file

@ -7,8 +7,8 @@ use openssl::nid::Nid;
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
use openssl::x509::{
extension::{
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
SubjectKeyIdentifier,
AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage,
SubjectAlternativeName, SubjectKeyIdentifier,
},
X509NameBuilder, X509ReqBuilder, X509,
};
@ -268,12 +268,19 @@ pub(crate) fn build_cert(
cert_builder.append_extension(
KeyUsage::new()
.critical()
.non_repudiation()
// .non_repudiation()
.digital_signature()
.key_encipherment()
.build()?,
)?;
cert_builder.append_extension(
ExtendedKeyUsage::new()
// .critical()
.server_auth()
.build()?,
)?;
let subject_key_identifier = SubjectKeyIdentifier::new()
.build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?;
cert_builder.append_extension(subject_key_identifier)?;

View file

@ -26,7 +26,7 @@ use crate::value::IntentTokenState;
const MAXIMUM_CRED_UPDATE_TTL: Duration = Duration::from_secs(900);
const MAXIMUM_INTENT_TTL: Duration = Duration::from_secs(86400);
const MINIMUM_INTENT_TTL: Duration = MAXIMUM_CRED_UPDATE_TTL;
const MINIMUM_INTENT_TTL: Duration = Duration::from_secs(300);
#[derive(Debug)]
pub enum PasswordQuality {

View file

@ -938,7 +938,7 @@ async fn test_server_credential_update_session_pw(rsclient: KanidmClient) {
// Create an intent token for them
let intent_token = rsclient
.idm_person_account_credential_update_intent("demo_account")
.idm_person_account_credential_update_intent("demo_account", Some(0))
.await
.unwrap();
@ -996,7 +996,7 @@ async fn test_server_credential_update_session_totp_pw(rsclient: KanidmClient) {
.unwrap();
let intent_token = rsclient
.idm_person_account_credential_update_intent("demo_account")
.idm_person_account_credential_update_intent("demo_account", Some(999999))
.await
.unwrap();
@ -1125,7 +1125,7 @@ async fn setup_demo_account_passkey(rsclient: &KanidmClient) -> WebauthnAuthenti
// Create an intent token for them
let intent_token = rsclient
.idm_person_account_credential_update_intent("demo_account")
.idm_person_account_credential_update_intent("demo_account", Some(1234))
.await
.unwrap();
@ -1289,7 +1289,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
{
// Create an intent token for them
let intent_token = rsclient
.idm_person_account_credential_update_intent("demo_account")
.idm_person_account_credential_update_intent("demo_account", None)
.await
.unwrap();

View file

@ -38,14 +38,14 @@ function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length) >>> 0;
const ptr = malloc(buf.length, 1) >>> 0;
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len) >>> 0;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8Memory0();
@ -61,7 +61,7 @@ function passStringToWasm0(arg, malloc, realloc) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3) >>> 0;
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
@ -234,19 +234,19 @@ function addBorrowedObject(obj) {
}
function __wbg_adapter_48(arg0, arg1, arg2) {
try {
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h81cab36ffd7a0f2a(arg0, arg1, addBorrowedObject(arg2));
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd63bd1cb9b35210b(arg0, arg1, addBorrowedObject(arg2));
} finally {
heap[stack_pointer++] = undefined;
}
}
function __wbg_adapter_51(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf5c80722c0530c5d(arg0, arg1, addHeapObject(arg2));
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h4a4d23d6ebf5b8fa(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_54(arg0, arg1, arg2) {
try {
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h4764cc6fa79b1493(arg0, arg1, addBorrowedObject(arg2));
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf7e9e605a1806538(arg0, arg1, addBorrowedObject(arg2));
} finally {
heap[stack_pointer++] = undefined;
}
@ -417,14 +417,14 @@ function __wbg_get_imports() {
imports.wbg.__wbg_setlistenerid_3183aae8fa5840fb = function(arg0, arg1) {
getObject(arg0).__yew_listener_id = arg1 >>> 0;
};
imports.wbg.__wbg_setsubtreeid_d32e6327eef1f7fc = function(arg0, arg1) {
getObject(arg0).__yew_subtree_id = arg1 >>> 0;
};
imports.wbg.__wbg_subtreeid_e348577f7ef777e3 = function(arg0, arg1) {
const ret = getObject(arg1).__yew_subtree_id;
getInt32Memory0()[arg0 / 4 + 1] = isLikeNone(ret) ? 0 : ret;
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
};
imports.wbg.__wbg_setsubtreeid_d32e6327eef1f7fc = function(arg0, arg1) {
getObject(arg0).__yew_subtree_id = arg1 >>> 0;
};
imports.wbg.__wbg_cachekey_b61393159c57fd7b = function(arg0, arg1) {
const ret = getObject(arg1).__yew_subtree_cache_key;
getInt32Memory0()[arg0 / 4 + 1] = isLikeNone(ret) ? 0 : ret;
@ -452,7 +452,7 @@ function __wbg_get_imports() {
deferred0_1 = arg1;
console.error(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(deferred0_0, deferred0_1);
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
}
};
imports.wbg.__wbindgen_number_new = function(arg0) {
@ -490,31 +490,38 @@ function __wbg_get_imports() {
wasm.__wbindgen_free(arg0, arg1 * 4);
console.warn(...v0);
};
imports.wbg.__wbg_body_db30cc67afcfce41 = function(arg0) {
imports.wbg.__wbg_documentURI_4bff51077cdeeac1 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).documentURI;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_body_674aec4c1c0910cd = function(arg0) {
const ret = getObject(arg0).body;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_createElement_d975e66d06bc88da = function() { return handleError(function (arg0, arg1, arg2) {
imports.wbg.__wbg_createElement_4891554b28d3388b = function() { return handleError(function (arg0, arg1, arg2) {
const ret = getObject(arg0).createElement(getStringFromWasm0(arg1, arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_createElementNS_0863d6a8a49df376 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
imports.wbg.__wbg_createElementNS_119acf9e82482041 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
const ret = getObject(arg0).createElementNS(arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_createTextNode_31876ed40128c33c = function(arg0, arg1, arg2) {
imports.wbg.__wbg_createTextNode_2fd22cd7e543f938 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).createTextNode(getStringFromWasm0(arg1, arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_getElementById_2d1ad15c49298068 = function(arg0, arg1, arg2) {
imports.wbg.__wbg_getElementById_cc0e0d931b0d9a28 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).getElementById(getStringFromWasm0(arg1, arg2));
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_querySelector_41d5da02fa776534 = function() { return handleError(function (arg0, arg1, arg2) {
imports.wbg.__wbg_querySelector_52ded52c20e23921 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = getObject(arg0).querySelector(getStringFromWasm0(arg1, arg2));
return isLikeNone(ret) ? 0 : addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_instanceof_Window_c5579e140698a9dc = function(arg0) {
imports.wbg.__wbg_instanceof_Window_9029196b662bc42a = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Window;
@ -524,45 +531,45 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_document_508774c021174a52 = function(arg0) {
imports.wbg.__wbg_document_f7ace2b956f30a4f = function(arg0) {
const ret = getObject(arg0).document;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_location_f6c62a50e72200c8 = function(arg0) {
imports.wbg.__wbg_location_56243dba507f472d = function(arg0) {
const ret = getObject(arg0).location;
return addHeapObject(ret);
};
imports.wbg.__wbg_history_43a4879ec56de5ce = function() { return handleError(function (arg0) {
imports.wbg.__wbg_history_3c2280e6b2a9316e = function() { return handleError(function (arg0) {
const ret = getObject(arg0).history;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_navigator_957c9b40d49df205 = function(arg0) {
imports.wbg.__wbg_navigator_7c9103698acde322 = function(arg0) {
const ret = getObject(arg0).navigator;
return addHeapObject(ret);
};
imports.wbg.__wbg_localStorage_b5b6d3c826dbfeda = function() { return handleError(function (arg0) {
imports.wbg.__wbg_localStorage_dbac11bd189e9fa0 = function() { return handleError(function (arg0) {
const ret = getObject(arg0).localStorage;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_sessionStorage_263f344230ee7188 = function() { return handleError(function (arg0) {
imports.wbg.__wbg_sessionStorage_3b863b6e15dd2bdc = function() { return handleError(function (arg0) {
const ret = getObject(arg0).sessionStorage;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_fetch_bb49ae9f1d79408b = function(arg0, arg1) {
imports.wbg.__wbg_fetch_336b6f0cb426b46e = function(arg0, arg1) {
const ret = getObject(arg0).fetch(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_value_664b8ba8bd4419b0 = function(arg0, arg1) {
imports.wbg.__wbg_value_3c5f08ffc2b7d6f9 = function(arg0, arg1) {
const ret = getObject(arg1).value;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_setvalue_272abbd8c7ff3573 = function(arg0, arg1, arg2) {
imports.wbg.__wbg_setvalue_0dc100d4b9908028 = function(arg0, arg1, arg2) {
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_instanceof_ShadowRoot_5aea367fb03b2fff = function(arg0) {
imports.wbg.__wbg_instanceof_ShadowRoot_b64337370f59fe2d = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof ShadowRoot;
@ -572,38 +579,38 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_host_ca6efb67ffed0f2e = function(arg0) {
imports.wbg.__wbg_host_e1c47c33975060d3 = function(arg0) {
const ret = getObject(arg0).host;
return addHeapObject(ret);
};
imports.wbg.__wbg_state_d5ffeae11b280151 = function() { return handleError(function (arg0) {
imports.wbg.__wbg_state_745dc4814d321eb3 = function() { return handleError(function (arg0) {
const ret = getObject(arg0).state;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_pushState_b98021531274a207 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) {
imports.wbg.__wbg_pushState_1145414a47c0b629 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) {
getObject(arg0).pushState(getObject(arg1), getStringFromWasm0(arg2, arg3), arg4 === 0 ? undefined : getStringFromWasm0(arg4, arg5));
}, arguments) };
imports.wbg.__wbg_href_d45a5745a211a3da = function(arg0, arg1) {
imports.wbg.__wbg_href_47b90f0ddf3ddcd7 = function(arg0, arg1) {
const ret = getObject(arg1).href;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_getItem_84095995ffbc84fc = function() { return handleError(function (arg0, arg1, arg2, arg3) {
imports.wbg.__wbg_getItem_ed8e218e51f1efeb = function() { return handleError(function (arg0, arg1, arg2, arg3) {
const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_removeItem_3edd1d51c937f201 = function() { return handleError(function (arg0, arg1, arg2) {
imports.wbg.__wbg_removeItem_02359267b311cb85 = function() { return handleError(function (arg0, arg1, arg2) {
getObject(arg0).removeItem(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_setItem_e9a65f0e6892d9c9 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
imports.wbg.__wbg_setItem_d002ee486462bfff = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbg_instanceof_HtmlInputElement_a15913e00980dd9c = function(arg0) {
imports.wbg.__wbg_instanceof_HtmlInputElement_31b50e0cf542c524 = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof HTMLInputElement;
@ -613,34 +620,30 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_checked_29f4b9f0e2a0087b = function(arg0) {
imports.wbg.__wbg_checked_5ccb3a66eb054121 = function(arg0) {
const ret = getObject(arg0).checked;
return ret;
};
imports.wbg.__wbg_setchecked_46f40fa426cedbb8 = function(arg0, arg1) {
imports.wbg.__wbg_setchecked_e5a50baea447b8a8 = function(arg0, arg1) {
getObject(arg0).checked = arg1 !== 0;
};
imports.wbg.__wbg_value_09d384cba1c51c6f = function(arg0, arg1) {
imports.wbg.__wbg_value_9423da9d988ee8cf = function(arg0, arg1) {
const ret = getObject(arg1).value;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_setvalue_7605619324f70225 = function(arg0, arg1, arg2) {
imports.wbg.__wbg_setvalue_1f95e61cbc382f7f = function(arg0, arg1, arg2) {
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_add_9c791198ad871a5a = function() { return handleError(function (arg0, arg1, arg2) {
imports.wbg.__wbg_add_3eafedc4b2a28db0 = function() { return handleError(function (arg0, arg1, arg2) {
getObject(arg0).add(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_remove_7643c63b1abb966b = function() { return handleError(function (arg0, arg1, arg2) {
imports.wbg.__wbg_remove_8ae45e50cb58bb66 = function() { return handleError(function (arg0, arg1, arg2) {
getObject(arg0).remove(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_new_5cb136b036dd2286 = function() { return handleError(function () {
const ret = new URLSearchParams();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_instanceof_WorkerGlobalScope_5188d176509513d4 = function(arg0) {
imports.wbg.__wbg_instanceof_WorkerGlobalScope_d9d741da0fb130ce = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof WorkerGlobalScope;
@ -650,11 +653,11 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_fetch_621998933558ad27 = function(arg0, arg1) {
imports.wbg.__wbg_fetch_8eaf01857a5bb21f = function(arg0, arg1) {
const ret = getObject(arg0).fetch(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_instanceof_Element_6fe31b975e43affc = function(arg0) {
imports.wbg.__wbg_instanceof_Element_4622f5da1249a3eb = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Element;
@ -664,38 +667,38 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_namespaceURI_a1c6e4b9bb827959 = function(arg0, arg1) {
imports.wbg.__wbg_namespaceURI_31718ed49b5343a3 = function(arg0, arg1) {
const ret = getObject(arg1).namespaceURI;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_classList_306c7f059aa66779 = function(arg0) {
imports.wbg.__wbg_classList_5f2fc1d67656292e = function(arg0) {
const ret = getObject(arg0).classList;
return addHeapObject(ret);
};
imports.wbg.__wbg_setinnerHTML_76dc2e7ffb1c1936 = function(arg0, arg1, arg2) {
imports.wbg.__wbg_setinnerHTML_b089587252408b67 = function(arg0, arg1, arg2) {
getObject(arg0).innerHTML = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_outerHTML_e90651c874c31e05 = function(arg0, arg1) {
imports.wbg.__wbg_outerHTML_f7749ceff37b5832 = function(arg0, arg1) {
const ret = getObject(arg1).outerHTML;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_children_a62d21390b1805ff = function(arg0) {
imports.wbg.__wbg_children_27ed308801b57d3f = function(arg0) {
const ret = getObject(arg0).children;
return addHeapObject(ret);
};
imports.wbg.__wbg_removeAttribute_77e4f460fd0fde34 = function() { return handleError(function (arg0, arg1, arg2) {
imports.wbg.__wbg_removeAttribute_d8404da431968808 = function() { return handleError(function (arg0, arg1, arg2) {
getObject(arg0).removeAttribute(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_setAttribute_1b177bcd399b9b56 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
imports.wbg.__wbg_setAttribute_e7e80b478b7b8b2f = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbg_instanceof_HtmlElement_bf2d86870dcd8306 = function(arg0) {
imports.wbg.__wbg_instanceof_HtmlElement_6f4725d4677c7968 = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof HTMLElement;
@ -705,226 +708,59 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_focus_6baebc9f44af9925 = function() { return handleError(function (arg0) {
imports.wbg.__wbg_focus_dbcbbbb2a04c0e1f = function() { return handleError(function (arg0) {
getObject(arg0).focus();
}, arguments) };
imports.wbg.__wbg_new_143b41b4342650bb = function() { return handleError(function () {
imports.wbg.__wbg_new_1eead62f64ca15ce = function() { return handleError(function () {
const ret = new Headers();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_get_827d1741c899cefa = function() { return handleError(function (arg0, arg1, arg2, arg3) {
imports.wbg.__wbg_get_2e9aab260014946d = function() { return handleError(function (arg0, arg1, arg2, arg3) {
const ret = getObject(arg1).get(getStringFromWasm0(arg2, arg3));
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_set_76353df4722f4954 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
imports.wbg.__wbg_set_b34caba58723c454 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) };
imports.wbg.__wbg_url_3325e0ef088003ca = function(arg0, arg1) {
imports.wbg.__wbg_url_fda63503ced387ff = function(arg0, arg1) {
const ret = getObject(arg1).url;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_headers_1eff4f53324496e6 = function(arg0) {
imports.wbg.__wbg_headers_b439dcff02e808e5 = function(arg0) {
const ret = getObject(arg0).headers;
return addHeapObject(ret);
};
imports.wbg.__wbg_newwithstr_49e8bfa3150f3210 = function() { return handleError(function (arg0, arg1) {
imports.wbg.__wbg_newwithstr_3d9bc779603a93c7 = function() { return handleError(function (arg0, arg1) {
const ret = new Request(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_newwithstrandinit_a4cd16dfaafcf625 = function() { return handleError(function (arg0, arg1, arg2) {
imports.wbg.__wbg_newwithstrandinit_cad5cd6038c7ff5d = function() { return handleError(function (arg0, arg1, arg2) {
const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_create_5e9a0f618bfcf68e = function() { return handleError(function (arg0, arg1) {
imports.wbg.__wbg_new_2a98b9c4a51bdc04 = function() { return handleError(function () {
const ret = new URLSearchParams();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_create_c7e40b6b88186cbf = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).create(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_get_26a321f45b19bc50 = function() { return handleError(function (arg0, arg1) {
imports.wbg.__wbg_get_e66794f89dcd7828 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).get(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_credentials_f0e7b21ccae12a71 = function(arg0) {
imports.wbg.__wbg_credentials_66b6baa89eb03c21 = function(arg0) {
const ret = getObject(arg0).credentials;
return addHeapObject(ret);
};
imports.wbg.__wbg_getClientExtensionResults_33e0afe04994016a = function(arg0) {
const ret = getObject(arg0).getClientExtensionResults();
return addHeapObject(ret);
};
imports.wbg.__wbg_log_dc06ec929fc95a20 = function(arg0) {
console.log(getObject(arg0));
};
imports.wbg.__wbg_instanceof_Event_32e538860e9889fb = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Event;
} catch {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_target_bb43778021b84733 = function(arg0) {
const ret = getObject(arg0).target;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_bubbles_0a277ad42caf0211 = function(arg0) {
const ret = getObject(arg0).bubbles;
return ret;
};
imports.wbg.__wbg_cancelBubble_42441ef40999b550 = function(arg0) {
const ret = getObject(arg0).cancelBubble;
return ret;
};
imports.wbg.__wbg_composedPath_85d84e53cceb3d62 = function(arg0) {
const ret = getObject(arg0).composedPath();
return addHeapObject(ret);
};
imports.wbg.__wbg_preventDefault_2f38e1471796356f = function(arg0) {
getObject(arg0).preventDefault();
};
imports.wbg.__wbg_newwithform_3d4f6ad6b97fe85a = function() { return handleError(function (arg0) {
const ret = new FormData(getObject(arg0));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_get_f028d2678f7c23f0 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).get(getStringFromWasm0(arg1, arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_href_68df54cac0a34be4 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).href;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_pathname_7937efe08358d5cb = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).pathname;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_search_41ecfaf18d054732 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).search;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_hash_afd040db1cf05017 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).hash;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_replace_2b3ffb56c000639b = function() { return handleError(function (arg0, arg1, arg2) {
getObject(arg0).replace(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_parentNode_65dd881ebb22f646 = function(arg0) {
const ret = getObject(arg0).parentNode;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_parentElement_065722829508e41a = function(arg0) {
const ret = getObject(arg0).parentElement;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_lastChild_649563f43d5b930d = function(arg0) {
const ret = getObject(arg0).lastChild;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_nextSibling_6e2efeefd07e6f9e = function(arg0) {
const ret = getObject(arg0).nextSibling;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_setnodeValue_008911a41f1b91a3 = function(arg0, arg1, arg2) {
getObject(arg0).nodeValue = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_textContent_d953d0aec79e1ba6 = function(arg0, arg1) {
const ret = getObject(arg1).textContent;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_appendChild_1139b53a65d69bed = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).appendChild(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_insertBefore_2e38a68009b551f3 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = getObject(arg0).insertBefore(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_removeChild_48d9566cffdfec93 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).removeChild(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_href_1833451470322cf1 = function(arg0, arg1) {
const ret = getObject(arg1).href;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_pathname_715df8a6b71fdfd7 = function(arg0, arg1) {
const ret = getObject(arg1).pathname;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_search_24b39c2a5b10e06c = function(arg0, arg1) {
const ret = getObject(arg1).search;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_setsearch_7aeec58875c5946b = function(arg0, arg1, arg2) {
getObject(arg0).search = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_hash_b1b4c5ad6e15e747 = function(arg0, arg1) {
const ret = getObject(arg1).hash;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_sethash_a30d0cfead0ea698 = function(arg0, arg1, arg2) {
getObject(arg0).hash = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_new_f6818a0e274befa9 = function() { return handleError(function (arg0, arg1) {
const ret = new URL(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_newwithbase_ab62e3a218d21489 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
const ret = new URL(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_addEventListener_3a7d7c4177ce91d1 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4));
}, arguments) };
imports.wbg.__wbg_removeEventListener_315d6f929fccf484 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), arg4 !== 0);
}, arguments) };
imports.wbg.__wbg_instanceof_HtmlFormElement_a67ff2b843593f03 = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof HTMLFormElement;
} catch {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_instanceof_Response_7ade9a5a066d1a55 = function(arg0) {
imports.wbg.__wbg_instanceof_Response_fc4327dbfcdf5ced = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Response;
@ -934,31 +770,192 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_status_d2b2d0889f7e970f = function(arg0) {
imports.wbg.__wbg_status_ac85a3142a84caa2 = function(arg0) {
const ret = getObject(arg0).status;
return ret;
};
imports.wbg.__wbg_headers_2de03c88f895093b = function(arg0) {
imports.wbg.__wbg_headers_b70de86b8e989bc0 = function(arg0) {
const ret = getObject(arg0).headers;
return addHeapObject(ret);
};
imports.wbg.__wbg_json_6c19bb86f10d6184 = function() { return handleError(function (arg0) {
imports.wbg.__wbg_json_2a46ed5b7c4d30d1 = function() { return handleError(function (arg0) {
const ret = getObject(arg0).json();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_text_65fa1887e8f7b4ac = function() { return handleError(function (arg0) {
imports.wbg.__wbg_text_a667ac1770538491 = function() { return handleError(function (arg0) {
const ret = getObject(arg0).text();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_get_7303ed2ef026b2f5 = function(arg0, arg1) {
imports.wbg.__wbg_log_1d3ae0273d8f4f8a = function(arg0) {
console.log(getObject(arg0));
};
imports.wbg.__wbg_target_f171e89c61e2bccf = function(arg0) {
const ret = getObject(arg0).target;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_bubbles_63572b91f3885ef1 = function(arg0) {
const ret = getObject(arg0).bubbles;
return ret;
};
imports.wbg.__wbg_cancelBubble_90d1c3aa2a76cbeb = function(arg0) {
const ret = getObject(arg0).cancelBubble;
return ret;
};
imports.wbg.__wbg_composedPath_cf1bb5b8bcff496f = function(arg0) {
const ret = getObject(arg0).composedPath();
return addHeapObject(ret);
};
imports.wbg.__wbg_preventDefault_24104f3f0a54546a = function(arg0) {
getObject(arg0).preventDefault();
};
imports.wbg.__wbg_newwithform_368648c82279d486 = function() { return handleError(function (arg0) {
const ret = new FormData(getObject(arg0));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_get_4c356dcef81d58a5 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).get(getStringFromWasm0(arg1, arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_href_d62a28e4fc1ab948 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).href;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_pathname_c8fd5c498079312d = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).pathname;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_search_6c3c472e076ee010 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).search;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_hash_a1a795b89dda8e3d = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).hash;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}, arguments) };
imports.wbg.__wbg_replace_5d1d2b7956cafd7b = function() { return handleError(function (arg0, arg1, arg2) {
getObject(arg0).replace(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_getClientExtensionResults_b9108fbba9f54b38 = function(arg0) {
const ret = getObject(arg0).getClientExtensionResults();
return addHeapObject(ret);
};
imports.wbg.__wbg_parentNode_9e53f8b17eb98c9d = function(arg0) {
const ret = getObject(arg0).parentNode;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_parentElement_c75962bc9997ea5f = function(arg0) {
const ret = getObject(arg0).parentElement;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_lastChild_0cee692010bac6c2 = function(arg0) {
const ret = getObject(arg0).lastChild;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_nextSibling_304d9aac7c2774ae = function(arg0) {
const ret = getObject(arg0).nextSibling;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_setnodeValue_d1c8382910b45e04 = function(arg0, arg1, arg2) {
getObject(arg0).nodeValue = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_textContent_c5d9e21ee03c63d4 = function(arg0, arg1) {
const ret = getObject(arg1).textContent;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_appendChild_51339d4cde00ee22 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).appendChild(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_insertBefore_ffa01d4b747c95fc = function() { return handleError(function (arg0, arg1, arg2) {
const ret = getObject(arg0).insertBefore(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_removeChild_973429f368206138 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).removeChild(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_href_17ed54b321396524 = function(arg0, arg1) {
const ret = getObject(arg1).href;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_pathname_57290e07c6bc0683 = function(arg0, arg1) {
const ret = getObject(arg1).pathname;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_search_2ff3bb9114e0ca34 = function(arg0, arg1) {
const ret = getObject(arg1).search;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_setsearch_16b87f04ea0e6b80 = function(arg0, arg1, arg2) {
getObject(arg0).search = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_hash_2b57e787945b2db0 = function(arg0, arg1) {
const ret = getObject(arg1).hash;
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_sethash_41d6e65816639c62 = function(arg0, arg1, arg2) {
getObject(arg0).hash = getStringFromWasm0(arg1, arg2);
};
imports.wbg.__wbg_new_a76f6bcb38f791ea = function() { return handleError(function (arg0, arg1) {
const ret = new URL(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_newwithbase_79b8cac27ce631ac = function() { return handleError(function (arg0, arg1, arg2, arg3) {
const ret = new URL(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_addEventListener_a5963e26cd7b176b = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4));
}, arguments) };
imports.wbg.__wbg_removeEventListener_782040b4432709cb = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).removeEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), arg4 !== 0);
}, arguments) };
imports.wbg.__wbg_instanceof_HtmlFormElement_b57527983c7c1ada = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof HTMLFormElement;
} catch {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) {
const ret = getObject(arg0)[arg1 >>> 0];
return addHeapObject(ret);
};
imports.wbg.__wbg_length_820c786973abdd8a = function(arg0) {
imports.wbg.__wbg_length_fff51ee6522a1a18 = function(arg0) {
const ret = getObject(arg0).length;
return ret;
};
imports.wbg.__wbg_new_0394642eae39db16 = function() {
imports.wbg.__wbg_new_898a68150f225f2e = function() {
const ret = new Array();
return addHeapObject(ret);
};
@ -966,78 +963,78 @@ function __wbg_get_imports() {
const ret = typeof(getObject(arg0)) === 'function';
return ret;
};
imports.wbg.__wbg_newnoargs_c9e6043b8ad84109 = function(arg0, arg1) {
imports.wbg.__wbg_newnoargs_581967eacc0e2604 = function(arg0, arg1) {
const ret = new Function(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_new_0f2b71ca2f2a6029 = function() {
imports.wbg.__wbg_new_56693dbed0c32988 = function() {
const ret = new Map();
return addHeapObject(ret);
};
imports.wbg.__wbg_next_f4bc0e96ea67da68 = function(arg0) {
imports.wbg.__wbg_next_526fc47e980da008 = function(arg0) {
const ret = getObject(arg0).next;
return addHeapObject(ret);
};
imports.wbg.__wbg_next_ec061e48a0e72a96 = function() { return handleError(function (arg0) {
imports.wbg.__wbg_next_ddb3312ca1c4e32a = function() { return handleError(function (arg0) {
const ret = getObject(arg0).next();
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_done_b6abb27d42b63867 = function(arg0) {
imports.wbg.__wbg_done_5c1f01fb660d73b5 = function(arg0) {
const ret = getObject(arg0).done;
return ret;
};
imports.wbg.__wbg_value_2f4ef2036bfad28e = function(arg0) {
imports.wbg.__wbg_value_1695675138684bd5 = function(arg0) {
const ret = getObject(arg0).value;
return addHeapObject(ret);
};
imports.wbg.__wbg_iterator_7c7e58f62eb84700 = function() {
imports.wbg.__wbg_iterator_97f0c81209c6c35a = function() {
const ret = Symbol.iterator;
return addHeapObject(ret);
};
imports.wbg.__wbg_get_f53c921291c381bd = function() { return handleError(function (arg0, arg1) {
imports.wbg.__wbg_get_97b561fb56f034b5 = function() { return handleError(function (arg0, arg1) {
const ret = Reflect.get(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_call_557a2f2deacc4912 = function() { return handleError(function (arg0, arg1) {
imports.wbg.__wbg_call_cb65541d95d71282 = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg0).call(getObject(arg1));
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_new_2b6fea4ea03b1b95 = function() {
imports.wbg.__wbg_new_b51585de1b234aff = function() {
const ret = new Object();
return addHeapObject(ret);
};
imports.wbg.__wbg_self_742dd6eab3e9211e = function() { return handleError(function () {
imports.wbg.__wbg_self_1ff1d729e9aae938 = function() { return handleError(function () {
const ret = self.self;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_window_c409e731db53a0e2 = function() { return handleError(function () {
imports.wbg.__wbg_window_5f4faef6c12b79ec = function() { return handleError(function () {
const ret = window.window;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_globalThis_b70c095388441f2d = function() { return handleError(function () {
imports.wbg.__wbg_globalThis_1d39714405582d3c = function() { return handleError(function () {
const ret = globalThis.globalThis;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_global_1c72617491ed7194 = function() { return handleError(function () {
imports.wbg.__wbg_global_651f05c6a0944d1c = function() { return handleError(function () {
const ret = global.global;
return addHeapObject(ret);
}, arguments) };
imports.wbg.__wbg_set_b4da98d504ac6091 = function(arg0, arg1, arg2) {
imports.wbg.__wbg_set_502d29070ea18557 = function(arg0, arg1, arg2) {
getObject(arg0)[arg1 >>> 0] = takeObject(arg2);
};
imports.wbg.__wbg_from_6bc98a09a0b58bb1 = function(arg0) {
imports.wbg.__wbg_from_d7c216d4616bb368 = function(arg0) {
const ret = Array.from(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_isArray_04e59fb73f78ab5b = function(arg0) {
imports.wbg.__wbg_isArray_4c24b343cb13cfb1 = function(arg0) {
const ret = Array.isArray(getObject(arg0));
return ret;
};
imports.wbg.__wbg_push_109cfc26d02582dd = function(arg0, arg1) {
imports.wbg.__wbg_push_ca1c26067ef907ac = function(arg0, arg1) {
const ret = getObject(arg0).push(getObject(arg1));
return ret;
};
imports.wbg.__wbg_instanceof_ArrayBuffer_ef2632aa0d4bfff8 = function(arg0) {
imports.wbg.__wbg_instanceof_ArrayBuffer_39ac22089b74fddb = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof ArrayBuffer;
@ -1047,7 +1044,7 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_instanceof_Error_fac23a8832b241da = function(arg0) {
imports.wbg.__wbg_instanceof_Error_ab19e20608ea43c7 = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Error;
@ -1057,78 +1054,78 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_message_eab7d45ec69a2135 = function(arg0) {
imports.wbg.__wbg_message_48bacc5ea57d74ee = function(arg0) {
const ret = getObject(arg0).message;
return addHeapObject(ret);
};
imports.wbg.__wbg_name_8e6176d4db1a502d = function(arg0) {
imports.wbg.__wbg_name_8f734cbbd6194153 = function(arg0) {
const ret = getObject(arg0).name;
return addHeapObject(ret);
};
imports.wbg.__wbg_toString_506566b763774a16 = function(arg0) {
imports.wbg.__wbg_toString_1c056108b87ba68b = function(arg0) {
const ret = getObject(arg0).toString();
return addHeapObject(ret);
};
imports.wbg.__wbg_set_da7be7bf0e037b14 = function(arg0, arg1, arg2) {
imports.wbg.__wbg_set_bedc3d02d0f05eb0 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).set(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_isSafeInteger_2088b01008075470 = function(arg0) {
imports.wbg.__wbg_isSafeInteger_bb8e18dd21c97288 = function(arg0) {
const ret = Number.isSafeInteger(getObject(arg0));
return ret;
};
imports.wbg.__wbg_new0_494c19a27871d56f = function() {
imports.wbg.__wbg_new0_c0be7df4b6bd481f = function() {
const ret = new Date();
return addHeapObject(ret);
};
imports.wbg.__wbg_toISOString_9970f74228c1a802 = function(arg0) {
imports.wbg.__wbg_toISOString_c588641de3e1665d = function(arg0) {
const ret = getObject(arg0).toISOString();
return addHeapObject(ret);
};
imports.wbg.__wbg_entries_13e011453776468f = function(arg0) {
imports.wbg.__wbg_entries_e51f29c7bba0c054 = function(arg0) {
const ret = Object.entries(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_is_20a2e5c82eecc47d = function(arg0, arg1) {
imports.wbg.__wbg_is_205d914af04a8faa = function(arg0, arg1) {
const ret = Object.is(getObject(arg0), getObject(arg1));
return ret;
};
imports.wbg.__wbg_toString_e2b23ac99490a381 = function(arg0) {
imports.wbg.__wbg_toString_a8e343996af880e9 = function(arg0) {
const ret = getObject(arg0).toString();
return addHeapObject(ret);
};
imports.wbg.__wbg_resolve_ae38ad63c43ff98b = function(arg0) {
imports.wbg.__wbg_resolve_53698b95aaf7fcf8 = function(arg0) {
const ret = Promise.resolve(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_8df675b8bb5d5e3c = function(arg0, arg1) {
imports.wbg.__wbg_then_f7e06ee3c11698eb = function(arg0, arg1) {
const ret = getObject(arg0).then(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_835b073a479138e5 = function(arg0, arg1, arg2) {
imports.wbg.__wbg_then_b2267541e2a73865 = function(arg0, arg1, arg2) {
const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_buffer_55ba7a6b1b92e2ac = function(arg0) {
imports.wbg.__wbg_buffer_085ec1f694018c4f = function(arg0) {
const ret = getObject(arg0).buffer;
return addHeapObject(ret);
};
imports.wbg.__wbg_newwithbyteoffsetandlength_88d1d8be5df94b9b = function(arg0, arg1, arg2) {
imports.wbg.__wbg_newwithbyteoffsetandlength_6da8e527659b86aa = function(arg0, arg1, arg2) {
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_new_09938a7d020f049b = function(arg0) {
imports.wbg.__wbg_new_8125e318e6245eed = function(arg0) {
const ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_set_3698e3ca519b3c3c = function(arg0, arg1, arg2) {
imports.wbg.__wbg_set_5cf90238115182c3 = function(arg0, arg1, arg2) {
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
};
imports.wbg.__wbg_length_0aab7ffd65ad19ed = function(arg0) {
imports.wbg.__wbg_length_72e2208bbc0efc61 = function(arg0) {
const ret = getObject(arg0).length;
return ret;
};
imports.wbg.__wbg_instanceof_Uint8Array_1349640af2da2e88 = function(arg0) {
imports.wbg.__wbg_instanceof_Uint8Array_d8d9cb2b8e8ac1d4 = function(arg0) {
let result;
try {
result = getObject(arg0) instanceof Uint8Array;
@ -1138,7 +1135,7 @@ function __wbg_get_imports() {
const ret = result;
return ret;
};
imports.wbg.__wbg_set_07da13cc24b69217 = function() { return handleError(function (arg0, arg1, arg2) {
imports.wbg.__wbg_set_092e06b0f9d71865 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
return ret;
}, arguments) };
@ -1162,15 +1159,15 @@ function __wbg_get_imports() {
const ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper4722 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 1080, __wbg_adapter_48);
imports.wbg.__wbindgen_closure_wrapper4681 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 1097, __wbg_adapter_48);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper5555 = function(arg0, arg1, arg2) {
imports.wbg.__wbindgen_closure_wrapper5482 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 1396, __wbg_adapter_51);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper5562 = function(arg0, arg1, arg2) {
imports.wbg.__wbindgen_closure_wrapper5489 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 1400, __wbg_adapter_54);
return addHeapObject(ret);
};

View file

@ -18,5 +18,7 @@
],
"module": "kanidmd_web_ui.js",
"homepage": "https://github.com/kanidm/kanidm/",
"sideEffects": false
"sideEffects": [
"./snippets/*"
]
}

View file

@ -0,0 +1,273 @@
use crate::utils;
#[cfg(debug_assertions)]
use gloo::console;
use kanidm_proto::v1::{CUIntentToken, UserAuthToken};
use yew::prelude::*;
use qrcode::render::svg;
use qrcode::QrCode;
use wasm_bindgen::UnwrapThrowExt;
use web_sys::Node;
use crate::error::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response};
enum State {
Valid,
Error { emsg: String, kopid: Option<String> },
}
#[allow(dead_code)]
enum CodeState {
Waiting,
Ready { token: CUIntentToken },
}
#[allow(dead_code)]
pub enum Msg {
Activate,
Ready { token: CUIntentToken },
Dismiss,
Error { emsg: String, kopid: Option<String> },
}
impl From<FetchError> for Msg {
fn from(fe: FetchError) -> Self {
Msg::Error {
emsg: fe.as_string(),
kopid: None,
}
}
}
#[derive(PartialEq, Eq, Properties)]
pub struct Props {
pub uat: UserAuthToken,
pub enabled: bool,
}
pub struct CreateResetCode {
state: State,
code_state: CodeState,
}
impl Component for CreateResetCode {
type Message = Msg;
type Properties = Props;
fn create(_ctx: &Context<Self>) -> Self {
CreateResetCode {
state: State::Valid,
code_state: CodeState::Waiting,
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::Activate => {
#[cfg(debug_assertions)]
console::debug!("modal activate");
let uat = &ctx.props().uat;
let id = uat.uuid.to_string();
ctx.link().send_future(async {
match Self::credential_get_update_intent_token(id).await {
Ok(v) => v,
Err(v) => v.into(),
}
});
true
}
Msg::Error { emsg, kopid } => {
self.code_state = CodeState::Waiting;
self.state = State::Error { emsg, kopid };
true
}
Msg::Dismiss => {
self.code_state = CodeState::Waiting;
self.state = State::Valid;
utils::modal_hide_by_id(crate::constants::ID_CRED_RESET_CODE);
true
}
Msg::Ready { token } => {
self.state = State::Valid;
self.code_state = CodeState::Ready { token };
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let button_enabled = ctx.props().enabled;
let flash = match &self.state {
State::Error { emsg, kopid } => {
let message = match kopid {
Some(k) => format!("An error occurred - {} - {}", emsg, k),
None => format!("An error occurred - {} - No Operation ID", emsg),
};
html! {
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{ message }
<button type="button" class="btn btn-close" data-dismiss="alert" aria-label="Close"></button>
</div>
}
}
_ => html! { <></> },
};
let code_reset_state = match &self.code_state {
CodeState::Waiting => html! {
<div class="spinner-border text-dark" role="status">
<span class="visually-hidden">{ "Loading..." }</span>
</div>
},
CodeState::Ready { token } => {
let mut url = utils::origin();
url.set_path("/ui/reset");
let reset_link = html! {
<a href={ url.to_string() }>{ url.to_string() }</a>
};
url.to_string();
url.query_pairs_mut()
.append_pair("token", token.token.as_str());
let qr = QrCode::new(url.as_str()).unwrap_throw();
let svg = qr.render::<svg::Color>().build();
#[allow(clippy::unwrap_used)]
let div = utils::document().create_element("div").unwrap();
div.set_inner_html(svg.as_str());
let node: Node = div.into();
let svg_html = Html::VRef(node);
let code = format!("Code: {}", token.token);
html! {
<>
<div class="col-6">
{ svg_html }
</div>
<div class="col-5">
<p>{ reset_link }</p>
<p>{ code }</p>
</div>
</>
}
}
};
html! {
<>
<button type="button" class="btn btn-primary"
disabled={ !button_enabled }
data-bs-toggle="modal"
data-bs-target={format!("#{}", crate::constants::ID_CRED_RESET_CODE)}
onclick={
ctx.link()
.callback(move |_| {
Msg::Activate
})
}
>
{ "Update your Authentication Settings on Another Device" }
</button>
<div class="modal" tabindex="-1" role="dialog" id={crate::constants::ID_CRED_RESET_CODE}>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{"Update your Authentication Settings on Another Device"}</h5>
<button
aria-label="Close"
class="btn-close"
onclick={
ctx.link()
.callback(move |_| {
Msg::Dismiss
})
}
type="button"
></button>
</div>
<div class="modal-body">
{ flash }
<div class="container">
<div class="row">
{ code_reset_state }
</div>
<div class="row">
<p>{ "You can add another device to your account by scanning this qr code, or going to the url above and entering in the code." }</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
onclick={
ctx.link().callback(|_e| {
Msg::Dismiss
})
}
>{"Cancel"}</button>
</div>
</div>
</div>
</div>
</>
}
}
fn changed(&mut self, _ctx: &Context<Self>, _props: &Self::Properties) -> bool {
false
}
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {}
fn destroy(&mut self, _ctx: &Context<Self>) {}
}
impl CreateResetCode {
async fn credential_get_update_intent_token(id: String) -> Result<Msg, FetchError> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::SameOrigin);
opts.credentials(RequestCredentials::SameOrigin);
let uri = format!("/v1/person/{}/_credential/_update_intent?ttl=0", id);
let request = Request::new_with_str_and_init(uri.as_str(), &opts)?;
request
.headers()
.set("content-type", "application/json")
.expect_throw("failed to set header");
let window = utils::window();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
let status = resp.status();
if status == 200 {
let jsval = JsFuture::from(resp.json()?).await?;
let token: CUIntentToken =
serde_wasm_bindgen::from_value(jsval).expect_throw("Invalid response type");
Ok(Msg::Ready { token })
} else {
let headers = resp.headers();
let kopid = headers.get("x-kanidm-opid").ok().flatten();
let text = JsFuture::from(resp.text()?).await?;
let emsg = text.as_string().unwrap_or_default();
// let jsval_json = JsFuture::from(resp.json()?).await?;
Ok(Msg::Error { emsg, kopid })
}
}
}

View file

@ -6,6 +6,7 @@ pub mod admin_groups;
pub mod admin_menu;
pub mod admin_oauth2;
pub mod change_unix_password;
pub mod create_reset_code;
/// creates the "Kanidm is alpha" banner
pub fn alpha_warning_banner() -> Html {

View file

@ -11,6 +11,7 @@ pub const ID_SIGNOUTMODAL: &str = "signoutModal";
// the HTML element ID that the unix password dialog box has
pub const ID_UNIX_PASSWORDCHANGE: &str = "unixPasswordModal";
pub const ID_CRED_RESET_CODE: &str = "credResetCodeModal";
// classes for buttons
pub const CLASS_BUTTON_DARK: &str = "btn btn-dark";
pub const CLASS_BUTTON_SUCCESS: &str = "btn btn-success";

View file

@ -260,21 +260,7 @@ impl Component for PasskeyModalApp {
let label_val = self.label_val.clone();
let passkey_state = match &self.state {
State::Init => {
html! {
<button id="passkey-generate" type="button" class="btn btn-secondary"
onclick={
ctx.link()
.callback(move |_| {
Msg::Generate
})
}
>
// TODO: start the session once the modal is popped up
{ "Start Creating a New Passkey" }</button>
}
}
State::Submitting | State::FetchingChallenge => {
State::Init | State::Submitting | State::FetchingChallenge => {
html! {
<div class="spinner-border text-dark" role="status">
<span class="visually-hidden">{ "Loading..." }</span>
@ -291,7 +277,7 @@ impl Component for PasskeyModalApp {
Msg::CredentialCreate
})
}
>{ "Do it!" }</button>
>{ "Begin Passkey Enrollment" }</button>
}
}
State::CredentialReady(_) => {
@ -363,6 +349,20 @@ impl Component for PasskeyModalApp {
};
html! {
<>
<button type="button"
class="btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#staticPasskeyCreate"
onclick={
ctx.link()
.callback(move |_| {
Msg::Generate
})
}
>
{ "Add Passkey" }
</button>
<div class="modal fade" id="staticPasskeyCreate" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticPasskeyLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@ -391,6 +391,7 @@ impl Component for PasskeyModalApp {
</div>
</div>
</div>
</>
}
}
}

View file

@ -370,9 +370,7 @@ impl CredentialResetApp {
<p>{ "❌ MFA Disabled" }</p>
<p>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticTotpCreate">
{ "Add TOTP" }
</button>
<TotpModalApp token={ token.clone() } cb={ cb.clone() }/>
</p>
<p>
@ -413,9 +411,7 @@ impl CredentialResetApp {
</>
<p>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticTotpCreate">
{ "Add TOTP" }
</button>
<TotpModalApp token={ token.clone() } cb={ cb.clone() }/>
</p>
<p>
@ -506,9 +502,7 @@ impl CredentialResetApp {
{ passkey_html }
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticPasskeyCreate">
{ "Add Passkey" }
</button>
<PasskeyModalApp token={ token.clone() } cb={ cb.clone() } />
<hr class="my-4" />
@ -543,12 +537,9 @@ impl CredentialResetApp {
</div>
</main>
<PasskeyModalApp token={ token.clone() } cb={ cb.clone() } />
<PwModalApp token={ token.clone() } cb={ cb.clone() } />
<TotpModalApp token={ token.clone() } cb={ cb.clone() }/>
<DeleteApp token= { token.clone() } cb={ cb.clone() }/>
{ passkey_modals_html }

View file

@ -5,7 +5,7 @@ use qrcode::render::svg;
use qrcode::QrCode;
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
use wasm_bindgen_futures::JsFuture;
use web_sys::{Node, Request, RequestInit, RequestMode, Response};
use web_sys::{Node, Request, RequestCredentials, RequestInit, RequestMode, Response};
use yew::prelude::*;
use super::reset::{EventBusMsg, ModalProps};
@ -77,6 +77,7 @@ impl TotpModalApp {
let mut opts = RequestInit::new();
opts.method("POST");
opts.mode(RequestMode::SameOrigin);
opts.credentials(RequestCredentials::SameOrigin);
opts.body(Some(&req_jsvalue));
@ -300,22 +301,7 @@ impl Component for TotpModalApp {
let totp_secret_state = match &self.secret {
// TODO: change this so it automagically starts the cred update session once the modal is created.
TotpValue::Init => {
html! {
<button
class="btn btn-secondary"
id="totp-generate"
type="button"
onclick={
ctx.link()
.callback(move |_| {
Msg::TotpGenerate
})
}
>{ "Click here to start the TOTP registration process" }</button>
}
}
TotpValue::Waiting => {
TotpValue::Init | TotpValue::Waiting => {
html! {
<div class="spinner-border text-dark" role="status">
<span class="visually-hidden">{ "Loading..." }</span>
@ -359,6 +345,20 @@ impl Component for TotpModalApp {
};
html! {
<>
<button type="button"
class="btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#staticTotpCreate"
onclick={
ctx.link()
.callback(move |_| {
Msg::TotpGenerate
})
}
>
{ "Add TOTP" }
</button>
<div class="modal fade" id="staticTotpCreate" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticTotpCreate" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
@ -459,6 +459,7 @@ impl Component for TotpModalApp {
</div>
</div>
</div>
</>
}
}
}

View file

@ -20,11 +20,10 @@ use crate::error::FetchError;
use crate::{models, utils};
pub struct LoginApp {
inputvalue: String,
state: LoginState,
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Clone)]
pub enum LoginWorkflow {
Login,
Reauth,
@ -43,8 +42,15 @@ enum TotpState {
}
enum LoginState {
InitLogin { enable: bool, remember_me: bool },
InitReauth { enable: bool },
InitLogin {
enable: bool,
remember_me: bool,
username: String,
},
InitReauth {
enable: bool,
spn: String,
},
// Select between different cred types, either password (and MFA) or Passkey
Select(Vec<AuthMech>),
// The choices of authentication mechanism.
@ -56,14 +62,16 @@ enum LoginState {
Passkey(CredentialRequestOptions),
SecurityKey(CredentialRequestOptions),
// Error, state handling.
Error { emsg: String, kopid: Option<String> },
Error {
emsg: String,
kopid: Option<String>,
},
UnknownUser,
Denied(String),
Authenticated,
}
pub enum LoginAppMsg {
Input(String),
Restart,
Begin,
PasswordSubmit,
@ -291,12 +299,14 @@ impl LoginApp {
}
fn view_state(&self, ctx: &Context<Self>) -> Html {
let inputvalue = self.inputvalue.clone();
match &self.state {
LoginState::InitLogin {
enable,
remember_me,
username,
} => {
let username = username.clone();
html! {
<>
<div class="container">
@ -316,10 +326,9 @@ impl LoginApp {
disabled={ !enable }
id="username"
name="username"
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e))) }
type="text"
autocomplete="username"
value={ inputvalue }
value={ username }
/>
</div>
@ -347,11 +356,12 @@ impl LoginApp {
</>
}
}
LoginState::InitReauth { enable } => {
LoginState::InitReauth { enable, spn } => {
let msg = format!("Reauthenticate as {} to continue", spn);
html! {
<>
<div class="container">
<p>{ "Reauthenticate to continue" }</p>
<p>{ msg }</p>
<form
onsubmit={ ctx.link().callback(|e: SubmitEvent| {
#[cfg(debug_assertions)]
@ -363,7 +373,9 @@ impl LoginApp {
<div class={CLASS_DIV_LOGIN_BUTTON}>
<button
type="submit"
class={CLASS_BUTTON_DARK}
class="autofocus form-control btn btn-dark"
autofocus=true
id="begin"
disabled={ !enable }
>{" Begin "}</button>
</div>
@ -431,10 +443,9 @@ impl LoginApp {
disabled={ !enable }
id="password"
name="password"
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e))) }
type="password"
autocomplete="current-password"
value={ inputvalue }
value=""
/>
</div>
<div class={CLASS_DIV_LOGIN_BUTTON}>
@ -467,10 +478,9 @@ impl LoginApp {
disabled={ !enable }
id="backup_code"
name="backup_code"
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e))) }
type="text"
autocomplete="off"
value={ inputvalue }
value=""
/>
</div>
<div class={CLASS_DIV_LOGIN_BUTTON}>
@ -498,12 +508,11 @@ impl LoginApp {
autofocus=true
class="autofocus form-control"
disabled={ state==&TotpState::Disabled }
id="otp"
name="otp"
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e)))}
id="totp"
name="totp"
type="text"
autocomplete="off"
value={ inputvalue }
value=""
/>
</div>
<div class={CLASS_DIV_LOGIN_BUTTON}>
@ -648,21 +657,18 @@ impl Component for LoginApp {
#[cfg(debug_assertions)]
console::debug!("create".to_string());
let mut inputvalue = "".to_string();
let workflow = ctx.props().workflow;
let workflow = &ctx.props().workflow;
let state = match workflow {
LoginWorkflow::Login => {
// Assume we are here for a good reason.
// -- clear the bearer to prevent conflict
models::clear_bearer_token();
// Do we have a login hint?
let (model_iv, remember_me) = models::pop_login_hint()
let (username, remember_me) = models::get_login_hint()
.map(|user| (user, false))
.or_else(|| models::get_login_remember_me().map(|user| (user, true)))
.unwrap_or_default();
inputvalue = model_iv;
#[cfg(debug_assertions)]
{
let document = utils::document();
@ -681,18 +687,29 @@ impl Component for LoginApp {
LoginState::InitLogin {
enable: true,
remember_me,
username,
}
}
LoginWorkflow::Reauth => {
// Unlike login, don't clear tokens or cookies - these are needed during the operation
// to actually start the reauth as the same user.
LoginState::InitReauth { enable: true }
match models::get_login_hint() {
Some(spn) => LoginState::InitReauth {
enable: true,
spn: spn.clone(),
},
None => LoginState::Error {
emsg: "Client Error - No login hint available".to_string(),
kopid: None,
},
}
}
};
add_body_form_classes!();
LoginApp { inputvalue, state }
LoginApp { state }
}
fn changed(&mut self, _ctx: &Context<Self>, _props: &Self::Properties) -> bool {
@ -701,38 +718,46 @@ impl Component for LoginApp {
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
LoginAppMsg::Input(mut inputvalue) => {
std::mem::swap(&mut self.inputvalue, &mut inputvalue);
true
}
LoginAppMsg::Restart => {
// Clear any leftover input. Reset to the remembered username if any.
match ctx.props().workflow {
match &ctx.props().workflow {
LoginWorkflow::Login => {
let (inputvalue, remember_me) = models::get_login_remember_me()
.map(|user| (user, true))
let (username, remember_me) = models::get_login_hint()
.map(|user| (user, false))
.or_else(|| models::get_login_remember_me().map(|user| (user, true)))
.unwrap_or_default();
self.inputvalue = inputvalue;
self.state = LoginState::InitLogin {
enable: true,
remember_me,
username,
};
}
LoginWorkflow::Reauth => {
self.inputvalue = "".to_string();
self.state = LoginState::InitReauth { enable: true };
match models::get_login_hint() {
Some(spn) => LoginState::InitReauth {
enable: true,
spn: spn.clone(),
},
None => LoginState::Error {
emsg: "Client Error - No login hint available".to_string(),
kopid: None,
},
};
}
}
true
}
LoginAppMsg::Begin => {
match ctx.props().workflow {
match &ctx.props().workflow {
LoginWorkflow::Login => {
#[cfg(debug_assertions)]
console::debug!(format!("begin -> {:?}", self.inputvalue));
// Disable the button?
let username = self.inputvalue.clone();
let username =
utils::get_value_from_element_id("username").unwrap_or_default();
#[cfg(debug_assertions)]
console::debug!(format!("begin for username -> {:?}", username));
// If the remember-me was checked, stash it here.
// If it was false, clear existing data.
@ -750,8 +775,10 @@ impl Component for LoginApp {
#[cfg(debug_assertions)]
console::debug!(format!("begin remember_me -> {:?}", remember_me));
let username_clone = username.clone();
ctx.link().send_future(async {
match Self::auth_init(username).await {
match Self::auth_init(username_clone).await {
Ok(v) => v,
Err(v) => v.into(),
}
@ -760,6 +787,7 @@ impl Component for LoginApp {
self.state = LoginState::InitLogin {
enable: false,
remember_me,
username,
};
}
LoginWorkflow::Reauth => {
@ -770,19 +798,29 @@ impl Component for LoginApp {
}
});
self.inputvalue = "".to_string();
self.state = LoginState::InitReauth { enable: false };
self.state = match models::get_login_hint() {
Some(spn) => LoginState::InitReauth {
enable: false,
spn: spn.clone(),
},
None => LoginState::Error {
emsg: "Client Error - No login hint available".to_string(),
kopid: None,
},
};
}
}
true
}
LoginAppMsg::PasswordSubmit => {
let password = utils::get_value_from_element_id("password").unwrap_or_default();
#[cfg(debug_assertions)]
console::debug!("At password step".to_string());
console::debug!("password step".to_string());
// Disable the button?
self.state = LoginState::Password(false);
let authreq = AuthRequest {
step: AuthStep::Cred(AuthCredential::Password(self.inputvalue.clone())),
step: AuthStep::Cred(AuthCredential::Password(password)),
};
ctx.link().send_future(async {
match Self::auth_step(authreq).await {
@ -790,17 +828,18 @@ impl Component for LoginApp {
Err(v) => v.into(),
}
});
// Clear the password from memory.
self.inputvalue = "".to_string();
true
}
LoginAppMsg::BackupCodeSubmit => {
let backup_code =
utils::get_value_from_element_id("backup_code").unwrap_or_default();
#[cfg(debug_assertions)]
console::debug!("backupcode".to_string());
console::debug!("backup_code".to_string());
// Disable the button?
self.state = LoginState::BackupCode(false);
let authreq = AuthRequest {
step: AuthStep::Cred(AuthCredential::BackupCode(self.inputvalue.clone())),
step: AuthStep::Cred(AuthCredential::BackupCode(backup_code)),
};
ctx.link().send_future(async {
match Self::auth_step(authreq).await {
@ -808,15 +847,15 @@ impl Component for LoginApp {
Err(v) => v.into(),
}
});
// Clear the backup code from memory.
self.inputvalue = "".to_string();
true
}
LoginAppMsg::TotpSubmit => {
let totp_str = utils::get_value_from_element_id("totp").unwrap_or_default();
#[cfg(debug_assertions)]
console::debug!("totp".to_string());
// Disable the button?
match self.inputvalue.parse::<u32>() {
match totp_str.parse::<u32>() {
Ok(totp) => {
self.state = LoginState::Totp(TotpState::Disabled);
let authreq = AuthRequest {
@ -834,9 +873,6 @@ impl Component for LoginApp {
}
}
// Clear the totp from memory.
self.inputvalue = "".to_string();
true
}
LoginAppMsg::SecurityKeySubmit(resp) => {
@ -871,7 +907,6 @@ impl Component for LoginApp {
}
LoginAppMsg::Start(resp) => {
// Clear any leftover input
self.inputvalue = "".to_string();
#[cfg(debug_assertions)]
console::debug!(format!("start -> {:?}", resp));
match resp.state {
@ -949,7 +984,6 @@ impl Component for LoginApp {
}
LoginAppMsg::Next(resp) => {
// Clear any leftover input
self.inputvalue = "".to_string();
#[cfg(debug_assertions)]
console::debug!(format!("next -> {:?}", resp));
@ -1066,14 +1100,12 @@ impl Component for LoginApp {
}
LoginAppMsg::UnknownUser => {
// Clear any leftover input
self.inputvalue = "".to_string();
console::warn!("Unknown user".to_string());
self.state = LoginState::UnknownUser;
true
}
LoginAppMsg::Error { emsg, kopid } => {
// Clear any leftover input
self.inputvalue = "".to_string();
console::error!(format!("error -> {:?}, {:?}", emsg, kopid));
self.state = LoginState::Error { emsg, kopid };
true
@ -1124,6 +1156,10 @@ impl Component for LoginApp {
fn destroy(&mut self, _ctx: &Context<Self>) {
#[cfg(debug_assertions)]
console::debug!("login::destroy".to_string());
// Done with this, clear it.
let _ = models::pop_login_hint();
remove_body_form_classes!();
}
@ -1135,5 +1171,6 @@ impl Component for LoginApp {
crate::utils::autofocus("password");
crate::utils::autofocus("backup_code");
crate::utils::autofocus("otp");
crate::utils::autofocus("begin");
}
}

View file

@ -22,9 +22,6 @@ pub enum Route {
#[at("/")]
Landing,
#[at("/ui/view/*")]
Views,
#[at("/ui/login")]
Login,
@ -40,6 +37,9 @@ pub enum Route {
#[not_found]
#[at("/ui/404")]
NotFound,
#[at("/ui/*")]
Views,
}
#[function_component(Landing)]

View file

@ -61,6 +61,13 @@ pub fn push_login_hint(r: String) {
TemporaryStorage::set("login_hint", r).expect_throw("failed to set login hint");
}
pub fn get_login_hint() -> Option<String> {
let l: Result<String, _> = TemporaryStorage::get("login_hint");
#[cfg(debug_assertions)]
console::debug!(format!("login_hint::get_login_hint -> {:?}", l).as_str());
l.ok()
}
pub fn pop_login_hint() -> Option<String> {
let l: Result<String, _> = TemporaryStorage::get("login_hint");
#[cfg(debug_assertions)]
@ -76,7 +83,7 @@ pub fn push_login_remember_me(r: String) {
pub fn get_login_remember_me() -> Option<String> {
let l: Result<String, _> = PersistentStorage::get("login_remember_me");
#[cfg(debug_assertions)]
console::debug!(format!("login_hint::pop_login_remember_me -> {:?}", l).as_str());
console::debug!(format!("login_hint::get_login_remember_me -> {:?}", l).as_str());
l.ok()
}

View file

@ -1,11 +1,10 @@
use gloo::console;
use gloo_net::http::Request;
use url::Url;
use wasm_bindgen::prelude::*;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
pub use web_sys::InputEvent;
use web_sys::{
Document, Event, HtmlElement, HtmlInputElement, RequestCredentials, RequestMode, Window,
};
use web_sys::{Document, HtmlElement, HtmlInputElement, RequestCredentials, RequestMode, Window};
use yew::virtual_dom::VNode;
use yew::{html, Html};
@ -23,6 +22,15 @@ pub fn body() -> HtmlElement {
document().body().expect_throw("Unable to retrieve body")
}
pub fn origin() -> Url {
let uri_string = document()
.document_uri()
.expect_throw("Unable to access document uri");
let mut url = Url::parse(&uri_string).expect_throw("Unable to parse document uri");
url.set_path("/");
url
}
pub fn autofocus(target: &str) {
// If an element with an id attribute matching 'target' exists, focus it.
let doc = document();
@ -38,12 +46,12 @@ pub fn autofocus(target: &str) {
}
}
pub fn get_value_from_input_event(e: InputEvent) -> String {
let event: Event = e.dyn_into().unwrap_throw();
let event_target = event.target().unwrap_throw();
let target: HtmlInputElement = event_target.dyn_into().unwrap_throw();
target.value()
}
// pub fn get_value_from_input_event(e: InputEvent) -> String {
// let event: Event = e.dyn_into().unwrap_throw();
// let event_target = event.target().unwrap_throw();
// let target: HtmlInputElement = event_target.dyn_into().unwrap_throw();
// target.value()
// }
// pub fn get_element_by_id(id: &str) -> Option<HtmlElement> {
// document()

View file

@ -66,7 +66,19 @@ impl Component for AppsApp {
#[cfg(debug_assertions)]
console::debug!("views::apps::update");
match msg {
Msg::Ready { apps } => self.state = State::Ready { apps },
Msg::Ready { mut apps } => {
apps.sort_by(|a, b| match (a, b) {
(
AppLink::Oauth2 {
display_name: dna, ..
},
AppLink::Oauth2 {
display_name: dnb, ..
},
) => dna.cmp(dnb),
});
self.state = State::Ready { apps }
}
Msg::Error { emsg, kopid } => self.state = State::Error { emsg, kopid },
}

View file

@ -1 +0,0 @@

View file

@ -13,56 +13,50 @@ use crate::manager::Route;
use crate::{models, utils};
mod apps;
mod components;
mod profile;
mod security;
use apps::AppsApp;
use profile::ProfileApp;
use security::SecurityApp;
#[derive(Routable, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub enum ViewRoute {
#[at("/ui/view/admin/*")]
#[at("/ui/admin/*")]
Admin,
#[at("/ui/view/apps")]
#[at("/ui/apps")]
Apps,
#[at("/ui/view/profile")]
#[at("/ui/profile")]
Profile,
#[at("/ui/view/security")]
Security,
#[not_found]
#[at("/ui/view/404")]
#[at("/ui/404")]
NotFound,
}
#[derive(Routable, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub enum AdminRoute {
#[at("/ui/view/admin/menu")]
#[at("/ui/admin/menu")]
AdminMenu,
#[at("/ui/view/admin/groups")]
#[at("/ui/admin/groups")]
AdminListGroups,
#[at("/ui/view/admin/accounts")]
#[at("/ui/admin/accounts")]
AdminListAccounts,
#[at("/ui/view/admin/oauth2")]
#[at("/ui/admin/oauth2")]
AdminListOAuth2,
#[at("/ui/view/admin/group/:uuid")]
#[at("/ui/admin/group/:uuid")]
ViewGroup { uuid: String },
#[at("/ui/view/admin/person/:uuid")]
#[at("/ui/admin/person/:uuid")]
ViewPerson { uuid: String },
#[at("/ui/view/admin/service_account/:uuid")]
#[at("/ui/admin/service_account/:uuid")]
ViewServiceAccount { uuid: String },
#[at("/ui/view/admin/oauth2/:rs_name")]
#[at("/ui/admin/oauth2/:rs_name")]
ViewOAuth2RP { rs_name: String },
#[not_found]
#[at("/ui/view/admin/404")]
#[at("/ui/404")]
NotFound,
}
@ -257,7 +251,7 @@ impl ViewsApp {
</Link<ViewRoute>>
</li>
if ui_hint_experimental {
if credential_update {
<li class="mb-1">
<Link<ViewRoute> classes="nav-link" to={ViewRoute::Profile}>
<span data-feather="file"></span>
@ -266,15 +260,6 @@ impl ViewsApp {
</li>
}
if credential_update {
<li class="mb-1">
<Link<ViewRoute> classes="nav-link" to={ViewRoute::Security}>
<span data-feather="file"></span>
{ "Security" }
</Link<ViewRoute>>
</li>
}
if ui_hint_experimental {
<li class="mb-1">
<Link<AdminRoute> classes="nav-link" to={AdminRoute::AdminMenu}>
@ -331,7 +316,6 @@ impl ViewsApp {
#[allow(clippy::let_unit_value)]
ViewRoute::Apps => html! { <AppsApp /> },
ViewRoute::Profile => html! { <ProfileApp current_user_uat={ current_user_uat.clone() } /> },
ViewRoute::Security => html! { <SecurityApp current_user_uat={ current_user_uat.clone() } /> },
ViewRoute::NotFound => html! {
<Redirect<Route> to={Route::NotFound}/>
},

View file

@ -1,82 +1,35 @@
#[cfg(debug_assertions)]
use gloo::console;
use kanidm_proto::v1::{Entry, WhoamiResponse};
use uuid::Uuid;
use kanidm_proto::v1::{CUSessionToken, CUStatus, UiHint, UserAuthToken};
use time::format_description::well_known::Rfc3339;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response};
use yew::prelude::*;
use yew::virtual_dom::VNode;
use yew_router::prelude::*;
use crate::components::change_unix_password::ChangeUnixPassword;
use crate::components::create_reset_code::CreateResetCode;
use crate::constants::CSS_PAGE_HEADER;
use crate::error::FetchError;
use crate::utils;
use crate::views::ViewProps;
struct Profile {
mail_primary: Option<String>,
spn: String,
displayname: String,
groups: Vec<String>,
uuid: Uuid,
}
impl TryFrom<Entry> for Profile {
type Error = String;
fn try_from(entry: Entry) -> Result<Self, Self::Error> {
console::error!("Entry Dump", format!("{:?}", entry));
let uuid = entry
.attrs
.get("uuid")
.and_then(|list| list.get(0))
.ok_or_else(|| "Missing UUID".to_string())
.and_then(|uuid_str| {
Uuid::parse_str(uuid_str).map_err(|_| "Invalid UUID".to_string())
})?;
let spn = entry
.attrs
.get("spn")
.and_then(|list| list.get(0))
.cloned()
.ok_or_else(|| "Missing SPN".to_string())?;
let displayname = entry
.attrs
.get("displayname")
.and_then(|list| list.get(0))
.cloned()
.ok_or_else(|| "Missing displayname".to_string())?;
let mut groups = entry.attrs.get("memberof").cloned().unwrap_or_default();
groups.sort_unstable();
let mail_primary = entry
.attrs
.get("mail_primary")
.and_then(|list| list.get(0))
.cloned();
Ok(Profile {
mail_primary,
spn,
displayname,
groups,
uuid,
})
}
}
enum State {
Loading,
Ready(Profile),
Error { emsg: String, kopid: Option<String> },
}
use crate::error::*;
use crate::manager::Route;
use crate::views::{ViewProps, ViewRoute};
use crate::{models, utils};
#[allow(clippy::large_enum_variant)]
// Page state
pub enum Msg {
Profile { entry: Entry },
Error { emsg: String, kopid: Option<String> },
// Nothing
RequestCredentialUpdate,
BeginCredentialUpdate {
token: CUSessionToken,
status: CUStatus,
},
Error {
emsg: String,
kopid: Option<String>,
},
RequestReauth,
}
impl From<FetchError> for Msg {
@ -88,7 +41,12 @@ impl From<FetchError> for Msg {
}
}
// User Profile UI
enum State {
Init,
Waiting,
Error { emsg: String, kopid: Option<String> },
}
pub struct ProfileApp {
state: State,
}
@ -97,37 +55,70 @@ impl Component for ProfileApp {
type Message = Msg;
type Properties = ViewProps;
fn create(ctx: &Context<Self>) -> Self {
fn create(_ctx: &Context<Self>) -> Self {
#[cfg(debug_assertions)]
console::debug!("views::profile::create");
ctx.link().send_future(async {
match Self::fetch_profile_data().await {
Ok(v) => v,
Err(v) => v.into(),
}
});
ProfileApp {
state: State::Loading,
}
console::debug!("views::security::create");
ProfileApp { state: State::Init }
}
fn changed(&mut self, _ctx: &Context<Self>, _props: &Self::Properties) -> bool {
#[cfg(debug_assertions)]
console::debug!("views::security::changed");
true
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
#[cfg(debug_assertions)]
console::debug!("profile::update");
console::debug!("views::security::update");
match msg {
Msg::Profile { entry } => {
self.state = match Profile::try_from(entry) {
Ok(profile) => State::Ready(profile),
Err(emsg) => State::Error { emsg, kopid: None },
};
Msg::RequestCredentialUpdate => {
// Submit a req to init the session.
// The uuid we want to submit against - hint, it's us.
let uat = &ctx.props().current_user_uat;
let id = uat.uuid.to_string();
ctx.link().send_future(async {
match Self::request_credential_update(id).await {
Ok(v) => v,
Err(v) => v.into(),
}
});
self.state = State::Waiting;
true
}
Msg::BeginCredentialUpdate { token, status } => {
// Got the rec, setup.
models::push_cred_update_session((token, status));
models::push_return_location(models::Location::Views(ViewRoute::Profile));
ctx.link()
.navigator()
.expect_throw("failed to read history")
.push(&Route::CredentialReset);
// No need to redraw, or reset state, since this redirect will destroy
// the state.
false
}
Msg::RequestReauth => {
models::push_return_location(models::Location::Views(ViewRoute::Profile));
let uat = &ctx.props().current_user_uat;
let spn = uat.spn.to_string();
// Setup the ui hint.
models::push_login_hint(spn);
ctx.link()
.navigator()
.expect_throw("failed to read history")
.push(&Route::Reauth);
// No need to redraw, or reset state, since this redirect will destroy
// the state.
false
}
Msg::Error { emsg, kopid } => {
self.state = State::Error { emsg, kopid };
true
@ -137,156 +128,126 @@ impl Component for ProfileApp {
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
#[cfg(debug_assertions)]
console::debug!("views::profile::rendered");
console::debug!("views::security::rendered");
}
fn view(&self, ctx: &Context<Self>) -> Html {
match &self.state {
State::Loading => {
let uat = &ctx.props().current_user_uat;
let jsdate = js_sys::Date::new_0();
let isotime: String = jsdate.to_iso_string().into();
// TODO: Actually check the time of expiry on the uat and have a timer set that
// re-locks things nicely.
let time = time::OffsetDateTime::parse(&isotime, &Rfc3339)
.map(|odt| odt + time::Duration::new(60, 0))
.expect_throw("Unable to process time stamp");
let is_priv_able = uat.purpose_readwrite_active(time);
let submit_enabled = match self.state {
State::Init | State::Error { .. } => is_priv_able,
State::Waiting => false,
};
let flash = match &self.state {
State::Error { emsg, kopid } => {
let message = match kopid {
Some(k) => format!("An error occurred - {} - {}", emsg, k),
None => format!("An error occurred - {} - No Operation ID", emsg),
};
html! {
<main class="text-center form-signin h-100">
<div class="vert-center">
<div class="spinner-border text-dark" role="status">
<span class="visually-hidden">{ "Loading..." }</span>
</div>
</div>
</main>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{ message }
<button type="button" class="btn btn-close" data-dismiss="alert" aria-label="Close"></button>
</div>
}
}
State::Ready(profile) => self.view_profile(ctx, profile),
State::Error { emsg, kopid } => self.do_alert_error(
"An error has occurred 😔 ",
Some(
format!(
"{}\n\n{}",
emsg.as_str(),
if let Some(opid) = kopid.as_ref() {
format!("Operation ID: {}", opid.clone())
} else {
"Error occurred client-side.".to_string()
}
)
.as_str(),
),
ctx,
),
_ => html! { <></> },
};
let main = if is_priv_able {
self.view_profile(ctx, submit_enabled, uat.clone())
} else {
html! {
<div>
<button type="button" class="btn btn-primary"
onclick={
ctx.link().callback(|_e| {
Msg::RequestReauth
})
}
>
{ "Unlock Profile Settings 🔒" }
</button>
</div>
}
};
html! {
<>
<div class={CSS_PAGE_HEADER}>
<h2>{ "Profile" }</h2>
</div>
{ flash }
{ main }
</>
}
}
}
impl ProfileApp {
fn do_alert_error(
&self,
alert_title: &str,
alert_message: Option<&str>,
_ctx: &Context<Self>,
) -> VNode {
fn view_profile(&self, ctx: &Context<Self>, submit_enabled: bool, uat: UserAuthToken) -> Html {
html! {
<div class="container">
<div class="row justify-content-md-center">
<div class="alert alert-danger" role="alert">
<p><strong>{ alert_title }</strong></p>
if let Some(value) = alert_message {
<p>{ value }</p>
}
<>
<div>
<button type="button" class="btn btn-primary"
disabled=true
>
{ "Profile Settings Unlocked 🔓" }
</button>
</div>
<hr/>
<div>
<p>
<button type="button" class="btn btn-primary"
disabled={ !submit_enabled }
onclick={
ctx.link().callback(|_e| {
Msg::RequestCredentialUpdate
})
}
>
{ "Password and Authentication Settings" }
</button>
</p>
</div>
<hr/>
<div>
<p>
<CreateResetCode uat={ uat.clone() } enabled={ submit_enabled } />
</p>
</div>
<hr/>
if uat.ui_hints.contains(&UiHint::PosixAccount) {
<div>
<p>
<ChangeUnixPassword uat={ uat } enabled={ submit_enabled } />
</p>
</div>
</div>
</div>
}
</>
}
}
/// UI view for the user profile
fn view_profile(&self, _ctx: &Context<Self>, profile: &Profile) -> Html {
let mail_primary = match profile.mail_primary.as_ref() {
Some(email_address) => {
html! {
<a href={ format!("mailto:{}", &email_address)}>
{email_address}
</a>
}
}
None => html! { {"<primary email is unset>"}},
};
let spn = &profile.spn.to_owned();
let spn_split = spn.split('@');
let username = &spn_split.clone().next().unwrap_throw();
let domain = &spn_split.clone().last().unwrap_throw();
let display_name = profile.displayname.to_owned();
let user_groups: Vec<String> = profile
.groups
.iter()
.map(|group_spn| {
#[allow(clippy::unwrap_used)]
group_spn.split('@').next().unwrap().to_string()
})
.collect();
let pagecontent = html! {
<dl class="row">
<dt class="col-6">{ "Display Name" }</dt>
<dd class="col">{ display_name }</dd>
<dt class="col-6">{ "Primary Email" }</dt>
<dd class="col">{mail_primary}</dd>
<dt class="col-6">{ "Group Memberships" }</dt>
<dd class="col">
<ul class="list-group">
{
if user_groups.is_empty() {
html!{
<li>{"Not a member of any groups"}</li>
}
} else {
html!{
{
for user_groups.iter()
.map(|group|
html!{ <li>{ group }</li> }
)
}
}
}
}
</ul>
</dd>
<dt class="col-6">
{ "User's SPN" }
</dt>
<dd class="col">
{ username.to_string() }{"@"}{ domain }
</dd>
<dt class="col-6">
{ "User's UUID" }
</dt>
<dd class="col">
{ format!("{}", &profile.uuid ) }
</dd>
</dl>
};
html! {
<>
<div class={CSS_PAGE_HEADER}>
<h2>{ "Profile" }</h2>
</div>
{ pagecontent }
</>
}
}
async fn fetch_profile_data() -> Result<Msg, FetchError> {
async fn request_credential_update(id: String) -> Result<Msg, FetchError> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::SameOrigin);
opts.credentials(RequestCredentials::SameOrigin);
let request = Request::new_with_str_and_init("/v1/self", &opts)?;
let uri = format!("/v1/person/{}/_credential/_update", id);
let request = Request::new_with_str_and_init(uri.as_str(), &opts)?;
request
.headers()
@ -300,21 +261,15 @@ impl ProfileApp {
if status == 200 {
let jsval = JsFuture::from(resp.json()?).await?;
let state: WhoamiResponse = serde_wasm_bindgen::from_value(jsval)
.map_err(|e| {
let e_msg = format!("serde error -> {:?}", e);
console::error!(e_msg.as_str());
})
.expect_throw("Invalid response type");
Ok(Msg::Profile {
entry: state.youare,
})
let (token, status): (CUSessionToken, CUStatus) =
serde_wasm_bindgen::from_value(jsval).expect_throw("Invalid response type");
Ok(Msg::BeginCredentialUpdate { token, status })
} else {
let headers = resp.headers();
let kopid = headers.get("x-kanidm-opid").ok().flatten();
let text = JsFuture::from(resp.text()?).await?;
let emsg = text.as_string().unwrap_or_default();
// let jsval_json = JsFuture::from(resp.json()?).await?;
Ok(Msg::Error { emsg, kopid })
}
}

View file

@ -1,257 +0,0 @@
#[cfg(debug_assertions)]
use gloo::console;
use kanidm_proto::v1::{CUSessionToken, CUStatus, UiHint};
use time::format_description::well_known::Rfc3339;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response};
use yew::prelude::*;
use yew_router::prelude::*;
use crate::components::change_unix_password::ChangeUnixPassword;
use crate::constants::CSS_PAGE_HEADER;
use crate::error::*;
use crate::manager::Route;
use crate::views::{ViewProps, ViewRoute};
use crate::{models, utils};
#[allow(clippy::large_enum_variant)]
// Page state
pub enum Msg {
// Nothing
RequestCredentialUpdate,
BeginCredentialUpdate {
token: CUSessionToken,
status: CUStatus,
},
Error {
emsg: String,
kopid: Option<String>,
},
RequestReauth,
}
impl From<FetchError> for Msg {
fn from(fe: FetchError) -> Self {
Msg::Error {
emsg: fe.as_string(),
kopid: None,
}
}
}
enum State {
Init,
Waiting,
Error { emsg: String, kopid: Option<String> },
}
pub struct SecurityApp {
state: State,
}
impl Component for SecurityApp {
type Message = Msg;
type Properties = ViewProps;
fn create(_ctx: &Context<Self>) -> Self {
#[cfg(debug_assertions)]
console::debug!("views::security::create");
SecurityApp { state: State::Init }
}
fn changed(&mut self, _ctx: &Context<Self>, _props: &Self::Properties) -> bool {
#[cfg(debug_assertions)]
console::debug!("views::security::changed");
true
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
#[cfg(debug_assertions)]
console::debug!("views::security::update");
match msg {
Msg::RequestCredentialUpdate => {
// Submit a req to init the session.
// The uuid we want to submit against - hint, it's us.
let uat = &ctx.props().current_user_uat;
let id = uat.uuid.to_string();
ctx.link().send_future(async {
match Self::request_credential_update(id).await {
Ok(v) => v,
Err(v) => v.into(),
}
});
self.state = State::Waiting;
true
}
Msg::BeginCredentialUpdate { token, status } => {
// Got the rec, setup.
models::push_cred_update_session((token, status));
models::push_return_location(models::Location::Views(ViewRoute::Security));
ctx.link()
.navigator()
.expect_throw("failed to read history")
.push(&Route::CredentialReset);
// No need to redraw, or reset state, since this redirect will destroy
// the state.
false
}
Msg::RequestReauth => {
models::push_return_location(models::Location::Views(ViewRoute::Security));
ctx.link()
.navigator()
.expect_throw("failed to read history")
.push(&Route::Reauth);
// No need to redraw, or reset state, since this redirect will destroy
// the state.
false
}
Msg::Error { emsg, kopid } => {
self.state = State::Error { emsg, kopid };
true
}
}
}
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
#[cfg(debug_assertions)]
console::debug!("views::security::rendered");
}
fn view(&self, ctx: &Context<Self>) -> Html {
let uat = ctx.props().current_user_uat.clone();
let jsdate = js_sys::Date::new_0();
let isotime: String = jsdate.to_iso_string().into();
// TODO: Actually check the time of expiry on the uat and have a timer set that
// re-locks things nicely.
let time = time::OffsetDateTime::parse(&isotime, &Rfc3339)
.map(|odt| odt + time::Duration::new(60, 0))
.expect_throw("Unable to process time stamp");
let is_priv_able = uat.purpose_readwrite_active(time);
let submit_enabled = match self.state {
State::Init | State::Error { .. } => is_priv_able,
State::Waiting => false,
};
let flash = match &self.state {
State::Error { emsg, kopid } => {
let message = match kopid {
Some(k) => format!("An error occurred - {} - {}", emsg, k),
None => format!("An error occurred - {} - No Operation ID", emsg),
};
html! {
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{ message }
<button type="button" class="btn btn-close" data-dismiss="alert" aria-label="Close"></button>
</div>
}
}
_ => html! { <></> },
};
let unlock = if is_priv_able {
html! {
<div>
<button type="button" class="btn btn-primary"
disabled=true
>
{ "Security Settings Unlocked 🔓" }
</button>
</div>
}
} else {
html! {
<div>
<button type="button" class="btn btn-primary"
onclick={
ctx.link().callback(|_e| {
Msg::RequestReauth
})
}
>
{ "Unlock Security Settings 🔒" }
</button>
</div>
}
};
html! {
<>
<div class={CSS_PAGE_HEADER}>
<h2>{ "Security" }</h2>
</div>
{ flash }
{ unlock }
<hr/>
<div>
<p>
<button type="button" class="btn btn-primary"
disabled={ !submit_enabled }
onclick={
ctx.link().callback(|_e| {
Msg::RequestCredentialUpdate
})
}
>
{ "Password and Authentication Settings" }
</button>
</p>
</div>
<hr/>
if uat.ui_hints.contains(&UiHint::PosixAccount) {
<div>
<p>
<ChangeUnixPassword uat={ uat } enabled={ is_priv_able } />
</p>
</div>
}
</>
}
}
}
impl SecurityApp {
async fn request_credential_update(id: String) -> Result<Msg, FetchError> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::SameOrigin);
opts.credentials(RequestCredentials::SameOrigin);
let uri = format!("/v1/person/{}/_credential/_update", id);
let request = Request::new_with_str_and_init(uri.as_str(), &opts)?;
request
.headers()
.set("content-type", "application/json")
.expect_throw("failed to set header");
let window = utils::window();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
let status = resp.status();
if status == 200 {
let jsval = JsFuture::from(resp.json()?).await?;
let (token, status): (CUSessionToken, CUStatus) =
serde_wasm_bindgen::from_value(jsval).expect_throw("Invalid response type");
Ok(Msg::BeginCredentialUpdate { token, status })
} else {
let headers = resp.headers();
let kopid = headers.get("x-kanidm-opid").ok().flatten();
let text = JsFuture::from(resp.text()?).await?;
let emsg = text.as_string().unwrap_or_default();
// let jsval_json = JsFuture::from(resp.json()?).await?;
Ok(Msg::Error { emsg, kopid })
}
}
}

View file

@ -517,7 +517,7 @@ impl AccountCredential {
pub fn debug(&self) -> bool {
match self {
AccountCredential::Status(aopt) => aopt.copt.debug,
AccountCredential::CreateResetToken(aopt) => aopt.copt.debug,
AccountCredential::CreateResetToken { copt, .. } => copt.debug,
AccountCredential::UseResetToken(aopt) => aopt.copt.debug,
AccountCredential::Update(aopt) => aopt.copt.debug,
}
@ -580,12 +580,12 @@ impl AccountCredential {
}
}
}
AccountCredential::CreateResetToken(aopt) => {
let client = aopt.copt.to_client(OpType::Write).await;
AccountCredential::CreateResetToken { aopts, copt, ttl } => {
let client = copt.to_client(OpType::Write).await;
// What's the client url?
match client
.idm_person_account_credential_update_intent(aopt.aopts.account_id.as_str())
.idm_person_account_credential_update_intent(aopts.account_id.as_str(), *ttl)
.await
{
Ok(cuintent_token) => {
@ -602,7 +602,7 @@ impl AccountCredential {
debug!(
"Successfully created credential reset token for {}: {}",
aopt.aopts.account_id, cuintent_token.token
aopts.account_id, cuintent_token.token
);
println!(
"The person can use one of the following to allow the credential reset"

View file

@ -213,7 +213,15 @@ pub enum AccountCredential {
/// Create a reset token that can be given to another person so they can
/// recover or reset their account credentials.
#[clap(name = "create-reset-token")]
CreateResetToken(AccountNamedOpt),
CreateResetToken {
#[clap(flatten)]
aopts: AccountCommonOpt,
#[clap(flatten)]
copt: CommonOpt,
/// Optionally set how many seconds the reset token should be valid for.
#[clap(long = "ttl")]
ttl: Option<u32>,
},
}
/// RADIUS secret management