mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 20:47:01 +01:00
feat: add unix passwod reset to security web ui (#1014)
* feat: add unix passwod reset to security web ui * refactor: fetch profile info in ViewsApp prevents constant re-fetching of the profile page and allows every view to access the current_user property * refactor: move unix password change to component * docs: add @theSuess to contributors * fix: further specify kind of password updated * refactor: perform validity check before submit * chore: regenerate vendored wasm package
This commit is contained in:
parent
b793ec6e79
commit
8416069c61
|
@ -18,6 +18,7 @@
|
||||||
* Kellin (kellinm)
|
* Kellin (kellinm)
|
||||||
* Carla Schroder (cjschroder)
|
* Carla Schroder (cjschroder)
|
||||||
* Thomas Sanchez (daedric)
|
* Thomas Sanchez (daedric)
|
||||||
|
* Dominik Süß (theSuess)
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
|
|
|
@ -324,7 +324,7 @@ impl fmt::Display for AuthType {
|
||||||
///
|
///
|
||||||
/// It's likely that this must have a relationship to the server's user structure
|
/// It's likely that this must have a relationship to the server's user structure
|
||||||
/// and to the Entry so that filters or access controls can be applied.
|
/// and to the Entry so that filters or access controls can be applied.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub struct UserAuthToken {
|
pub struct UserAuthToken {
|
||||||
pub session_id: Uuid,
|
pub session_id: Uuid,
|
||||||
|
@ -1023,7 +1023,7 @@ impl WhoamiRequest {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
pub struct WhoamiResponse {
|
pub struct WhoamiResponse {
|
||||||
// Should we just embed the entry? Or destructure it?
|
// Should we just embed the entry? Or destructure it?
|
||||||
pub youare: Entry,
|
pub youare: Entry,
|
||||||
|
|
|
@ -50,9 +50,11 @@ features = [
|
||||||
"Element",
|
"Element",
|
||||||
"Event",
|
"Event",
|
||||||
"FocusEvent",
|
"FocusEvent",
|
||||||
|
"FormData",
|
||||||
"Headers",
|
"Headers",
|
||||||
"HtmlButtonElement",
|
"HtmlButtonElement",
|
||||||
"HtmlDocument",
|
"HtmlDocument",
|
||||||
|
"HtmlFormElement",
|
||||||
"Navigator",
|
"Navigator",
|
||||||
"PublicKeyCredential",
|
"PublicKeyCredential",
|
||||||
"PublicKeyCredentialCreationOptions",
|
"PublicKeyCredentialCreationOptions",
|
||||||
|
|
|
@ -224,7 +224,7 @@ function addBorrowedObject(obj) {
|
||||||
}
|
}
|
||||||
function __wbg_adapter_30(arg0, arg1, arg2) {
|
function __wbg_adapter_30(arg0, arg1, arg2) {
|
||||||
try {
|
try {
|
||||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h059700c1260af691(arg0, arg1, addBorrowedObject(arg2));
|
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hea78f67aa49cdca6(arg0, arg1, addBorrowedObject(arg2));
|
||||||
} finally {
|
} finally {
|
||||||
heap[stack_pointer++] = undefined;
|
heap[stack_pointer++] = undefined;
|
||||||
}
|
}
|
||||||
|
@ -252,11 +252,11 @@ function makeClosure(arg0, arg1, dtor, f) {
|
||||||
return real;
|
return real;
|
||||||
}
|
}
|
||||||
function __wbg_adapter_33(arg0, arg1, arg2) {
|
function __wbg_adapter_33(arg0, arg1, arg2) {
|
||||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hba0e2d91e3f361c8(arg0, arg1, addHeapObject(arg2));
|
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h5d377d592c5210bc(arg0, arg1, addHeapObject(arg2));
|
||||||
}
|
}
|
||||||
|
|
||||||
function __wbg_adapter_36(arg0, arg1, arg2) {
|
function __wbg_adapter_36(arg0, arg1, arg2) {
|
||||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd75fa9c9bfc3f75e(arg0, arg1, addHeapObject(arg2));
|
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h5f33dcf69dc6b3de(arg0, arg1, addHeapObject(arg2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -380,17 +380,13 @@ function getImports() {
|
||||||
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
|
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
|
||||||
const ret = getObject(arg0) === undefined;
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbindgen_json_parse = function(arg0, arg1) {
|
imports.wbg.__wbindgen_json_parse = function(arg0, arg1) {
|
||||||
const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
|
const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_number_new = function(arg0) {
|
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||||
const ret = arg0;
|
const ret = getObject(arg0) === undefined;
|
||||||
return addHeapObject(ret);
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
|
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
|
||||||
const obj = getObject(arg1);
|
const obj = getObject(arg1);
|
||||||
|
@ -398,6 +394,10 @@ function getImports() {
|
||||||
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
|
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
|
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbindgen_number_new = function(arg0) {
|
||||||
|
const ret = arg0;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
imports.wbg.__wbg_new_693216e109162396 = function() {
|
imports.wbg.__wbg_new_693216e109162396 = function() {
|
||||||
const ret = new Error();
|
const ret = new Error();
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
|
@ -416,17 +416,17 @@ function getImports() {
|
||||||
wasm.__wbindgen_free(arg0, arg1);
|
wasm.__wbindgen_free(arg0, arg1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_debug_5a27eb2cb0d074ba = function(arg0, arg1) {
|
imports.wbg.__wbg_debug_f058a17401150b46 = function(arg0, arg1) {
|
||||||
var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice();
|
var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice();
|
||||||
wasm.__wbindgen_free(arg0, arg1 * 4);
|
wasm.__wbindgen_free(arg0, arg1 * 4);
|
||||||
console.debug(...v0);
|
console.debug(...v0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_error_1a35d3879f286b52 = function(arg0, arg1) {
|
imports.wbg.__wbg_error_726977b4dd084e24 = function(arg0, arg1) {
|
||||||
var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice();
|
var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice();
|
||||||
wasm.__wbindgen_free(arg0, arg1 * 4);
|
wasm.__wbindgen_free(arg0, arg1 * 4);
|
||||||
console.error(...v0);
|
console.error(...v0);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_warn_2aa0e7178e1d35f6 = function(arg0, arg1) {
|
imports.wbg.__wbg_warn_921059440157e870 = function(arg0, arg1) {
|
||||||
var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice();
|
var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice();
|
||||||
wasm.__wbindgen_free(arg0, arg1 * 4);
|
wasm.__wbindgen_free(arg0, arg1 * 4);
|
||||||
console.warn(...v0);
|
console.warn(...v0);
|
||||||
|
@ -487,6 +487,30 @@ function getImports() {
|
||||||
const ret = getObject(arg0).querySelector(getStringFromWasm0(arg1, arg2));
|
const ret = getObject(arg0).querySelector(getStringFromWasm0(arg1, arg2));
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_value_eb32f706ae6bfab2 = function(arg0, arg1) {
|
||||||
|
const ret = getObject(arg1).value;
|
||||||
|
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
const len0 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_setvalue_3dd349be116107ce = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_get_aab8f8a9b87125ad = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||||
|
const ret = getObject(arg1).get(getStringFromWasm0(arg2, arg3));
|
||||||
|
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
var len0 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_set_b5c36262f65fae92 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||||
|
getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_instanceof_HtmlFormElement_cf35f564c31c7e40 = function(arg0) {
|
||||||
|
const ret = getObject(arg0) instanceof HTMLFormElement;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
imports.wbg.__wbg_getItem_1db55b1eb4116c1e = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
imports.wbg.__wbg_getItem_1db55b1eb4116c1e = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||||
const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));
|
const ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));
|
||||||
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
@ -500,66 +524,6 @@ function getImports() {
|
||||||
imports.wbg.__wbg_setItem_1f474989a35f4c9f = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
imports.wbg.__wbg_setItem_1f474989a35f4c9f = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||||
getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
imports.wbg.__wbg_get_aab8f8a9b87125ad = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
|
||||||
const ret = getObject(arg1).get(getStringFromWasm0(arg2, arg3));
|
|
||||||
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
var len0 = WASM_VECTOR_LEN;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_set_b5c36262f65fae92 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
|
||||||
getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_pathname_8ed2fc02f98aeaaf = function(arg0, arg1) {
|
|
||||||
const ret = getObject(arg1).pathname;
|
|
||||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_new_d1d1300265e34170 = function() { return handleError(function (arg0, arg1) {
|
|
||||||
const ret = new URL(getStringFromWasm0(arg0, arg1));
|
|
||||||
return addHeapObject(ret);
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_value_eb32f706ae6bfab2 = function(arg0, arg1) {
|
|
||||||
const ret = getObject(arg1).value;
|
|
||||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_setvalue_3dd349be116107ce = function(arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_headers_0aeca08d4e61e2e7 = function(arg0) {
|
|
||||||
const ret = getObject(arg0).headers;
|
|
||||||
return addHeapObject(ret);
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_newwithstrandinit_de7c409ec8538105 = function() { return handleError(function (arg0, arg1, arg2) {
|
|
||||||
const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
|
|
||||||
return addHeapObject(ret);
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_add_a1fa1336c6b306df = function() { return handleError(function (arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).add(getStringFromWasm0(arg1, arg2));
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_remove_dce5eca3c9fcea70 = function() { return handleError(function (arg0, arg1, arg2) {
|
|
||||||
getObject(arg0).remove(getStringFromWasm0(arg1, arg2));
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_create_2c518207ce8a6157 = function() { return handleError(function (arg0, arg1) {
|
|
||||||
const ret = getObject(arg0).create(getObject(arg1));
|
|
||||||
return addHeapObject(ret);
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_get_5ec74cdfbbefe775 = function() { return handleError(function (arg0, arg1) {
|
|
||||||
const ret = getObject(arg0).get(getObject(arg1));
|
|
||||||
return addHeapObject(ret);
|
|
||||||
}, arguments) };
|
|
||||||
imports.wbg.__wbg_href_cae04ee9562fc683 = function(arg0, arg1) {
|
|
||||||
const ret = getObject(arg1).href;
|
|
||||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
||||||
const len0 = WASM_VECTOR_LEN;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
|
||||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
|
||||||
};
|
|
||||||
imports.wbg.__wbg_instanceof_HtmlInputElement_3fad42774bc62388 = function(arg0) {
|
imports.wbg.__wbg_instanceof_HtmlInputElement_3fad42774bc62388 = function(arg0) {
|
||||||
const ret = getObject(arg0) instanceof HTMLInputElement;
|
const ret = getObject(arg0) instanceof HTMLInputElement;
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -577,6 +541,46 @@ function getImports() {
|
||||||
imports.wbg.__wbg_setvalue_7b7950dacc5eb607 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_setvalue_7b7950dacc5eb607 = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
|
getObject(arg0).value = getStringFromWasm0(arg1, arg2);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_add_a1fa1336c6b306df = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).add(getStringFromWasm0(arg1, arg2));
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_remove_dce5eca3c9fcea70 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).remove(getStringFromWasm0(arg1, arg2));
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_pathname_8ed2fc02f98aeaaf = function(arg0, arg1) {
|
||||||
|
const ret = getObject(arg1).pathname;
|
||||||
|
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
const len0 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_d1d1300265e34170 = function() { return handleError(function (arg0, arg1) {
|
||||||
|
const ret = new URL(getStringFromWasm0(arg0, arg1));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_create_2c518207ce8a6157 = function() { return handleError(function (arg0, arg1) {
|
||||||
|
const ret = getObject(arg0).create(getObject(arg1));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_get_5ec74cdfbbefe775 = function() { return handleError(function (arg0, arg1) {
|
||||||
|
const ret = getObject(arg0).get(getObject(arg1));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_href_cae04ee9562fc683 = function(arg0, arg1) {
|
||||||
|
const ret = getObject(arg1).href;
|
||||||
|
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
const len0 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_headers_0aeca08d4e61e2e7 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).headers;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_newwithstrandinit_de7c409ec8538105 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
imports.wbg.__wbg_instanceof_Element_1714e50f9bda1d15 = function(arg0) {
|
imports.wbg.__wbg_instanceof_Element_1714e50f9bda1d15 = function(arg0) {
|
||||||
const ret = getObject(arg0) instanceof Element;
|
const ret = getObject(arg0) instanceof Element;
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -608,6 +612,14 @@ function getImports() {
|
||||||
imports.wbg.__wbg_focus_66bb7c767837cd51 = function() { return handleError(function (arg0) {
|
imports.wbg.__wbg_focus_66bb7c767837cd51 = function() { return handleError(function (arg0) {
|
||||||
getObject(arg0).focus();
|
getObject(arg0).focus();
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_credentials_c3a25c2c25bfa304 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).credentials;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_getClientExtensionResults_93d06fddc73f65f9 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).getClientExtensionResults();
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
imports.wbg.__wbg_instanceof_Event_8c74064684d03e14 = function(arg0) {
|
imports.wbg.__wbg_instanceof_Event_8c74064684d03e14 = function(arg0) {
|
||||||
const ret = getObject(arg0) instanceof Event;
|
const ret = getObject(arg0) instanceof Event;
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -623,12 +635,12 @@ function getImports() {
|
||||||
imports.wbg.__wbg_preventDefault_b4d36c8196fbe491 = function(arg0) {
|
imports.wbg.__wbg_preventDefault_b4d36c8196fbe491 = function(arg0) {
|
||||||
getObject(arg0).preventDefault();
|
getObject(arg0).preventDefault();
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_credentials_c3a25c2c25bfa304 = function(arg0) {
|
imports.wbg.__wbg_newwithform_e0bf0bf59a04cc42 = function() { return handleError(function (arg0) {
|
||||||
const ret = getObject(arg0).credentials;
|
const ret = new FormData(getObject(arg0));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
}, arguments) };
|
||||||
imports.wbg.__wbg_getClientExtensionResults_93d06fddc73f65f9 = function(arg0) {
|
imports.wbg.__wbg_get_7d2187aabf8b90b6 = function(arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg0).getClientExtensionResults();
|
const ret = getObject(arg0).get(getStringFromWasm0(arg1, arg2));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbg_addEventListener_ec92ea1297eefdfc = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
imports.wbg.__wbg_addEventListener_ec92ea1297eefdfc = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||||
|
@ -813,16 +825,16 @@ function getImports() {
|
||||||
const ret = wasm.memory;
|
const ret = wasm.memory;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper4903 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper4401 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 1268, __wbg_adapter_30);
|
const ret = makeMutClosure(arg0, arg1, 1059, __wbg_adapter_30);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper5008 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper4577 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeClosure(arg0, arg1, 1301, __wbg_adapter_33);
|
const ret = makeClosure(arg0, arg1, 1084, __wbg_adapter_33);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper5273 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper4718 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 1350, __wbg_adapter_36);
|
const ret = makeMutClosure(arg0, arg1, 1120, __wbg_adapter_36);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Binary file not shown.
302
kanidmd_web_ui/src/components/change_unix_password.rs
Normal file
302
kanidmd_web_ui/src/components/change_unix_password.rs
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
use compact_jwt::{Jws, JwsUnverified};
|
||||||
|
use kanidm_proto::v1::{SingleStringRequest, UserAuthToken};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
|
||||||
|
use wasm_bindgen_futures::JsFuture;
|
||||||
|
use web_sys::{FormData, HtmlFormElement};
|
||||||
|
|
||||||
|
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::error::*;
|
||||||
|
use crate::utils;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum PwCheck {
|
||||||
|
Init,
|
||||||
|
Valid,
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChangeUnixPassword {
|
||||||
|
state: State,
|
||||||
|
pw_check: PwCheck,
|
||||||
|
pw_val: String,
|
||||||
|
pw_check_val: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct FormValues {
|
||||||
|
password_input: String,
|
||||||
|
password_repeat_input: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FormData> for FormValues {
|
||||||
|
fn from(data: FormData) -> Self {
|
||||||
|
Self {
|
||||||
|
password_input: data.get("password_input").as_string().unwrap(),
|
||||||
|
password_repeat_input: data.get("password_repeat_input").as_string().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
Submit(FormData),
|
||||||
|
Error { emsg: String, kopid: Option<String> },
|
||||||
|
Success,
|
||||||
|
PasswordCheck,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FetchError> for Msg {
|
||||||
|
fn from(fe: FetchError) -> Self {
|
||||||
|
Msg::Error {
|
||||||
|
emsg: fe.as_string(),
|
||||||
|
kopid: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum State {
|
||||||
|
Init,
|
||||||
|
Error { emsg: String, kopid: Option<String> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Properties)]
|
||||||
|
pub struct ChangeUnixPasswordProps {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ChangeUnixPassword {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ChangeUnixPasswordProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
state: State::Init,
|
||||||
|
pw_check: PwCheck::Init,
|
||||||
|
pw_val: "".to_string(),
|
||||||
|
pw_check_val: "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::Submit(data) => {
|
||||||
|
let fd: FormValues = data.into();
|
||||||
|
if fd.password_input != fd.password_repeat_input {
|
||||||
|
return self.update(
|
||||||
|
ctx,
|
||||||
|
Msg::Error {
|
||||||
|
emsg: "Password fields did not match".to_string(),
|
||||||
|
kopid: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let tk = ctx.props().token.clone();
|
||||||
|
ctx.link().send_future(async {
|
||||||
|
match Self::update_unix_password(tk, fd.password_input).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(v) => v.into(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Msg::Error { emsg, kopid } => {
|
||||||
|
self.reset();
|
||||||
|
self.state = State::Error { emsg, kopid };
|
||||||
|
self.pw_check = PwCheck::Init;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Msg::Success => {
|
||||||
|
self.reset();
|
||||||
|
utils::modal_hide_by_id(crate::constants::ID_UNIX_PASSWORDCHANGE);
|
||||||
|
self.state = State::Init;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Msg::PasswordCheck => {
|
||||||
|
let pw = utils::get_value_from_element_id("password_input")
|
||||||
|
.unwrap_or_else(|| "".to_string());
|
||||||
|
let check = utils::get_value_from_element_id("password_repeat_input")
|
||||||
|
.unwrap_or_else(|| "".to_string());
|
||||||
|
|
||||||
|
if pw == check {
|
||||||
|
self.pw_check = PwCheck::Valid
|
||||||
|
} else {
|
||||||
|
self.pw_check = PwCheck::Invalid
|
||||||
|
}
|
||||||
|
self.pw_val = pw;
|
||||||
|
self.pw_check_val = check;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
let flash = match &self.state {
|
||||||
|
State::Error { emsg, kopid } => {
|
||||||
|
let message = match kopid {
|
||||||
|
Some(k) => format!("An error occured - {} - {}", emsg, k),
|
||||||
|
None => format!("An error occured - {} - 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 submit_enabled = self.pw_check == PwCheck::Valid;
|
||||||
|
|
||||||
|
let pw_val = self.pw_val.clone();
|
||||||
|
let pw_check_val = self.pw_check_val.clone();
|
||||||
|
let pw_check_class = match &self.pw_check {
|
||||||
|
PwCheck::Init | PwCheck::Valid => classes!("form-control"),
|
||||||
|
PwCheck::Invalid => classes!("form-control", "is-invalid"),
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<button type="button" class="btn btn-primary"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target={format!("#{}", crate::constants::ID_UNIX_PASSWORDCHANGE)}
|
||||||
|
>
|
||||||
|
{ "Update your Unix Password" }
|
||||||
|
</button>
|
||||||
|
<div class="modal" tabindex="-1" role="dialog" id={crate::constants::ID_UNIX_PASSWORDCHANGE}>
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<form
|
||||||
|
onsubmit={
|
||||||
|
ctx.link().callback(|e: FocusEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
let form = e.target().and_then(|t| t.dyn_into::<HtmlFormElement>().ok()).unwrap();
|
||||||
|
Msg::Submit(FormData::new_with_form(&form).unwrap())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{"Update your unix password"}</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<p> { "This password is used when logging into a unix-like system as well as applications utilizing LDAP" } </p>
|
||||||
|
{ flash }
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_input"> {"New Password" }</label>
|
||||||
|
<input
|
||||||
|
autofocus=true
|
||||||
|
class="autofocus form-control"
|
||||||
|
name="password_input"
|
||||||
|
id="password_input"
|
||||||
|
type="password"
|
||||||
|
value={ pw_val }
|
||||||
|
oninput={
|
||||||
|
ctx.link()
|
||||||
|
.callback(move |_| {
|
||||||
|
Msg::PasswordCheck
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_repeat_input"> {"Repeat Password" }</label>
|
||||||
|
<input
|
||||||
|
class={ pw_check_class }
|
||||||
|
name="password_repeat_input"
|
||||||
|
id="password_repeat_input"
|
||||||
|
type="password"
|
||||||
|
value={ pw_check_val }
|
||||||
|
oninput={
|
||||||
|
ctx.link()
|
||||||
|
.callback(move |_| {
|
||||||
|
Msg::PasswordCheck
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{ "Passwords do not match." }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-success" disabled={ !submit_enabled }>{ "Update Password" }</button>
|
||||||
|
<button type="button" class="btn btn-secondary"
|
||||||
|
onclick={
|
||||||
|
ctx.link().callback(|_e| {
|
||||||
|
Msg::Success
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>{"Cancel"}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {}
|
||||||
|
|
||||||
|
fn destroy(&mut self, _ctx: &Context<Self>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChangeUnixPassword {
|
||||||
|
async fn update_unix_password(token: String, new_password: String) -> Result<Msg, FetchError> {
|
||||||
|
let jwtu = JwsUnverified::from_str(&token).expect_throw("Invalid UAT, unable to parse");
|
||||||
|
|
||||||
|
let uat: Jws<UserAuthToken> = jwtu
|
||||||
|
.unsafe_release_without_verification()
|
||||||
|
.expect_throw("Unvalid UAT, unable to release ");
|
||||||
|
|
||||||
|
let id = uat.inner.uuid.to_string();
|
||||||
|
let changereq_jsvalue = serde_json::to_string(&SingleStringRequest {
|
||||||
|
value: new_password,
|
||||||
|
})
|
||||||
|
.map(|s| JsValue::from(&s))
|
||||||
|
.expect_throw("Failed to change request");
|
||||||
|
let mut opts = RequestInit::new();
|
||||||
|
opts.method("PUT");
|
||||||
|
opts.mode(RequestMode::SameOrigin);
|
||||||
|
opts.body(Some(&changereq_jsvalue));
|
||||||
|
|
||||||
|
let uri = format!("/v1/person/{}/_unix/_credential", 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");
|
||||||
|
request
|
||||||
|
.headers()
|
||||||
|
.set("authorization", format!("Bearer {}", token).as_str())
|
||||||
|
.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 {
|
||||||
|
Ok(Msg::Success)
|
||||||
|
} 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_else(|| "".to_string());
|
||||||
|
Ok(Msg::Error { emsg, kopid })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.pw_val = "".to_string();
|
||||||
|
self.pw_check_val = "".to_string();
|
||||||
|
self.pw_check = PwCheck::Init;
|
||||||
|
}
|
||||||
|
}
|
1
kanidmd_web_ui/src/components/mod.rs
Normal file
1
kanidmd_web_ui/src/components/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod change_unix_password;
|
|
@ -6,6 +6,8 @@ pub const CSS_CLASSES_BODY_FORM: &[&str] = &["flex-column", "d-flex", "h-100"];
|
||||||
// the HTML element ID that the signout modal dialogue box has
|
// the HTML element ID that the signout modal dialogue box has
|
||||||
pub const ID_SIGNOUTMODAL: &str = "signoutModal";
|
pub const ID_SIGNOUTMODAL: &str = "signoutModal";
|
||||||
|
|
||||||
|
// the HTML element ID that the unix password dialog box has
|
||||||
|
pub const ID_UNIX_PASSWORDCHANGE: &str = "unixPasswordModal";
|
||||||
// classes for buttons
|
// classes for buttons
|
||||||
pub const CLASS_BUTTON_DARK: &str = "btn btn-dark";
|
pub const CLASS_BUTTON_DARK: &str = "btn btn-dark";
|
||||||
pub const CLASS_BUTTON_SUCCESS: &str = "btn btn-success";
|
pub const CLASS_BUTTON_SUCCESS: &str = "btn btn-success";
|
||||||
|
|
|
@ -301,6 +301,11 @@ impl Component for PwModalApp {
|
||||||
type="password"
|
type="password"
|
||||||
value={ pw_check_val }
|
value={ pw_check_val }
|
||||||
/>
|
/>
|
||||||
|
if !submit_enabled {
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
{ "Passwords do not match." }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|
|
@ -29,6 +29,8 @@ mod oauth2;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod views;
|
mod views;
|
||||||
|
|
||||||
|
mod components;
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
|
||||||
pub fn run_app() -> Result<(), JsValue> {
|
pub fn run_app() -> Result<(), JsValue> {
|
||||||
yew::start_app::<manager::ManagerApp>();
|
yew::start_app::<manager::ManagerApp>();
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
#[cfg(debug)]
|
|
||||||
use gloo::console;
|
use gloo::console;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
use crate::manager::Route;
|
use crate::manager::Route;
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
use kanidm_proto::v1::WhoamiResponse;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||||
|
@ -49,15 +50,18 @@ enum State {
|
||||||
#[derive(PartialEq, Eq, Properties)]
|
#[derive(PartialEq, Eq, Properties)]
|
||||||
pub struct ViewProps {
|
pub struct ViewProps {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
|
pub current_user: Option<WhoamiResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ViewsApp {
|
pub struct ViewsApp {
|
||||||
state: State,
|
state: State,
|
||||||
|
current_user: Option<WhoamiResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ViewsMsg {
|
pub enum ViewsMsg {
|
||||||
Verified(String),
|
Verified(String),
|
||||||
Logout,
|
Logout,
|
||||||
|
ProfileInfoRecieved(WhoamiResponse),
|
||||||
Error { emsg: String, kopid: Option<String> },
|
Error { emsg: String, kopid: Option<String> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,24 +74,6 @@ impl From<FetchError> for ViewsMsg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn switch(route: &ViewRoute) -> Html {
|
|
||||||
#[cfg(debug)]
|
|
||||||
console::debug!("views::switch");
|
|
||||||
|
|
||||||
// safety - can't panic because to get to this location we MUST be authenticated!
|
|
||||||
let token =
|
|
||||||
models::get_bearer_token().expect_throw("Invalid state, bearer token must be present!");
|
|
||||||
|
|
||||||
match route {
|
|
||||||
ViewRoute::Apps => html! { <AppsApp /> },
|
|
||||||
ViewRoute::Profile => html! { <ProfileApp token={ token } /> },
|
|
||||||
ViewRoute::Security => html! { <SecurityApp token={ token } /> },
|
|
||||||
ViewRoute::NotFound => html! {
|
|
||||||
<Redirect<Route> to={Route::NotFound}/>
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for ViewsApp {
|
impl Component for ViewsApp {
|
||||||
type Message = ViewsMsg;
|
type Message = ViewsMsg;
|
||||||
type Properties = ();
|
type Properties = ();
|
||||||
|
@ -114,7 +100,10 @@ impl Component for ViewsApp {
|
||||||
None => State::LoginRequired,
|
None => State::LoginRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
ViewsApp { state }
|
ViewsApp {
|
||||||
|
state,
|
||||||
|
current_user: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
||||||
|
@ -123,12 +112,20 @@ impl Component for ViewsApp {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
#[cfg(debug)]
|
#[cfg(debug)]
|
||||||
console::debug!("views::update");
|
console::debug!("views::update");
|
||||||
match msg {
|
match msg {
|
||||||
ViewsMsg::Verified(token) => {
|
ViewsMsg::Verified(token) => {
|
||||||
|
let tk = token.clone();
|
||||||
self.state = State::Authenticated(token);
|
self.state = State::Authenticated(token);
|
||||||
|
// Populate the user profile
|
||||||
|
ctx.link().send_future(async {
|
||||||
|
match Self::fetch_user_data(tk).await {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(v) => v.into(),
|
||||||
|
}
|
||||||
|
});
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
ViewsMsg::Logout => {
|
ViewsMsg::Logout => {
|
||||||
|
@ -136,6 +133,10 @@ impl Component for ViewsApp {
|
||||||
self.state = State::LoginRequired;
|
self.state = State::LoginRequired;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
ViewsMsg::ProfileInfoRecieved(profile) => {
|
||||||
|
self.current_user = Some(profile);
|
||||||
|
true
|
||||||
|
}
|
||||||
ViewsMsg::Error { emsg, kopid } => {
|
ViewsMsg::Error { emsg, kopid } => {
|
||||||
self.state = State::Error { emsg, kopid };
|
self.state = State::Error { emsg, kopid };
|
||||||
true
|
true
|
||||||
|
@ -207,6 +208,8 @@ impl Component for ViewsApp {
|
||||||
impl ViewsApp {
|
impl ViewsApp {
|
||||||
/// The base page for the user dashboard
|
/// The base page for the user dashboard
|
||||||
fn view_authenticated(&self, ctx: &Context<Self>) -> Html {
|
fn view_authenticated(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
let current_user = self.current_user.clone();
|
||||||
|
|
||||||
// WARN set dash-body against body here?
|
// WARN set dash-body against body here?
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
|
@ -278,12 +281,24 @@ impl ViewsApp {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<main class="p-3 x-auto">
|
<main class="p-3 x-auto">
|
||||||
<Switch<ViewRoute> render={ Switch::render(switch) } />
|
<Switch<ViewRoute> render={ Switch::render(move |route: &ViewRoute| {
|
||||||
|
// safety - can't panic because to get to this location we MUST be authenticated!
|
||||||
|
let token =
|
||||||
|
models::get_bearer_token().expect_throw("Invalid state, bearer token must be present!");
|
||||||
|
|
||||||
|
match route {
|
||||||
|
ViewRoute::Apps => html! { <AppsApp /> },
|
||||||
|
ViewRoute::Profile => html! { <ProfileApp token={ token } current_user={ current_user.clone() } /> },
|
||||||
|
ViewRoute::Security => html! { <SecurityApp token={ token } current_user={ current_user.clone() } /> },
|
||||||
|
ViewRoute::NotFound => html! {
|
||||||
|
<Redirect<Route> to={Route::NotFound}/>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})} />
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_token_valid(token: String) -> Result<ViewsMsg, FetchError> {
|
async fn check_token_valid(token: String) -> Result<ViewsMsg, FetchError> {
|
||||||
let mut opts = RequestInit::new();
|
let mut opts = RequestInit::new();
|
||||||
opts.method("GET");
|
opts.method("GET");
|
||||||
|
@ -318,4 +333,42 @@ impl ViewsApp {
|
||||||
Ok(ViewsMsg::Error { emsg, kopid })
|
Ok(ViewsMsg::Error { emsg, kopid })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async fn fetch_user_data(token: String) -> Result<ViewsMsg, FetchError> {
|
||||||
|
let mut opts = RequestInit::new();
|
||||||
|
opts.method("GET");
|
||||||
|
opts.mode(RequestMode::SameOrigin);
|
||||||
|
|
||||||
|
let request = Request::new_with_str_and_init("/v1/self", &opts)?;
|
||||||
|
request
|
||||||
|
.headers()
|
||||||
|
.set("content-type", "application/json")
|
||||||
|
.expect_throw("failed to set header");
|
||||||
|
request
|
||||||
|
.headers()
|
||||||
|
.set("authorization", format!("Bearer {}", token).as_str())
|
||||||
|
.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();
|
||||||
|
let headers = resp.headers();
|
||||||
|
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||||
|
|
||||||
|
if status == 200 {
|
||||||
|
let jsval = JsFuture::from(resp.json()?).await?;
|
||||||
|
let whoamiresponse: WhoamiResponse = jsval
|
||||||
|
.into_serde()
|
||||||
|
.map_err(|e| {
|
||||||
|
let e_msg = format!("serde error getting user data -> {:?}", e);
|
||||||
|
console::error!(e_msg.as_str());
|
||||||
|
})
|
||||||
|
.expect_throw("Invalid response type");
|
||||||
|
Ok(ViewsMsg::ProfileInfoRecieved(whoamiresponse))
|
||||||
|
} else {
|
||||||
|
let text = JsFuture::from(resp.text()?).await?;
|
||||||
|
let emsg = text.as_string().unwrap_or_else(|| "".to_string());
|
||||||
|
Ok(ViewsMsg::Error { emsg, kopid })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,130 +1,35 @@
|
||||||
use crate::error::FetchError;
|
|
||||||
// use crate::error::*;
|
|
||||||
// use crate::models;
|
|
||||||
use crate::utils;
|
|
||||||
use crate::views::ViewProps;
|
use crate::views::ViewProps;
|
||||||
|
|
||||||
// use compact_jwt::{Jws, JwsUnverified};
|
|
||||||
use gloo::console;
|
use gloo::console;
|
||||||
use kanidm_proto::v1::WhoamiResponse;
|
|
||||||
// use kanidm_proto::v1::{UserAuthToken,WhoamiResponse};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
// use std::str::FromStr;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
// use wasm_bindgen::JsValue;
|
|
||||||
use wasm_bindgen::UnwrapThrowExt;
|
|
||||||
use wasm_bindgen_futures::JsFuture;
|
|
||||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
// use web_sys::{Request, RequestInit, RequestMode, Response};
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
TokenValid(String),
|
|
||||||
TokenInvalid,
|
|
||||||
Error { emsg: String, kopid: Option<String> },
|
|
||||||
ProfileInfoRecieved(WhoamiResponse),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ProfileAppState {
|
|
||||||
Loading,
|
|
||||||
Loaded,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FetchError> for Msg {
|
|
||||||
fn from(fe: FetchError) -> Self {
|
|
||||||
Msg::Error {
|
|
||||||
emsg: fe.as_string(),
|
|
||||||
kopid: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User Profile UI
|
// User Profile UI
|
||||||
pub struct ProfileApp {
|
pub struct ProfileApp {}
|
||||||
state: ProfileAppState,
|
|
||||||
token: Option<String>,
|
|
||||||
user: Option<WhoamiResponse>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for ProfileApp {
|
impl Component for ProfileApp {
|
||||||
type Message = Msg;
|
type Message = ();
|
||||||
type Properties = ViewProps;
|
type Properties = ViewProps;
|
||||||
|
|
||||||
fn create(ctx: &Context<Self>) -> Self {
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
#[cfg(debug)]
|
#[cfg(debug)]
|
||||||
console::debug!("views::profile::create");
|
console::debug!("views::profile::create");
|
||||||
|
|
||||||
// Submit a req to init the session.
|
ProfileApp {}
|
||||||
// The uuid we want to submit against - hint, it's us.
|
|
||||||
let token = ctx.props().token.clone();
|
|
||||||
#[cfg(debug)]
|
|
||||||
console::debug!("token: ", &token);
|
|
||||||
|
|
||||||
ctx.link().send_future(async {
|
|
||||||
match Self::fetch_token_valid(token).await {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(v) => v.into(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ProfileApp {
|
|
||||||
state: ProfileAppState::Loading,
|
|
||||||
token: None,
|
|
||||||
user: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
fn changed(&mut self, ctx: &Context<Self>) -> bool {
|
||||||
#[cfg(debug)]
|
console::debug!(format!(
|
||||||
console::debug!("views::profile::changed");
|
"views::profile::changed current_user: {:?}",
|
||||||
false
|
ctx.props().current_user,
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
#[cfg(debug)]
|
|
||||||
console::debug!("views::profile::update");
|
|
||||||
match msg {
|
|
||||||
Msg::Error { emsg, kopid } => {
|
|
||||||
console::error!(format!(
|
|
||||||
"Failed to do something {:?} - kopid {:?}",
|
|
||||||
emsg, kopid
|
|
||||||
));
|
));
|
||||||
|
true
|
||||||
}
|
}
|
||||||
Msg::TokenInvalid => {
|
|
||||||
// TODO redirect off to login
|
|
||||||
let location = utils::window().location();
|
|
||||||
|
|
||||||
match location.replace("/") {
|
fn update(&mut self, ctx: &Context<Self>, _msg: Self::Message) -> bool {
|
||||||
// No need to redraw, we are leaving.
|
console::debug!(format!(
|
||||||
Ok(_) => return false,
|
"views::profile::update current_user: {:?}",
|
||||||
Err(e) => {
|
ctx.props().current_user,
|
||||||
// Something went bang, opps.
|
));
|
||||||
console::error!(format!("{:?}", e).as_str());
|
|
||||||
// self.state = State::ErrInvalidRequest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Msg::TokenValid(token) => {
|
|
||||||
// nothin' much
|
|
||||||
self.token = Some(token.clone());
|
|
||||||
#[cfg(debug)]
|
|
||||||
console::debug!(format!("Token is valid! ({})", token));
|
|
||||||
|
|
||||||
ctx.link().send_future(async {
|
|
||||||
match Self::fetch_user_data(token).await {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(v) => v.into(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Msg::ProfileInfoRecieved(data) => {
|
|
||||||
#[cfg(debug)]
|
|
||||||
console::debug!(format!("ProfileInfoRecieved({:?})", data));
|
|
||||||
self.state = ProfileAppState::Loaded;
|
|
||||||
self.user = Some(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,25 +39,17 @@ impl Component for ProfileApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// UI view for the user profile
|
/// UI view for the user profile
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
#[cfg(debug)]
|
let pagecontent = match &ctx.props().current_user {
|
||||||
console::debug!(format!(
|
None => {
|
||||||
"views::profile::starting view state: {:?}",
|
|
||||||
&self.state
|
|
||||||
));
|
|
||||||
|
|
||||||
let pagecontent = match self.state {
|
|
||||||
ProfileAppState::Loading => {
|
|
||||||
html! {
|
html! {
|
||||||
<h2>
|
<h2>
|
||||||
{"Loading user info..."}
|
{"Loading user info..."}
|
||||||
</h2>
|
</h2>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProfileAppState::Loaded => {
|
Some(userinfo) => {
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
let userinfo = self.user.as_ref().unwrap();
|
|
||||||
|
|
||||||
let mail_primary = match userinfo.uat.mail_primary.as_ref() {
|
let mail_primary = match userinfo.uat.mail_primary.as_ref() {
|
||||||
Some(email_address) => {
|
Some(email_address) => {
|
||||||
html! {
|
html! {
|
||||||
|
@ -238,77 +135,3 @@ impl Component for ProfileApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProfileApp {
|
|
||||||
async fn fetch_token_valid(token: String) -> Result<Msg, FetchError> {
|
|
||||||
let mut opts = RequestInit::new();
|
|
||||||
opts.method("GET");
|
|
||||||
opts.mode(RequestMode::SameOrigin);
|
|
||||||
let request = Request::new_with_str_and_init("/v1/auth/valid", &opts)?;
|
|
||||||
|
|
||||||
request
|
|
||||||
.headers()
|
|
||||||
.set("content-type", "application/json")
|
|
||||||
.expect_throw("failed to set header");
|
|
||||||
request
|
|
||||||
.headers()
|
|
||||||
.set("authorization", format!("Bearer {}", token).as_str())
|
|
||||||
.expect_throw("failed to set header");
|
|
||||||
|
|
||||||
let window = crate::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 {
|
|
||||||
Ok(Msg::TokenValid(token))
|
|
||||||
} else if status == 401 {
|
|
||||||
Ok(Msg::TokenInvalid)
|
|
||||||
} 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_else(|| "".to_string());
|
|
||||||
Ok(Msg::Error { emsg, kopid })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_user_data(token: String) -> Result<Msg, FetchError> {
|
|
||||||
let mut opts = RequestInit::new();
|
|
||||||
opts.method("GET");
|
|
||||||
opts.mode(RequestMode::SameOrigin);
|
|
||||||
|
|
||||||
let request = Request::new_with_str_and_init("/v1/self", &opts)?;
|
|
||||||
request
|
|
||||||
.headers()
|
|
||||||
.set("content-type", "application/json")
|
|
||||||
.expect_throw("failed to set header");
|
|
||||||
request
|
|
||||||
.headers()
|
|
||||||
.set("authorization", format!("Bearer {}", token).as_str())
|
|
||||||
.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();
|
|
||||||
let headers = resp.headers();
|
|
||||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
|
||||||
|
|
||||||
if status == 200 {
|
|
||||||
let jsval = JsFuture::from(resp.json()?).await?;
|
|
||||||
let whoamiresponse: WhoamiResponse = jsval
|
|
||||||
.into_serde()
|
|
||||||
.map_err(|e| {
|
|
||||||
let e_msg = format!("serde error getting user data -> {:?}", e);
|
|
||||||
console::error!(e_msg.as_str());
|
|
||||||
})
|
|
||||||
.expect_throw("Invalid response type");
|
|
||||||
Ok(Msg::ProfileInfoRecieved(whoamiresponse))
|
|
||||||
} else {
|
|
||||||
let text = JsFuture::from(resp.text()?).await?;
|
|
||||||
let emsg = text.as_string().unwrap_or_else(|| "".to_string());
|
|
||||||
Ok(Msg::Error { emsg, kopid })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::error::*;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
|
||||||
|
use crate::components::change_unix_password::ChangeUnixPassword;
|
||||||
use crate::manager::Route;
|
use crate::manager::Route;
|
||||||
use crate::views::{ViewProps, ViewRoute};
|
use crate::views::{ViewProps, ViewRoute};
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ impl Component for SecurityApp {
|
||||||
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
||||||
#[cfg(debug)]
|
#[cfg(debug)]
|
||||||
console::debug!("views::security::changed");
|
console::debug!("views::security::changed");
|
||||||
false
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
@ -127,7 +128,7 @@ impl Component for SecurityApp {
|
||||||
State::Waiting => false,
|
State::Waiting => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let error = match &self.state {
|
let flash = match &self.state {
|
||||||
State::Error { emsg, kopid } => {
|
State::Error { emsg, kopid } => {
|
||||||
let message = match kopid {
|
let message = match kopid {
|
||||||
Some(k) => format!("An error occured - {} - {}", emsg, k),
|
Some(k) => format!("An error occured - {} - {}", emsg, k),
|
||||||
|
@ -136,19 +137,21 @@ impl Component for SecurityApp {
|
||||||
html! {
|
html! {
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
{ message }
|
{ message }
|
||||||
<button type="button" class="btn btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn btn-close" data-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => html! { <></> },
|
_ => html! { <></> },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let current_user = ctx.props().current_user.clone();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||||
<h2>{ "Security" }</h2>
|
<h2>{ "Security" }</h2>
|
||||||
</div>
|
</div>
|
||||||
{ error }
|
{ flash }
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<button type="button" class="btn btn-primary"
|
<button type="button" class="btn btn-primary"
|
||||||
|
@ -164,6 +167,16 @@ impl Component for SecurityApp {
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<hr/>
|
||||||
|
if let Some(user) = current_user {
|
||||||
|
if user.youare.attrs.get("class").map(|x| x.contains(&String::from("posixaccount"))).unwrap_or(true) {
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<ChangeUnixPassword token={ctx.props().token.clone()}></ChangeUnixPassword>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +188,7 @@ impl SecurityApp {
|
||||||
opts.method("GET");
|
opts.method("GET");
|
||||||
opts.mode(RequestMode::SameOrigin);
|
opts.mode(RequestMode::SameOrigin);
|
||||||
|
|
||||||
let uri = format!("/v1/account/{}/_credential/_update", id);
|
let uri = format!("/v1/person/{}/_credential/_update", id);
|
||||||
|
|
||||||
let request = Request::new_with_str_and_init(uri.as_str(), &opts)?;
|
let request = Request::new_with_str_and_init(uri.as_str(), &opts)?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue