A pile of Wasm UI tweaks (#958)

This commit is contained in:
James Hodgkinson 2022-08-01 15:52:01 +10:00 committed by GitHub
parent c0cca979d9
commit 845cabb206
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 5576 additions and 495 deletions

4853
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -44,3 +44,14 @@ webauthn-authenticator-rs = { git = "https://github.com/kanidm/webauthn-rs.git",
# compact_jwt = { path = "../compact_jwt" } # compact_jwt = { path = "../compact_jwt" }
# compact_jwt = { git = "https://github.com/kanidm/compact-jwt.git" } # compact_jwt = { git = "https://github.com/kanidm/compact-jwt.git" }
# enshrinken the WASMs
[profile.release.package.kanidmd_web_ui]
# optimization over all codebase ( better optimization, slower build )
codegen-units = 1
# optimization for size ( more aggressive )
opt-level = 'z'
# optimization for size
# opt-level = 's'
# link time optimization using using whole-program analysis
# lto = true

View file

@ -0,0 +1,12 @@
FROM ubuntu/apache2:latest
RUN apt-get update
RUN apt-get install -y \
libapache2-mod-auth-openidc \
apache2-utils
RUN a2enmod auth_openidc
RUN a2enmod ssl
RUN rm /etc/apache2/sites-enabled/000-default.conf
COPY index.html /var/www/html/index.html
COPY oauth2.conf /etc/apache2/sites-enabled/oauth2.conf

View file

@ -0,0 +1,32 @@
# set the following environment variables
# OAUTH_HOSTNAME - the hostname you'll be exposing this as
# OAUTH_PORT - the external port this'll be running on (sets it in --publish)
# this is 8553, but ... things get weird if the stack doesn't end up being accessed
# through 443, eg via a tunneled proxy
# KANIDM_HOSTNAME - the hostname of the Kanidm instance
# KANIDM_PORT - if you're running it on a different port
# KANIDM_CLIENT_SECRET - the client secret for the RP in Kanidm's OAuth config
KANIDM_PORT ?= 443
OAUTH_PORT ?= 8553
.DEFAULT: build_and_run
.PHONY: build_and_run
build_and_run: build run
.PHONY: build
build:
docker build -t kanidm_oauth_test:latest .
.PHONY: run
run:
docker rm -f kanidm_oauth_test
docker run --rm -it \
--env OAUTH_HOSTNAME=$(OAUTH_HOSTNAME) \
--env KANIDM_HOSTNAME=$(KANIDM_HOSTNAME) \
--env KANIDM_PORT=$(KANIDM_PORT) \
--env KANIDM_CLIENT_SECRET=$(KANIDM_CLIENT_SECRET) \
--volume /tmp/kanidm/:/certs/ \
--publish "$(OAUTH_PORT):443" \
--name kanidm_oauth_test \
kanidm_oauth_test:latest

View file

@ -0,0 +1,16 @@
# Apache OAuth config example
This example is here mainly for devs to come up with super complicated ways to test the changes they're
making which affect OAuth things.
## Example of how to run it
```shell
OAUTH_HOSTNAME=test-oauth2.example.com \
KANIDM_HOSTNAME=test-kanidm.example.com \
KANIDM_CLIENT_SECRET=1234Hq5d1J5GG9VNae3bRMFGDVFR3bUyyXg3RPRSefJLNhee \
KANIDM_PORT=443 \
make
```
This'll build and run the docker container.

View file

@ -0,0 +1,15 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Kanidm OAuth Test Page</title>
</style>
</head>
<body>
<h1>Hi there!</h1>
<p>
Looks like you've successfully authenticated to the page, welcome!
</p>
</body>
</html>

View file

@ -0,0 +1,46 @@
# this is a template file to set up an oauth2 RP for testing
# LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so
LogLevel debug
ErrorLog /dev/stderr
TransferLog /dev/stdout
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName localhost
SSLEngine on
# use the results from insecure_generate_certs.sh - super handy
SSLCertificateFile /certs/cert.pem
SSLCertificateKeyFile /certs/key.pem
# since we're using a self-signed cert we have to disable this
OIDCSSLValidateServer Off
OIDCProviderMetadataURL https://${KANIDM_HOSTNAME}:${KANIDM_PORT}/oauth2/openid/test_rp/.well-known/openid-configuration
OIDCClientID test_rp
OIDCClientSecret ${KANIDM_CLIENT_SECRET}
OIDCUserInfoTokenMethod authz_header
OIDCPKCEMethod S256
OIDCCookieSameSite On
# Define the OpenID Connect scope that is requested from the OP (eg. "openid email profile").
# When not defined, the bare minimal scope "openid" is used.
# OIDCScope "openid"
# OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
OIDCRedirectURI https://${OAUTH_HOSTNAME}/redirect_url
OIDCCryptoPassphrase Th1sIsA5uperS3cretP4ssphraseD0ntT3llTh3Cr4bz
OIDCUserInfoRefreshInterval 300
OIDCJWKSRefreshInterval 300
OIDCSessionInactivityTimeout 300
OIDCSessionType client-cookie:persistent
# preferred_username
OIDCRemoteUserClaim preferred_username
<Location />
AuthType openid-connect
Require valid-user
</Location>
</VirtualHost>
</IfModule>

View file

@ -1110,23 +1110,23 @@ async fn credential_update_exec(
CUAction::PasskeyRemove => { CUAction::PasskeyRemove => {
// TODO: make this a scrollable selector with a "cancel" option as the default // TODO: make this a scrollable selector with a "cancel" option as the default
match client match client
.idm_account_credential_update_status(&session_token) .idm_account_credential_update_status(&session_token)
.await .await
{ {
Ok(status) => { Ok(status) => {
if status.passkeys.is_empty() { if status.passkeys.is_empty() {
println!("No passkeys are configured for this user"); println!("No passkeys are configured for this user");
return return;
} }
println!("Current passkeys:"); println!("Current passkeys:");
for pk in status.passkeys { for pk in status.passkeys {
println!(" {} ({})", pk.tag, pk.uuid); println!(" {} ({})", pk.tag, pk.uuid);
}
},
Err(e) => {
eprintln!("An error occured pulling existing credentials -> {:?}", e);
} }
} }
Err(e) => {
eprintln!("An error occured pulling existing credentials -> {:?}", e);
}
}
let uuid_s: String = Input::new() let uuid_s: String = Input::new()
.with_prompt("\nEnter the UUID of the Passkey to remove (blank to stop) # ") .with_prompt("\nEnter the UUID of the Passkey to remove (blank to stop) # ")
.validate_with(|input: &String| -> Result<(), &str> { .validate_with(|input: &String| -> Result<(), &str> {

View file

@ -18,7 +18,8 @@ RUN zypper install -y \
sqlite3-devel \ sqlite3-devel \
sccache \ sccache \
gcc \ gcc \
rsync rsync \
which
RUN zypper clean -a RUN zypper clean -a
COPY . /usr/src/kanidm COPY . /usr/src/kanidm
@ -45,7 +46,7 @@ RUN if [ "${SCCACHE_REDIS}" != "" ]; \
export RUSTC_WRAPPER=sccache && \ export RUSTC_WRAPPER=sccache && \
sccache --start-server; \ sccache --start-server; \
fi && \ fi && \
./build_wasm_dev.sh ./build_wasm.sh
WORKDIR /usr/src/kanidm/kanidmd/daemon WORKDIR /usr/src/kanidm/kanidmd/daemon

View file

@ -470,7 +470,7 @@ impl Oauth2ResourceServersReadTransaction {
if auth_req.redirect_uri.origin() != o2rs.origin { if auth_req.redirect_uri.origin() != o2rs.origin {
admin_warn!( admin_warn!(
origin = ?o2rs.origin, origin = ?o2rs.origin,
"Invalid oauth2 redirect_uri (must be related to origin of)" "Invalid oauth2 redirect_uri (must be related to origin of) - got {:?}", auth_req.redirect_uri.origin()
); );
return Err(Oauth2Error::InvalidOrigin); return Err(Oauth2Error::InvalidOrigin);
} }

View file

@ -1897,7 +1897,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
.ok_or(OperationError::InvalidRequestState) .ok_or(OperationError::InvalidRequestState)
.and_then(|session| session.totp_accept_sha1(&origin, &aste.target)) .and_then(|session| session.totp_accept_sha1(&origin, &aste.target))
.map_err(|e| { .map_err(|e| {
admin_error!("Failed to accept sha1 totp {:?}", e); admin_error!("Failed to accept SHA1 TOTP {:?}", e);
e e
})?; })?;
@ -1913,7 +1913,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
})?; })?;
// reg the token // reg the token
let modlist = session.account.gen_totp_mod(token).map_err(|e| { let modlist = session.account.gen_totp_mod(token).map_err(|e| {
admin_error!("Failed to gen totp mod {:?}", e); admin_error!("Failed to gen TOTP mod {:?}", e);
e e
})?; })?;
// Perform the mod // Perform the mod

View file

@ -441,7 +441,9 @@ pub fn domain_rename_core(config: &Configuration) {
} }
let qs_write = task::block_on(qs.write_async(duration_from_epoch_now())); let qs_write = task::block_on(qs.write_async(duration_from_epoch_now()));
let r = qs_write.domain_rename(new_domain_name).and_then(|_| qs_write.commit()); let r = qs_write
.domain_rename(new_domain_name)
.and_then(|_| qs_write.commit());
match r { match r {
Ok(_) => info!("Domain Rename Success!"), Ok(_) => info!("Domain Rename Success!"),

View file

@ -13,42 +13,29 @@ documentation = "https://docs.rs/kanidm/latest/kanidm/"
homepage = "https://github.com/kanidm/kanidm/" homepage = "https://github.com/kanidm/kanidm/"
repository = "https://github.com/kanidm/kanidm/" repository = "https://github.com/kanidm/kanidm/"
[profile.release] # These are ignored because the crate is in a workspace
#[profile.release]
# less code to include into binary # less code to include into binary
panic = 'abort'
# optimization over all codebase ( better optimization, slower build )
codegen-units = 1
# optimization for size ( more aggressive )
opt-level = 'z'
# optimization for size
# opt-level = 's'
# link time optimization using using whole-program analysis
lto = true
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
serde = { version = "^1.0.139", features = ["derive"] }
serde_json = "^1.0.82"
wasm-bindgen = { version = "^0.2.81", features = ["serde-serialize"] }
wasm-bindgen-futures = { version = "^0.4.30" }
kanidm_proto = { path = "../kanidm_proto", features = ["wasm"] }
qrcode = { version = "^0.12.0", default-features = false, features = ["svg"] }
yew-router = "^0.16.0"
yew = "^0.19.3"
yew-agent = "^0.1.0"
gloo = "^0.8.0"
js-sys = "^0.3.58"
uuid = "^1.1.2"
compact_jwt = { version = "^0.2.3", default-features = false, features = ["unsafe_release_without_verify"] } compact_jwt = { version = "^0.2.3", default-features = false, features = ["unsafe_release_without_verify"] }
# compact_jwt = { path = "../../compact_jwt" , default-features = false, features = ["unsafe_release_without_verify"] } # compact_jwt = { path = "../../compact_jwt" , default-features = false, features = ["unsafe_release_without_verify"] }
gloo = "^0.8.0"
js-sys = "^0.3.58"
kanidm_proto = { path = "../kanidm_proto", features = ["wasm"] }
qrcode = { version = "^0.12.0", default-features = false, features = ["svg"] }
serde = { version = "^1.0.139", features = ["derive"] }
serde_json = "^1.0.82"
uuid = "^1.1.2"
wasm-bindgen = { version = "^0.2.81", features = ["serde-serialize"] }
wasm-bindgen-futures = { version = "^0.4.30" }
yew = "^0.19.3"
yew-agent = "^0.1.0"
yew-router = "^0.16.0"
wee_alloc = "^0.4.5" wee_alloc = "^0.4.5"
[dependencies.web-sys] [dependencies.web-sys]

View file

@ -1 +0,0 @@
build_wasm_release.sh

33
kanidmd_web_ui/build_wasm.sh Executable file
View file

@ -0,0 +1,33 @@
#!/bin/sh
# This builds the assets for the Web UI, defaulting to a release build.
if [ ! -f build_wasm.sh ]; then
echo "Please run from the crate directory. (kanidmd_web_ui)"
exit 1
fi
if [ -z "${BUILD_FLAGS}" ]; then
BUILD_FLAGS="--release --no-typescript"
fi
if [ -z "$(which rsync)" ]; then
echo "Cannot find rsync which is needed to move things around, quitting!"
exit 1
fi
if [ "$(find ./pkg/ -name 'kanidmd*' | wc -l)" -gt 0 ]; then
echo "Cleaning up"
rm pkg/kanidmd*
fi
# we can disable this since we want it to expand
# shellcheck disable=SC2086
wasm-pack build ${BUILD_FLAGS} --target web || exit 1
touch ./pkg/ANYTHING_HERE_WILL_BE_DELETED_ADD_TO_SRC && \
rsync --delete-after -r --copy-links -v ./src/img/ ./pkg/img/ && \
rsync --delete-after -r --copy-links -v ./src/external/ ./pkg/external/ && \
cp ./src/style.css ./pkg/style.css && \
cp ./src/wasmloader.js ./pkg/wasmloader.js && \
rm ./pkg/.gitignore

View file

@ -1,14 +0,0 @@
#!/bin/sh
if [ -z "${BUILD_FLAGS}" ]; then
BUILD_FLAGS="--release"
fi
wasm-pack build ${BUILD_FLAGS} --target web || exit 1
touch ./pkg/ANYTHING_HERE_WILL_BE_DELETED_ADD_TO_SRC && \
rsync --delete-after -r --copy-links -v ./src/img/ ./pkg/img/ && \
rsync --delete-after -r --copy-links -v ./src/external/ ./pkg/external/ && \
cp ./src/style.css ./pkg/style.css && \
cp ./src/wasmloader.js ./pkg/wasmloader.js && \
rm ./pkg/.gitignore

View file

@ -0,0 +1 @@
build_wasm.sh

View file

@ -1,40 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
*/
export function run_app(): void;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly run_app: (a: number) => void;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
readonly __wbindgen_export_2: WebAssembly.Table;
readonly _dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hc22c8c82b073edeb: (a: number, b: number, c: number) => void;
readonly _dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h88db5b93ed6c64b4: (a: number, b: number, c: number) => void;
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha0cda2043cde760e: (a: number, b: number, c: number) => void;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number) => void;
readonly __wbindgen_exn_store: (a: number) => void;
}
/**
* Synchronously compiles the given `bytes` and instantiates the WebAssembly module.
*
* @param {BufferSource} bytes
*
* @returns {InitOutput}
*/
export function initSync(bytes: BufferSource): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;

View file

@ -8,20 +8,6 @@ heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; } function getObject(idx) { return heap[idx]; }
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
let WASM_VECTOR_LEN = 0; let WASM_VECTOR_LEN = 0;
let cachedUint8Memory0 = new Uint8Array(); let cachedUint8Memory0 = new Uint8Array();
@ -95,14 +81,12 @@ function getInt32Memory0() {
return cachedInt32Memory0; return cachedInt32Memory0;
} }
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); function isLikeNone(x) {
return x === undefined || x === null;
cachedTextDecoder.decode();
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
} }
let heap_next = heap.length;
function addHeapObject(obj) { function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1); if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next; const idx = heap_next;
@ -112,8 +96,24 @@ function addHeapObject(obj) {
return idx; return idx;
} }
function isLikeNone(x) { const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
return x === undefined || x === null;
cachedTextDecoder.decode();
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
} }
let cachedFloat64Memory0 = new Float64Array(); let cachedFloat64Memory0 = new Float64Array();
@ -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__hc22c8c82b073edeb(arg0, arg1, addBorrowedObject(arg2)); wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0f7357f2a774775d(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__h88db5b93ed6c64b4(arg0, arg1, addHeapObject(arg2)); wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h207c5c753184291b(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__ha0cda2043cde760e(arg0, arg1, addHeapObject(arg2)); wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hae7ad8c3f91d439a(arg0, arg1, addHeapObject(arg2));
} }
/** /**
@ -336,9 +336,6 @@ async function load(module, imports) {
function getImports() { function getImports() {
const imports = {}; const imports = {};
imports.wbg = {}; imports.wbg = {};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbindgen_json_serialize = function(arg0, arg1) { imports.wbg.__wbindgen_json_serialize = function(arg0, arg1) {
const obj = getObject(arg1); const obj = getObject(arg1);
const ret = JSON.stringify(obj === undefined ? null : obj); const ret = JSON.stringify(obj === undefined ? null : obj);
@ -347,22 +344,6 @@ function getImports() {
getInt32Memory0()[arg0 / 4 + 1] = len0; getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0; getInt32Memory0()[arg0 / 4 + 0] = ptr0;
}; };
imports.wbg.__wbindgen_cb_drop = function(arg0) {
const obj = takeObject(arg0).original;
if (obj.cnt-- == 1) {
obj.a = 0;
return true;
}
const ret = false;
return ret;
};
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
};
imports.wbg.__wbg_modalhidebyid_b9efcd5f48cb1c79 = function(arg0, arg1) {
modal_hide_by_id(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) { imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1); const obj = getObject(arg1);
const ret = typeof(obj) === 'string' ? obj : undefined; const ret = typeof(obj) === 'string' ? obj : undefined;
@ -375,6 +356,25 @@ function getImports() {
const ret = getObject(arg0); const ret = getObject(arg0);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbindgen_cb_drop = function(arg0) {
const obj = takeObject(arg0).original;
if (obj.cnt-- == 1) {
obj.a = 0;
return true;
}
const ret = false;
return ret;
};
imports.wbg.__wbg_modalhidebyid_b9efcd5f48cb1c79 = function(arg0, arg1) {
modal_hide_by_id(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_boolean_get = function(arg0) { imports.wbg.__wbindgen_boolean_get = function(arg0) {
const v = getObject(arg0); const v = getObject(arg0);
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
@ -421,10 +421,10 @@ function getImports() {
wasm.__wbindgen_free(arg0, arg1 * 4); wasm.__wbindgen_free(arg0, arg1 * 4);
console.debug(...v0); console.debug(...v0);
}; };
imports.wbg.__wbg_log_06b7ffc63a0f8bee = function(arg0, arg1) { imports.wbg.__wbg_error_1a35d3879f286b52 = 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.log(...v0); console.error(...v0);
}; };
imports.wbg.__wbg_warn_2aa0e7178e1d35f6 = function(arg0, arg1) { imports.wbg.__wbg_warn_2aa0e7178e1d35f6 = function(arg0, arg1) {
var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice(); var v0 = getArrayJsValueFromWasm0(arg0, arg1).slice();
@ -631,17 +631,6 @@ function getImports() {
const ret = getObject(arg0).getClientExtensionResults(); const ret = getObject(arg0).getClientExtensionResults();
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbg_instanceof_HtmlDocument_508cdf1699f6d222 = function(arg0) {
const ret = getObject(arg0) instanceof HTMLDocument;
return ret;
};
imports.wbg.__wbg_cookie_d9164dc637b7b2fd = function() { return handleError(function (arg0, arg1) {
const ret = getObject(arg1).cookie;
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;
}, arguments) };
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) {
getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4)); getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3), getObject(arg4));
}, arguments) }; }, arguments) };
@ -824,16 +813,16 @@ function getImports() {
const ret = wasm.memory; const ret = wasm.memory;
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2488 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper4809 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 1041, __wbg_adapter_30); const ret = makeMutClosure(arg0, arg1, 1273, __wbg_adapter_30);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2593 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper4914 = function(arg0, arg1, arg2) {
const ret = makeClosure(arg0, arg1, 1074, __wbg_adapter_33); const ret = makeClosure(arg0, arg1, 1306, __wbg_adapter_33);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2859 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper5180 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 1127, __wbg_adapter_36); const ret = makeMutClosure(arg0, arg1, 1359, __wbg_adapter_36);
return addHeapObject(ret); return addHeapObject(ret);
}; };

View file

@ -1,13 +0,0 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export function run_app(a: number): void;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;
export const __wbindgen_export_2: WebAssembly.Table;
export function _dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hc22c8c82b073edeb(a: number, b: number, c: number): void;
export function _dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h88db5b93ed6c64b4(a: number, b: number, c: number): void;
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha0cda2043cde760e(a: number, b: number, c: number): void;
export function __wbindgen_add_to_stack_pointer(a: number): number;
export function __wbindgen_free(a: number, b: number): void;
export function __wbindgen_exn_store(a: number): void;

View file

@ -14,11 +14,9 @@
"files": [ "files": [
"kanidmd_web_ui_bg.wasm", "kanidmd_web_ui_bg.wasm",
"kanidmd_web_ui.js", "kanidmd_web_ui.js",
"kanidmd_web_ui.d.ts",
"LICENSE.md" "LICENSE.md"
], ],
"module": "kanidmd_web_ui.js", "module": "kanidmd_web_ui.js",
"homepage": "https://github.com/kanidm/kanidm/", "homepage": "https://github.com/kanidm/kanidm/",
"types": "kanidmd_web_ui.d.ts",
"sideEffects": false "sideEffects": false
} }

View file

@ -6,10 +6,9 @@ async function main() {
} }
main() main()
// this is used in // this is used in modals
export function modal_hide_by_id(m) { export function modal_hide_by_id(m) {
var elem = document.getElementById(m); var elem = document.getElementById(m);
var modal = bootstrap.Modal.getInstance(elem); var modal = bootstrap.Modal.getInstance(elem);
modal.hide(); modal.hide();
}; };

View file

@ -4,6 +4,7 @@ use crate::utils;
use super::eventbus::{EventBus, EventBusMsg}; use super::eventbus::{EventBus, EventBusMsg};
use super::reset::ModalProps; use super::reset::ModalProps;
#[cfg(debug)]
use gloo::console; use gloo::console;
use yew::prelude::*; use yew::prelude::*;
use yew_agent::Dispatched; use yew_agent::Dispatched;
@ -90,18 +91,21 @@ impl Component for DeleteApp {
type Properties = ModalProps; type Properties = ModalProps;
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("delete modal create"); #[cfg(debug)]
console::debug!("delete modal create");
DeleteApp { state: State::Init } DeleteApp { state: State::Init }
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("delete modal::change"); #[cfg(debug)]
console::debug!("delete modal::change");
false false
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("delete modal::update"); #[cfg(debug)]
console::debug!("delete modal::update");
let token_c = ctx.props().token.clone(); let token_c = ctx.props().token.clone();
match msg { match msg {
Msg::Cancel => { Msg::Cancel => {
@ -130,15 +134,18 @@ impl Component for DeleteApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("delete modal::rendered"); #[cfg(debug)]
console::debug!("delete modal::rendered");
} }
fn destroy(&mut self, _ctx: &Context<Self>) { fn destroy(&mut self, _ctx: &Context<Self>) {
console::log!("delete modal::destroy"); #[cfg(debug)]
console::debug!("delete modal::destroy");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
console::log!("delete modal::view"); #[cfg(debug)]
console::debug!("delete modal::view");
let submit_enabled = matches!(&self.state, State::Init); let submit_enabled = matches!(&self.state, State::Init);
@ -160,6 +167,7 @@ impl Component for DeleteApp {
<div class="modal-body"> <div class="modal-body">
<p>{ "Delete your Password and any associated MFA?" }</p> <p>{ "Delete your Password and any associated MFA?" }</p>
<p><strong>{ "Note:"}</strong>{" this will not remove Passkeys." }</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View file

@ -117,7 +117,7 @@ impl Component for PasskeyModalApp {
type Properties = ModalProps; type Properties = ModalProps;
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("passkey modal create"); console::debug!("passkey modal create");
PasskeyModalApp { PasskeyModalApp {
state: State::Init, state: State::Init,
@ -126,12 +126,12 @@ impl Component for PasskeyModalApp {
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("passkey modal::change"); console::debug!("passkey modal::change");
false false
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("passkey modal::update"); console::debug!("passkey modal::update");
match msg { match msg {
Msg::LabelCheck => { Msg::LabelCheck => {
let label = utils::get_value_from_element_id("passkey-label") let label = utils::get_value_from_element_id("passkey-label")
@ -179,7 +179,7 @@ impl Component for PasskeyModalApp {
self.state = State::FetchingChallenge; self.state = State::FetchingChallenge;
} }
Msg::ChallengeReady(challenge) => { Msg::ChallengeReady(challenge) => {
console::log!(format!("{:?}", challenge).as_str()); console::debug!(format!("{:?}", challenge).as_str());
self.state = State::ChallengeReady(challenge); self.state = State::ChallengeReady(challenge);
} }
Msg::CredentialCreate => { Msg::CredentialCreate => {
@ -209,7 +209,7 @@ impl Component for PasskeyModalApp {
Msg::CredentialReady(rpkc) Msg::CredentialReady(rpkc)
} }
Err(e) => { Err(e) => {
console::log!(format!("error -> {:?}", e).as_str()); console::error!(format!("error -> {:?}", e).as_str());
Msg::NavigatorError Msg::NavigatorError
} }
} }
@ -217,7 +217,7 @@ impl Component for PasskeyModalApp {
} }
} }
Msg::CredentialReady(rpkc) => { Msg::CredentialReady(rpkc) => {
console::log!(format!("{:?}", rpkc).as_str()); console::debug!(format!("{:?}", rpkc).as_str());
self.state = State::CredentialReady(rpkc); self.state = State::CredentialReady(rpkc);
} }
Msg::NavigatorError => { Msg::NavigatorError => {
@ -247,15 +247,15 @@ impl Component for PasskeyModalApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("passkey modal::rendered"); console::debug!("passkey modal::rendered");
} }
fn destroy(&mut self, _ctx: &Context<Self>) { fn destroy(&mut self, _ctx: &Context<Self>) {
console::log!("passkey modal::destroy"); console::debug!("passkey modal::destroy");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
console::log!("passkey modal::view"); console::debug!("passkey modal::view");
let label_val = self.label_val.clone(); let label_val = self.label_val.clone();
@ -355,7 +355,7 @@ impl Component for PasskeyModalApp {
<form class="row g-3 needs-validation" novalidate=true <form class="row g-3 needs-validation" novalidate=true
onsubmit={ ctx.link().callback(move |e: FocusEvent| { onsubmit={ ctx.link().callback(move |e: FocusEvent| {
console::log!("passkey modal::on form submit prevent default"); console::debug!("passkey modal::on form submit prevent default");
e.prevent_default(); e.prevent_default();
if submit_enabled { if submit_enabled {
Msg::Submit Msg::Submit

View file

@ -50,14 +50,14 @@ impl PasskeyRemoveModalApp {
let tag = tag.to_string(); let tag = tag.to_string();
html! { html! {
<li> <div class="row mb-3">
<div class="row g-3"> <div class="col">{ tag.clone() }</div>
<p>{ tag }</p> <div class="col">
<button type="button" class="btn btn-dark btn-sml" data-bs-toggle="modal" data-bs-target={ remove_tgt }> <button type="button" class="btn btn-dark btn-sml" id={tag} data-bs-toggle="modal" data-bs-target={ remove_tgt }>
{ "Remove" } { "Remove" }
</button> </button>
</div>
</div> </div>
</li>
} }
} }
@ -126,7 +126,7 @@ impl Component for PasskeyRemoveModalApp {
type Properties = PasskeyRemoveModalProps; type Properties = PasskeyRemoveModalProps;
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
console::log!("passkey remove modal create"); console::debug!("passkey remove modal create");
let tag = ctx.props().tag.clone(); let tag = ctx.props().tag.clone();
let uuid = ctx.props().uuid.clone(); let uuid = ctx.props().uuid.clone();
@ -141,12 +141,12 @@ impl Component for PasskeyRemoveModalApp {
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("passkey remove modal::change"); console::debug!("passkey remove modal::change");
false false
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("passkey remove modal::update"); console::debug!("passkey remove modal::update");
match msg { match msg {
Msg::Submit => { Msg::Submit => {
self.reset_and_hide(); self.reset_and_hide();
@ -178,15 +178,15 @@ impl Component for PasskeyRemoveModalApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("passkey remove modal::rendered"); console::debug!("passkey remove modal::rendered");
} }
fn destroy(&mut self, _ctx: &Context<Self>) { fn destroy(&mut self, _ctx: &Context<Self>) {
console::log!("passkey remove modal::destroy"); console::debug!("passkey remove modal::destroy");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
console::log!("passkey remove modal::view"); console::debug!("passkey remove modal::view");
let remove_tgt = self.target.clone(); let remove_tgt = self.target.clone();
let remove_id = format!("staticPasskeyRemove-{}", self.uuid); let remove_id = format!("staticPasskeyRemove-{}", self.uuid);

View file

@ -114,7 +114,8 @@ impl Component for PwModalApp {
type Properties = ModalProps; type Properties = ModalProps;
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("pw modal create"); #[cfg(debug)]
console::debug!("pw modal create");
PwModalApp { PwModalApp {
state: PwState::Init, state: PwState::Init,
@ -125,12 +126,14 @@ impl Component for PwModalApp {
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("pw modal::change"); #[cfg(debug)]
console::debug!("pw modal::change");
false false
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("pw modal::update"); #[cfg(debug)]
console::debug!("pw modal::update");
match msg { match msg {
Msg::PasswordCheck => { Msg::PasswordCheck => {
let pw = let pw =
@ -181,15 +184,18 @@ impl Component for PwModalApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("pw modal::rendered"); #[cfg(debug)]
console::debug!("pw modal::rendered");
} }
fn destroy(&mut self, _ctx: &Context<Self>) { fn destroy(&mut self, _ctx: &Context<Self>) {
console::log!("pw modal::destroy"); #[cfg(debug)]
console::debug!("pw modal::destroy");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
console::log!("pw modal::view"); #[cfg(debug)]
console::debug!("pw modal::view");
let (pw_class, pw_feedback) = match &self.state { let (pw_class, pw_feedback) = match &self.state {
PwState::Feedback(feedback) => { PwState::Feedback(feedback) => {
@ -250,7 +256,7 @@ impl Component for PwModalApp {
<div class="modal-body"> <div class="modal-body">
<form class="row g-3 needs-validation" novalidate=true <form class="row g-3 needs-validation" novalidate=true
onsubmit={ ctx.link().callback(move |e: FocusEvent| { onsubmit={ ctx.link().callback(move |e: FocusEvent| {
console::log!("pw modal::on form submit prevent default"); console::debug!("pw modal::on form submit prevent default");
e.prevent_default(); e.prevent_default();
if submit_enabled { if submit_enabled {
Msg::PasswordSubmit Msg::PasswordSubmit

View file

@ -95,7 +95,8 @@ impl Component for CredentialResetApp {
type Properties = (); type Properties = ();
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
console::log!("credential::reset::create"); #[cfg(debug)]
console::debug!("credential::reset::create");
// On a page refresh/reload, should we restart a session that *may* have existed? // On a page refresh/reload, should we restart a session that *may* have existed?
// This could be achieved with local storage // This could be achieved with local storage
@ -114,11 +115,12 @@ impl Component for CredentialResetApp {
.location() .location()
.expect_throw("Can't access current location"); .expect_throw("Can't access current location");
// TODO: the error here ... isn't always an error, when a user comes from the dashboard they don't set a cred token in the URL, probably should handle this with a *slightly* nicer error
let query: Option<CUIntentToken> = location let query: Option<CUIntentToken> = location
.query() .query()
.map_err(|e| { .map_err(|e| {
let e_msg = format!("query decode error -> {:?}", e); let e_msg = format!("error decoding URL parameters -> {:?}", e);
console::log!(e_msg.as_str()); console::error!(e_msg.as_str());
}) })
.ok(); .ok();
@ -153,18 +155,20 @@ impl Component for CredentialResetApp {
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("credential::reset::change"); #[cfg(debug)]
console::debug!("credential::reset::change");
false false
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("credential::reset::update"); #[cfg(debug)]
console::debug!("credential::reset::update");
let next_state = match (msg, &self.state) { let next_state = match (msg, &self.state) {
(Msg::Ignore, _) => None, (Msg::Ignore, _) => None,
(Msg::TokenSubmit, State::TokenInput) => { (Msg::TokenSubmit, State::TokenInput) => {
#[allow(clippy::expect_used)] #[allow(clippy::expect_used)]
let token = utils::get_value_from_element_id("autofocus") let token = utils::get_value_from_element_id("token")
.expect("Unable to find autofocus element"); .expect("Unable to find an input with id=token");
ctx.link().send_future(async { ctx.link().send_future(async {
match Self::exchange_intent_token(token).await { match Self::exchange_intent_token(token).await {
@ -176,18 +180,20 @@ impl Component for CredentialResetApp {
Some(State::WaitingForStatus) Some(State::WaitingForStatus)
} }
(Msg::BeginSession { token, status }, State::WaitingForStatus) => { (Msg::BeginSession { token, status }, State::WaitingForStatus) => {
console::log!(format!("{:?}", status).as_str()); #[cfg(debug)]
console::debug!(format!("begin session {:?}", status).as_str());
Some(State::Main { token, status }) Some(State::Main { token, status })
} }
(Msg::UpdateSession { status }, State::Main { token, status: _ }) => { (Msg::UpdateSession { status }, State::Main { token, status: _ }) => {
console::log!(format!("{:?}", status).as_str()); #[cfg(debug)]
console::debug!(format!("{:?}", status).as_str());
Some(State::Main { Some(State::Main {
token: token.clone(), token: token.clone(),
status, status,
}) })
} }
(Msg::Commit, State::Main { token, status }) => { (Msg::Commit, State::Main { token, status }) => {
console::log!(format!("{:?}", status).as_str()); console::debug!(format!("{:?}", status).as_str());
let token_c = token.clone(); let token_c = token.clone();
ctx.link().send_future(async { ctx.link().send_future(async {
@ -200,7 +206,7 @@ impl Component for CredentialResetApp {
Some(State::WaitingForCommit) Some(State::WaitingForCommit)
} }
(Msg::Cancel, State::Main { token, status }) => { (Msg::Cancel, State::Main { token, status }) => {
console::log!(format!("{:?}", status).as_str()); console::debug!(format!("{:?}", status).as_str());
let token_c = token.clone(); let token_c = token.clone();
ctx.link().send_future(async { ctx.link().send_future(async {
@ -214,14 +220,15 @@ impl Component for CredentialResetApp {
} }
(Msg::Success, State::WaitingForCommit) => { (Msg::Success, State::WaitingForCommit) => {
let loc = models::pop_return_location(); let loc = models::pop_return_location();
console::log!(format!("Going to -> {:?}", loc)); #[cfg(debug)]
console::debug!(format!("Going to -> {:?}", loc));
loc.goto(&ctx.link().history().expect_throw("failed to read history")); loc.goto(&ctx.link().history().expect_throw("failed to read history"));
None None
} }
(Msg::Error { emsg, kopid }, _) => Some(State::Error { emsg, kopid }), (Msg::Error { emsg, kopid }, _) => Some(State::Error { emsg, kopid }),
(_, _) => { (_, _) => {
console::debug!("CredentialResetApp state match fail on update."); console::error!("CredentialResetApp state match fail on update.");
None None
} }
}; };
@ -235,12 +242,15 @@ impl Component for CredentialResetApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
crate::utils::autofocus(); #[cfg(debug)]
console::log!("credential::reset::rendered"); console::debug!("credential::reset::rendered");
// because sometimes bootstrap doesn't catch it, which is annoying.
crate::utils::autofocus("token");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
console::log!("credential::reset::view"); #[cfg(debug)]
console::debug!("credential::reset::view");
match &self.state { match &self.state {
State::TokenInput => self.view_token_input(ctx), State::TokenInput => self.view_token_input(ctx),
State::WaitingForStatus | State::WaitingForCommit => self.view_waiting(ctx), State::WaitingForStatus | State::WaitingForCommit => self.view_waiting(ctx),
@ -250,7 +260,8 @@ impl Component for CredentialResetApp {
} }
fn destroy(&mut self, _ctx: &Context<Self>) { fn destroy(&mut self, _ctx: &Context<Self>) {
console::log!("credential::reset::destroy"); #[cfg(debug)]
console::debug!("credential::reset::destroy");
remove_body_form_classes!(); remove_body_form_classes!();
} }
} }
@ -261,31 +272,38 @@ impl CredentialResetApp {
<main class="flex-shrink-0 form-signin"> <main class="flex-shrink-0 form-signin">
<center> <center>
<img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/> <img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/>
<h2>{ "Credential Reset" } </h2>
// TODO: replace this with a call to domain info // TODO: replace this with a call to domain info
<h3>{ "Kanidm idm.example.com" } </h3> <h3>{ "idm.example.com" } </h3>
</center> </center>
<p>
{"Enter your credential reset token"}
</p>
<form <form
onsubmit={ ctx.link().callback(|e: FocusEvent| { onsubmit={ ctx.link().callback(|e: FocusEvent| {
console::log!("credential::reset::view_token_input -> TokenInput - prevent_default()"); console::debug!("credential::reset::view_token_input -> TokenInput - prevent_default()");
e.prevent_default(); e.prevent_default();
Msg::TokenSubmit Msg::TokenSubmit
} ) } } ) }
action="javascript:void(0);"> action="javascript:void(0);">
<p class="text-center">
<label for="token" class="form-label">
{"Enter your credential reset token."}
</label>
<input <input
id="autofocus" id="token"
name="token"
autofocus=true
type="text" type="text"
class="form-control" class="form-control"
value="" value=""
/> />
<button type="submit" class="btn btn-dark">{" Submit "}</button><br /> </p>
</form> <p class="text-center">
<p> <button type="submit" class="btn btn-primary">{" Submit "}</button><br />
<a href="/"><button href="/" class="btn btn-dark" aria-label="Return home">{"Return to the home page"}</button></a> </p>
</p> </form>
<p class="text-center">
<a href="/"><button href="/" class="btn btn-secondary" aria-label="Return home">{"Return to the home page"}</button></a>
</p>
</main> </main>
} }
@ -321,8 +339,8 @@ impl CredentialResetApp {
}) => { }) => {
html! { html! {
<> <>
<p>{ "Password Set" }</p> <p>{ "Password Set" }</p>
<p>{ "Mfa Disabled" }</p> <p>{ "❌ MFA Disabled" }</p>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticTotpCreate"> <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticTotpCreate">
{ "Add TOTP" } { "Add TOTP" }
@ -373,8 +391,8 @@ impl CredentialResetApp {
}) => { }) => {
html! { html! {
<> <>
<p>{ "Password Set" }</p> <p>{ "Password Set" }</p>
<p>{ "Mfa Enabled" }</p> <p>{ "✅ MFA Enabled" }</p>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticTotpCreate"> <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#staticTotpCreate">
{ "Reset TOTP" } { "Reset TOTP" }
@ -401,15 +419,14 @@ impl CredentialResetApp {
} }
} else { } else {
html! { html! {
<div class="container"> <>
<ul class="list-unstyled"> <h4>{"Registered Passkeys"}</h4>
{ for status.passkeys.iter() { for status.passkeys.iter()
.map(|detail| .map(|detail|
PasskeyRemoveModalApp::render_button(&detail.tag, detail.uuid) PasskeyRemoveModalApp::render_button(&detail.tag, detail.uuid)
) )
} }
</ul> </>
</div>
} }
}; };
@ -424,10 +441,11 @@ impl CredentialResetApp {
}; };
html! { html! {
<>
<div class="d-flex align-items-start form-cred-reset-body"> <div class="d-flex align-items-start form-cred-reset-body">
<main class="w-100"> <main class="w-100">
<div class="py-5 text-center"> <div class="py-3 text-center">
<h4>{ "Updating Credentials" }</h4> <h3>{ "Updating Credentials" }</h3>
<p>{ displayname }</p> <p>{ displayname }</p>
<p>{ spn }</p> <p>{ spn }</p>
</div> </div>
@ -444,20 +462,22 @@ impl CredentialResetApp {
<hr class="my-4" /> <hr class="my-4" />
<h4>{"Password / MFA"}</h4>
{ pw_html } { pw_html }
<hr class="my-4" /> <hr class="my-4" />
// TODO: this could probably just be a link back home, currently it breaks and sends the user to an error..
<button class="w-50 btn btn-danger btn-lg" type="submit" <button class="w-50 btn btn-danger btn-lg" type="submit"
disabled=false disabled=false
onclick={ onclick={
ctx.link() ctx.link()
.callback(move |_| { .callback(move |_| {
Msg::Cancel Msg::Cancel
}) })
} }
>{ "Cancel" }</button> >{ "Cancel" }</button>
<button class="w-50 btn btn-success btn-lg" type="submit" <button class="w-50 btn btn-success btn-lg" type="submit"
disabled={ !can_commit } disabled={ !can_commit }
onclick={ onclick={
ctx.link() ctx.link()
@ -481,15 +501,19 @@ impl CredentialResetApp {
{ passkey_modals_html } { passkey_modals_html }
</div> </div>
{ crate::utils::do_footer() }
</>
} }
} }
fn view_error(&self, _ctx: &Context<Self>, msg: &str, kopid: Option<&str>) -> Html { fn view_error(&self, _ctx: &Context<Self>, msg: &str, kopid: Option<&str>) -> Html {
html! { html! {
<main class="form-signin"> <main class="form-signin">
<div class="container"> <p class="text-center">
<img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/>
</p>
<div class="alert alert-danger" role="alert">
<h2>{ "An Error Occured 🥺" }</h2> <h2>{ "An Error Occured 🥺" }</h2>
</div>
<p>{ msg.to_string() }</p> <p>{ msg.to_string() }</p>
<p> <p>
{ {
@ -500,6 +524,10 @@ impl CredentialResetApp {
} }
} }
</p> </p>
</div>
<p class="text-center">
<a href="/"><button href="/" class="btn btn-secondary" aria-label="Return home">{"Return to the home page"}</button></a>
</p>
</main> </main>
} }
} }

View file

@ -4,6 +4,7 @@ use crate::utils;
use super::eventbus::{EventBus, EventBusMsg}; use super::eventbus::{EventBus, EventBusMsg};
use super::reset::ModalProps; use super::reset::ModalProps;
#[cfg(debug)]
use gloo::console; use gloo::console;
use web_sys::Node; use web_sys::Node;
use yew::prelude::*; use yew::prelude::*;
@ -125,7 +126,8 @@ impl Component for TotpModalApp {
type Properties = ModalProps; type Properties = ModalProps;
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("totp modal create"); #[cfg(debug)]
console::debug!("totp modal create");
TotpModalApp { TotpModalApp {
state: TotpState::Init, state: TotpState::Init,
@ -135,12 +137,14 @@ impl Component for TotpModalApp {
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("totp modal::change"); #[cfg(debug)]
console::debug!("totp modal::change");
false false
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("totp modal::update"); #[cfg(debug)]
console::debug!("totp modal::update");
let token_c = ctx.props().token.clone(); let token_c = ctx.props().token.clone();
match msg { match msg {
Msg::TotpCancel => { Msg::TotpCancel => {
@ -235,15 +239,18 @@ impl Component for TotpModalApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("totp modal::rendered"); #[cfg(debug)]
console::debug!("totp modal::rendered");
} }
fn destroy(&mut self, _ctx: &Context<Self>) { fn destroy(&mut self, _ctx: &Context<Self>) {
console::log!("totp modal::destroy"); #[cfg(debug)]
console::debug!("totp modal::destroy");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
console::log!("totp modal::view"); #[cfg(debug)]
console::debug!("totp modal::view");
let totp_class = match &self.check { let totp_class = match &self.check {
TotpCheck::Invalid | TotpCheck::Sha1Accept => classes!("form-control", "is-invalid"), TotpCheck::Invalid | TotpCheck::Sha1Accept => classes!("form-control", "is-invalid"),
@ -268,7 +275,7 @@ impl Component for TotpModalApp {
Msg::TotpAcceptSha1 Msg::TotpAcceptSha1
}) })
} }
>{ "Accept Sha1 Token" }</button> >{ "Accept SHA1 Token" }</button>
}, },
_ => html! { _ => html! {
<button id="totp-submit" type="button" class="btn btn-primary" <button id="totp-submit" type="button" class="btn btn-primary"

View file

@ -5,6 +5,7 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_futures::{spawn_local, JsFuture}; use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{Request, RequestInit, RequestMode, Response}; use web_sys::{Request, RequestInit, RequestMode, Response};
use yew::prelude::*; use yew::prelude::*;
use yew::virtual_dom::VNode;
use yew_router::prelude::*; use yew_router::prelude::*;
use crate::error::FetchError; use crate::error::FetchError;
@ -12,7 +13,7 @@ use crate::models;
use crate::utils; use crate::utils;
use kanidm_proto::v1::{ use kanidm_proto::v1::{
AuthAllowed, AuthCredential, AuthRequest, AuthResponse, AuthState, AuthStep, AuthMech AuthAllowed, AuthCredential, AuthMech, AuthRequest, AuthResponse, AuthState, AuthStep,
}; };
use kanidm_proto::webauthn::PublicKeyCredential; use kanidm_proto::webauthn::PublicKeyCredential;
@ -114,7 +115,7 @@ impl LoginApp {
} else if status == 404 { } else if status == 404 {
let kopid = headers.get("x-kanidm-opid").ok().flatten(); let kopid = headers.get("x-kanidm-opid").ok().flatten();
let text = JsFuture::from(resp.text()?).await?; let text = JsFuture::from(resp.text()?).await?;
console::log!(format!( console::error!(format!(
"User not found: {:?}. Operation ID: {:?}", "User not found: {:?}. Operation ID: {:?}",
text, kopid text, kopid
)); ));
@ -170,6 +171,15 @@ impl LoginApp {
} }
} }
/// Renders the "Start again" button
fn button_start_again(&self, ctx: &Context<Self>) -> VNode {
html! {
<div class="col-md-auto text-center">
<button type="button" class="btn btn-dark" onclick={ ctx.link().callback(|_| LoginAppMsg::Restart) } >{" Start Again "}</button>
</div>
}
}
fn render_auth_allowed(&self, ctx: &Context<Self>, idx: usize, allow: &AuthAllowed) -> Html { fn render_auth_allowed(&self, ctx: &Context<Self>, idx: usize, allow: &AuthAllowed) -> Html {
html! { html! {
<li> <li>
@ -184,10 +194,10 @@ impl LoginApp {
fn render_mech_select(&self, ctx: &Context<Self>, idx: usize, allow: &AuthMech) -> Html { fn render_mech_select(&self, ctx: &Context<Self>, idx: usize, allow: &AuthMech) -> Html {
html! { html! {
<li> <li class="text-center">
<button <button
type="button" type="button"
class="btn btn-dark" class="btn btn-dark mb-2"
onclick={ ctx.link().callback(move |_| LoginAppMsg::Select(idx)) } onclick={ ctx.link().callback(move |_| LoginAppMsg::Select(idx)) }
>{ allow.to_string() }</button> >{ allow.to_string() }</button>
</li> </li>
@ -201,22 +211,25 @@ impl LoginApp {
html! { html! {
<> <>
<div class="container"> <div class="container">
<label for="autofocus" class="form-label">{ " Username " }</label> <label for="username" class="form-label">{ "Username" }</label>
<form <form
onsubmit={ ctx.link().callback(|e: FocusEvent| { onsubmit={ ctx.link().callback(|e: FocusEvent| {
console::log!("login::view_state -> Init - prevent_default()".to_string()); console::debug!("login::view_state -> Init - prevent_default()".to_string());
e.prevent_default(); e.prevent_default();
LoginAppMsg::Begin LoginAppMsg::Begin
} ) } } ) }
action="javascript:void(0);" action="javascript:void(0);"
> >
<div class="input-group mb-3"> <div class="input-group mb-3">
<input id="autofocus" <input
type="text" autofocus=true
class="form-control" class="autofocus form-control"
value={ inputvalue }
disabled={ !enable } disabled={ !enable }
id="username"
name="username"
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e))) } oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e))) }
type="text"
value={ inputvalue }
/> />
</div> </div>
@ -271,34 +284,29 @@ impl LoginApp {
LoginState::Password(enable) => { LoginState::Password(enable) => {
html! { html! {
<> <>
<div class="container">
<p>
{" Password: "}
</p>
</div>
<div class="container"> <div class="container">
<form class="row g-3" <form class="row g-3"
action="javascript:void(0);"
onsubmit={ ctx.link().callback(|e: FocusEvent| { onsubmit={ ctx.link().callback(|e: FocusEvent| {
console::log!("login::view_state -> Password - prevent_default()".to_string()); console::debug!("login::view_state -> Password - prevent_default()".to_string());
e.prevent_default(); e.prevent_default();
LoginAppMsg::PasswordSubmit LoginAppMsg::PasswordSubmit
} ) } } ) }
action="javascript:void(0);"
> >
<div class="col-12"> <div class="col-12"><label for="password" class="form-label">{ "Password" }</label>
<input <input
id="autofocus" autofocus=true
type="password" class="autofocus form-control"
class="form-control"
value={ inputvalue }
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e))) }
disabled={ !enable } disabled={ !enable }
id="password"
name="password"
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e))) }
type="password"
value={ inputvalue }
/> />
</div> </div>
<div class="col-12"> <div class="col-12 text-center">
<center> <button type="submit" class="btn btn-dark" disabled={ !enable }>{ "Submit" }</button>
<button type="submit" class="btn btn-dark" disabled={ !enable }>{" Submit "}</button>
</center>
</div> </div>
</form> </form>
</div> </div>
@ -308,29 +316,30 @@ impl LoginApp {
LoginState::BackupCode(enable) => { LoginState::BackupCode(enable) => {
html! { html! {
<> <>
<div class="container">
<p>
{" Backup Code: "}
</p>
</div>
<div class="container"> <div class="container">
<form <form
onsubmit={ ctx.link().callback(|e: FocusEvent| { onsubmit={ ctx.link().callback(|e: FocusEvent| {
console::log!("login::view_state -> BackupCode - prevent_default()".to_string()); console::debug!("login::view_state -> BackupCode - prevent_default()".to_string());
e.prevent_default(); e.prevent_default();
LoginAppMsg::BackupCodeSubmit LoginAppMsg::BackupCodeSubmit
} ) } } ) }
action="javascript:void(0);" action="javascript:void(0);"
> >
<label for="backup_code" class="form-label">
{"Backup Code"}
</label>
<input <input
id="autofocus" autofocus=true
type="text" class="autofocus form-control"
class="form-control"
value={ inputvalue }
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e))) }
disabled={ !enable } 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"
value={ inputvalue }
/> />
<button type="submit" class="btn btn-dark" disabled={ !enable }>{" Submit "}</button> <button type="submit" class="btn btn-dark">{" Submit "}</button>
</form> </form>
</div> </div>
</> </>
@ -339,25 +348,25 @@ impl LoginApp {
LoginState::Totp(state) => { LoginState::Totp(state) => {
html! { html! {
<> <>
<div class="container">
<p>
{" TOTP: "}
{ if state==&TotpState::Invalid { "can only contain numeric digits" } else { "" } }
</p>
</div>
<div class="container"> <div class="container">
<form <form
onsubmit={ ctx.link().callback(|e: FocusEvent| { onsubmit={ ctx.link().callback(|e: FocusEvent| {
console::log!("login::view_state -> Totp - prevent_default()".to_string()); console::debug!("login::view_state -> Totp - prevent_default()".to_string());
e.prevent_default(); e.prevent_default();
LoginAppMsg::TotpSubmit LoginAppMsg::TotpSubmit
} ) } } ) }
action="javascript:void(0);" action="javascript:void(0);"
> >
<label for="totp" class="form-label">
{"TOTP"}
{ if state==&TotpState::Invalid { "can only contain numeric digits" } else { "" } }
</label>
<input <input
id="autofocus" autofocus=true
name="totp"
id="totp"
type="text" type="text"
class="form-control" class="autofocus form-control"
value={ inputvalue } value={ inputvalue }
oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e)))} oninput={ ctx.link().callback(|e: InputEvent| LoginAppMsg::Input(utils::get_value_from_input_event(e)))}
disabled={ state==&TotpState::Disabled } disabled={ state==&TotpState::Disabled }
@ -445,9 +454,9 @@ impl LoginApp {
}; };
html! { html! {
<div class="container"> <div class="container text-center">
<p> <p>
{" Passkey "} {"Prompting for Passkey authentication..."}
</p> </p>
</div> </div>
} }
@ -455,7 +464,8 @@ impl LoginApp {
LoginState::Authenticated => { LoginState::Authenticated => {
let loc = models::pop_return_location(); let loc = models::pop_return_location();
// redirect // redirect
console::log!(format!("authenticated, try going to -> {:?}", loc)); #[cfg(debug)]
console::debug!(format!("authenticated, try going to -> {:?}", loc));
loc.goto(&ctx.link().history().expect_throw("failed to read history")); loc.goto(&ctx.link().history().expect_throw("failed to read history"));
html! { html! {
<div class="container"> <div class="container">
@ -473,9 +483,7 @@ impl LoginApp {
<p><strong>{ "Authentication Denied" }</strong></p> <p><strong>{ "Authentication Denied" }</strong></p>
<p>{ msg.as_str() }</p> <p>{ msg.as_str() }</p>
</div> </div>
<div class="col-md-auto"> { self.button_start_again(ctx) }
<button type="button" class="btn btn-dark" onclick={ ctx.link().callback(|_| LoginAppMsg::Restart) } >{" Start Again "}</button>
</div>
</div> </div>
</div> </div>
} }
@ -487,23 +495,16 @@ impl LoginApp {
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
{ "That username was not found. Please try again!" } { "That username was not found. Please try again!" }
</div> </div>
<div class="col-md-auto"> { self.button_start_again(ctx) }
<button type="button"
class="btn btn-dark"
onclick={ ctx.link().callback(|_| LoginAppMsg::Restart) }
>
{" Start Again "}</button>
</div>
</div> </div>
</div> </div>
} }
} }
LoginState::Error { emsg, kopid } => { LoginState::Error { emsg, kopid } => {
html! { html! {
<div class="container"> <>
<p> <div class="alert alert-danger" role="alert">
{ "An error has occured 😔 " } <h2>{ "An error has occured 😔 " }</h2>
</p>
<p> <p>
{ emsg.as_str() } { emsg.as_str() }
</p> </p>
@ -513,10 +514,14 @@ impl LoginApp {
format!("Operation ID: {}", opid.clone()) format!("Operation ID: {}", opid.clone())
} }
else { else {
"Local Error".to_string() } "Error occurred client-side.".to_string()
}
} }
</p> </p>
</div> </div>
{ self.button_start_again(ctx) }
</>
} }
} }
} }
@ -528,22 +533,27 @@ impl Component for LoginApp {
type Properties = (); type Properties = ();
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("create".to_string()); #[cfg(debug)]
console::debug!("create".to_string());
// Assume we are here for a good reason. // Assume we are here for a good reason.
models::clear_bearer_token(); models::clear_bearer_token();
// Do we have a login hint? // Do we have a login hint?
let inputvalue = models::pop_login_hint().unwrap_or_else(|| "".to_string()); let inputvalue = models::pop_login_hint().unwrap_or_else(|| "".to_string());
// Clean any cookies.
let document = utils::document();
let html_document = document #[cfg(debug)]
.dyn_into::<web_sys::HtmlDocument>() {
.expect_throw("failed to dyn cast to htmldocument"); let document = utils::document();
let cookie = html_document let html_document = document
.cookie() .dyn_into::<web_sys::HtmlDocument>()
.expect_throw("failed to access page cookies"); .expect_throw("failed to dyn cast to htmldocument");
console::log!("cookies".to_string()); let cookie = html_document
console::log!(cookie); .cookie()
.expect_throw("failed to access page cookies");
console::debug!("cookies".to_string());
console::debug!(cookie);
}
// Clean any cookies.
// TODO: actually check that it's cleaning the cookies.
let state = LoginState::Init(true); let state = LoginState::Init(true);
@ -574,7 +584,8 @@ impl Component for LoginApp {
true true
} }
LoginAppMsg::Begin => { LoginAppMsg::Begin => {
console::log!(format!("begin -> {:?}", self.inputvalue)); #[cfg(debug)]
console::debug!(format!("begin -> {:?}", self.inputvalue));
// Disable the button? // Disable the button?
let username = self.inputvalue.clone(); let username = self.inputvalue.clone();
ctx.link().send_future(async { ctx.link().send_future(async {
@ -587,7 +598,8 @@ impl Component for LoginApp {
true true
} }
LoginAppMsg::PasswordSubmit => { LoginAppMsg::PasswordSubmit => {
console::log!("At password step".to_string()); #[cfg(debug)]
console::debug!("At password step".to_string());
// Disable the button? // Disable the button?
self.state = LoginState::Password(false); self.state = LoginState::Password(false);
let authreq = AuthRequest { let authreq = AuthRequest {
@ -605,7 +617,8 @@ impl Component for LoginApp {
true true
} }
LoginAppMsg::BackupCodeSubmit => { LoginAppMsg::BackupCodeSubmit => {
console::log!("backupcode".to_string()); #[cfg(debug)]
console::debug!("backupcode".to_string());
// Disable the button? // Disable the button?
self.state = LoginState::BackupCode(false); self.state = LoginState::BackupCode(false);
let authreq = AuthRequest { let authreq = AuthRequest {
@ -623,7 +636,8 @@ impl Component for LoginApp {
true true
} }
LoginAppMsg::TotpSubmit => { LoginAppMsg::TotpSubmit => {
console::log!("totp".to_string()); #[cfg(debug)]
console::debug!("totp".to_string());
// Disable the button? // Disable the button?
match self.inputvalue.parse::<u32>() { match self.inputvalue.parse::<u32>() {
Ok(totp) => { Ok(totp) => {
@ -650,7 +664,8 @@ impl Component for LoginApp {
true true
} }
LoginAppMsg::SecurityKeySubmit(resp) => { LoginAppMsg::SecurityKeySubmit(resp) => {
console::log!("At securitykey step".to_string()); #[cfg(debug)]
console::debug!("At securitykey step".to_string());
let authreq = AuthRequest { let authreq = AuthRequest {
step: AuthStep::Cred(AuthCredential::SecurityKey(resp)), step: AuthStep::Cred(AuthCredential::SecurityKey(resp)),
}; };
@ -665,7 +680,8 @@ impl Component for LoginApp {
false false
} }
LoginAppMsg::PasskeySubmit(resp) => { LoginAppMsg::PasskeySubmit(resp) => {
console::log!("At passkey step".to_string()); #[cfg(debug)]
console::debug!("At passkey step".to_string());
let authreq = AuthRequest { let authreq = AuthRequest {
step: AuthStep::Cred(AuthCredential::Passkey(resp)), step: AuthStep::Cred(AuthCredential::Passkey(resp)),
}; };
@ -682,7 +698,8 @@ impl Component for LoginApp {
LoginAppMsg::Start(session_id, resp) => { LoginAppMsg::Start(session_id, resp) => {
// Clear any leftover input // Clear any leftover input
self.inputvalue = "".to_string(); self.inputvalue = "".to_string();
console::log!(format!("start -> {:?} : {:?}", resp, session_id)); #[cfg(debug)]
console::debug!(format!("start -> {:?} : {:?}", resp, session_id));
match resp.state { match resp.state {
AuthState::Choose(mut mechs) => { AuthState::Choose(mut mechs) => {
self.session_id = session_id; self.session_id = session_id;
@ -702,18 +719,20 @@ impl Component for LoginApp {
// We do NOT need to change state or redraw // We do NOT need to change state or redraw
false false
} else { } else {
console::log!("multiple mechs exist".to_string()); #[cfg(debug)]
console::debug!("multiple mechs exist".to_string());
self.state = LoginState::Select(mechs); self.state = LoginState::Select(mechs);
true true
} }
} }
AuthState::Denied(reason) => { AuthState::Denied(reason) => {
console::log!(format!("denied -> {:?}", reason)); #[cfg(debug)]
console::debug!(format!("denied -> {:?}", reason));
self.state = LoginState::Denied(reason); self.state = LoginState::Denied(reason);
true true
} }
_ => { _ => {
console::log!("invalid state transition".to_string()); console::error!("invalid state transition".to_string());
self.state = LoginState::Error { self.state = LoginState::Error {
emsg: "Invalid UI State Transition".to_string(), emsg: "Invalid UI State Transition".to_string(),
kopid: None, kopid: None,
@ -723,33 +742,32 @@ impl Component for LoginApp {
} }
} }
LoginAppMsg::Select(idx) => { LoginAppMsg::Select(idx) => {
console::log!(format!("chose -> {:?}", idx)); #[cfg(debug)]
console::debug!(format!("chose -> {:?}", idx));
match &self.state { match &self.state {
LoginState::Select(allowed) => { LoginState::Select(allowed) => match allowed.get(idx) {
match allowed.get(idx) { Some(mech) => {
Some(mech) => { let authreq = AuthRequest {
let authreq = AuthRequest { step: AuthStep::Begin(mech.clone()),
step: AuthStep::Begin(mech.clone()), };
}; let session_id = self.session_id.clone();
let session_id = self.session_id.clone(); ctx.link().send_future(async {
ctx.link().send_future(async { match Self::auth_step(authreq, session_id).await {
match Self::auth_step(authreq, session_id).await { Ok(v) => v,
Ok(v) => v, Err(v) => v.into(),
Err(v) => v.into(), }
} });
});
}
None => {
console::log!("invalid allowed mech idx".to_string());
self.state = LoginState::Error {
emsg: "Invalid Continue Index".to_string(),
kopid: None,
};
}
} }
} None => {
console::error!("invalid allowed mech idx".to_string());
self.state = LoginState::Error {
emsg: "Invalid Continue Index".to_string(),
kopid: None,
};
}
},
_ => { _ => {
console::log!("invalid state transition".to_string()); console::error!("invalid state transition".to_string());
self.state = LoginState::Error { self.state = LoginState::Error {
emsg: "Invalid UI State Transition".to_string(), emsg: "Invalid UI State Transition".to_string(),
kopid: None, kopid: None,
@ -761,12 +779,13 @@ impl Component for LoginApp {
LoginAppMsg::Next(resp) => { LoginAppMsg::Next(resp) => {
// Clear any leftover input // Clear any leftover input
self.inputvalue = "".to_string(); self.inputvalue = "".to_string();
console::log!(format!("next -> {:?}", resp)); #[cfg(debug)]
console::debug!(format!("next -> {:?}", resp));
// Based on the state we have, we need to chose our steps. // Based on the state we have, we need to chose our steps.
match resp.state { match resp.state {
AuthState::Choose(_mechs) => { AuthState::Choose(_mechs) => {
console::log!("invalid state transition".to_string()); console::error!("invalid state transition".to_string());
self.state = LoginState::Error { self.state = LoginState::Error {
emsg: "Invalid UI State Transition".to_string(), emsg: "Invalid UI State Transition".to_string(),
kopid: None, kopid: None,
@ -799,13 +818,14 @@ impl Component for LoginApp {
} }
} else { } else {
// Else, present the options in a choice. // Else, present the options in a choice.
console::log!("multiple choices exist".to_string()); #[cfg(debug)]
console::debug!("multiple choices exist".to_string());
self.state = LoginState::Continue(allowed); self.state = LoginState::Continue(allowed);
} }
true true
} }
AuthState::Denied(reason) => { AuthState::Denied(reason) => {
console::log!(format!("denied -> {:?}", reason)); console::error!(format!("denied -> {:?}", reason));
self.state = LoginState::Denied(reason); self.state = LoginState::Denied(reason);
true true
} }
@ -819,7 +839,8 @@ impl Component for LoginApp {
} }
LoginAppMsg::Continue(idx) => { LoginAppMsg::Continue(idx) => {
// Are we in the correct internal state? // Are we in the correct internal state?
console::log!(format!("chose -> {:?}", idx)); #[cfg(debug)]
console::debug!(format!("chose -> {:?}", idx));
match &self.state { match &self.state {
LoginState::Continue(allowed) => { LoginState::Continue(allowed) => {
match allowed.get(idx) { match allowed.get(idx) {
@ -843,7 +864,7 @@ impl Component for LoginApp {
self.state = LoginState::Passkey(challenge.clone().into()) self.state = LoginState::Passkey(challenge.clone().into())
} }
None => { None => {
console::log!("invalid allowed mech idx".to_string()); console::error!("invalid allowed mech idx".to_string());
self.state = LoginState::Error { self.state = LoginState::Error {
emsg: "Invalid Continue Index".to_string(), emsg: "Invalid Continue Index".to_string(),
kopid: None, kopid: None,
@ -852,7 +873,7 @@ impl Component for LoginApp {
} }
} }
_ => { _ => {
console::log!("invalid state transition".to_string()); console::error!("invalid state transition".to_string());
self.state = LoginState::Error { self.state = LoginState::Error {
emsg: "Invalid UI State Transition".to_string(), emsg: "Invalid UI State Transition".to_string(),
kopid: None, kopid: None,
@ -864,14 +885,14 @@ impl Component for LoginApp {
LoginAppMsg::UnknownUser => { LoginAppMsg::UnknownUser => {
// Clear any leftover input // Clear any leftover input
self.inputvalue = "".to_string(); self.inputvalue = "".to_string();
console::log!("Unknown user".to_string()); console::warn!("Unknown user".to_string());
self.state = LoginState::UnknownUser; self.state = LoginState::UnknownUser;
true true
} }
LoginAppMsg::Error { emsg, kopid } => { LoginAppMsg::Error { emsg, kopid } => {
// Clear any leftover input // Clear any leftover input
self.inputvalue = "".to_string(); self.inputvalue = "".to_string();
console::log!(format!("error -> {:?}, {:?}", emsg, kopid)); console::error!(format!("error -> {:?}, {:?}", emsg, kopid));
self.state = LoginState::Error { emsg, kopid }; self.state = LoginState::Error { emsg, kopid };
true true
} }
@ -879,7 +900,8 @@ impl Component for LoginApp {
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
console::log!("login::view".to_string()); #[cfg(debug)]
console::debug!("login::view".to_string());
// How do we add a top level theme? // How do we add a top level theme?
/* /*
let (width, height): (u32, u32) = if let Some(win) = web_sys::window() { let (width, height): (u32, u32) = if let Some(win) = web_sys::window() {
@ -911,22 +933,19 @@ impl Component for LoginApp {
</center> </center>
{ self.view_state(ctx) } { self.view_state(ctx) }
</main> </main>
<footer class="footer mt-auto py-3 bg-light text-end"> { crate::utils::do_footer() }
<div class="container">
<span class="text-muted">{ "Powered by " }<a href="https://kanidm.com">{ "Kanidm" }</a></span>
</div>
</footer>
</> </>
} }
} }
fn destroy(&mut self, _ctx: &Context<Self>) { fn destroy(&mut self, _ctx: &Context<Self>) {
console::log!("login::destroy".to_string()); #[cfg(debug)]
console::debug!("login::destroy".to_string());
remove_body_form_classes!(); remove_body_form_classes!();
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
crate::utils::autofocus(); #[cfg(debug)]
console::log!("login::rendered".to_string()); console::debug!("login::rendered".to_string());
} }
} }

View file

@ -6,7 +6,7 @@ macro_rules! add_body_form_classes {
() => { () => {
for x in $crate::constants::CSS_CLASSES_BODY_FORM { for x in $crate::constants::CSS_CLASSES_BODY_FORM {
if let Err(e) = $crate::utils::body().class_list().add_1(x) { if let Err(e) = $crate::utils::body().class_list().add_1(x) {
console::log!(format!("class_list add error -> {:?}", e)); console::error!(format!("class_list add error -> {:?}", e));
}; };
} }
}; };
@ -18,7 +18,7 @@ macro_rules! remove_body_form_classes {
() => { () => {
for x in $crate::constants::CSS_CLASSES_BODY_FORM { for x in $crate::constants::CSS_CLASSES_BODY_FORM {
if let Err(e) = $crate::utils::body().class_list().remove_1(x) { if let Err(e) = $crate::utils::body().class_list().remove_1(x) {
console::log!(format!("class_list removal error -> {:?}", e)); console::error!(format!("class_list removal error -> {:?}", e));
}; };
} }
}; };

View file

@ -4,6 +4,7 @@
//! not atuhenticated, this will determine that and send you to authentication first, then //! not atuhenticated, this will determine that and send you to authentication first, then
//! will allow you to proceed with the oauth flow. //! will allow you to proceed with the oauth flow.
#[cfg(debug)]
use gloo::console; use gloo::console;
use wasm_bindgen::UnwrapThrowExt; use wasm_bindgen::UnwrapThrowExt;
use yew::functional::*; use yew::functional::*;
@ -49,7 +50,8 @@ fn landing() -> Html {
} }
fn switch(route: &Route) -> Html { fn switch(route: &Route) -> Html {
console::log!("manager::switch"); #[cfg(debug)]
console::debug!("manager::switch");
match route { match route {
Route::Landing => html! { <Landing /> }, Route::Landing => html! { <Landing /> },
Route::Login => html! { <LoginApp /> }, Route::Login => html! { <LoginApp /> },
@ -76,24 +78,28 @@ impl Component for ManagerApp {
type Properties = (); type Properties = ();
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("manager::create"); #[cfg(debug)]
console::debug!("manager::create");
ManagerApp {} ManagerApp {}
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("manager::change"); #[cfg(debug)]
console::debug!("manager::change");
false false
} }
fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
console::log!("manager::update"); #[cfg(debug)]
console::debug!("manager::update");
true true
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("manager::rendered"); #[cfg(debug)]
console::debug!("manager::rendered");
// Can only access the current_route AFTER it renders. // Can only access the current_route AFTER it renders.
// console::log!(format!("{:?}", yew_router::current_route::<Route>()).as_str()) // console::debug!(format!("{:?}", yew_router::current_route::<Route>()).as_str())
} }
fn view(&self, _ctx: &Context<Self>) -> Html { fn view(&self, _ctx: &Context<Self>) -> Html {

View file

@ -15,7 +15,8 @@ use kanidm_proto::v1::{CUSessionToken, CUStatus};
pub fn get_bearer_token() -> Option<String> { pub fn get_bearer_token() -> Option<String> {
let prev_session: Result<String, _> = PersistentStorage::get("kanidm_bearer_token"); let prev_session: Result<String, _> = PersistentStorage::get("kanidm_bearer_token");
console::log!(format!("kanidm_bearer_token -> {:?}", prev_session).as_str()); #[cfg(debug)]
console::debug!(format!("kanidm_bearer_token -> {:?}", prev_session).as_str());
prev_session.ok() prev_session.ok()
} }
@ -45,34 +46,36 @@ impl Location {
} }
pub fn push_return_location(l: Location) { pub fn push_return_location(l: Location) {
TemporaryStorage::set("return_location", l).expect_throw("failed to set header"); TemporaryStorage::set("return_location", l)
.expect_throw("failed to set return_location in temporary storage");
} }
pub fn pop_return_location() -> Location { pub fn pop_return_location() -> Location {
let l: Result<Location, _> = TemporaryStorage::get("return_location"); let l: Result<Location, _> = TemporaryStorage::get("return_location");
console::log!(format!("return_location -> {:?}", l).as_str()); console::debug!(format!("return_location -> {:?}", l).as_str());
TemporaryStorage::delete("return_location"); TemporaryStorage::delete("return_location");
l.unwrap_or(Location::Manager(Route::Landing)) l.unwrap_or(Location::Manager(Route::Landing))
} }
pub fn push_oauth2_authorisation_request(r: AuthorisationRequest) { pub fn push_oauth2_authorisation_request(r: AuthorisationRequest) {
TemporaryStorage::set("oauth2_authorisation_request", r).expect_throw("failed to set header"); TemporaryStorage::set("oauth2_authorisation_request", r)
.expect_throw("failed to set oauth2_authorisation_request in temporary storage");
} }
pub fn pop_oauth2_authorisation_request() -> Option<AuthorisationRequest> { pub fn pop_oauth2_authorisation_request() -> Option<AuthorisationRequest> {
let l: Result<AuthorisationRequest, _> = TemporaryStorage::get("oauth2_authorisation_request"); let l: Result<AuthorisationRequest, _> = TemporaryStorage::get("oauth2_authorisation_request");
console::log!(format!("oauth2_authorisation_request -> {:?}", l).as_str()); console::debug!(format!("oauth2_authorisation_request -> {:?}", l).as_str());
TemporaryStorage::delete("oauth2_authorisation_request"); TemporaryStorage::delete("oauth2_authorisation_request");
l.ok() l.ok()
} }
pub fn push_login_hint(r: String) { pub fn push_login_hint(r: String) {
TemporaryStorage::set("login_hint", r).expect_throw("failed to set header"); TemporaryStorage::set("login_hint", r).expect_throw("failed to set login hint");
} }
pub fn pop_login_hint() -> Option<String> { pub fn pop_login_hint() -> Option<String> {
let l: Result<String, _> = TemporaryStorage::get("login_hint"); let l: Result<String, _> = TemporaryStorage::get("login_hint");
console::log!(format!("login_hint -> {:?}", l).as_str()); console::debug!(format!("login_hint::pop_login_hint -> {:?}", l).as_str());
TemporaryStorage::delete("login_hint"); TemporaryStorage::delete("login_hint");
l.ok() l.ok()
} }

View file

@ -33,7 +33,7 @@ enum State {
pii_scopes: Vec<String>, pii_scopes: Vec<String>,
consent_token: String, consent_token: String,
}, },
ConsentGranted, ConsentGranted(String),
ErrInvalidRequest, ErrInvalidRequest,
} }
@ -43,7 +43,7 @@ pub struct Oauth2App {
pub enum Oauth2Msg { pub enum Oauth2Msg {
LoginProceed, LoginProceed,
ConsentGranted, ConsentGranted(String),
TokenValid, TokenValid,
Consent { Consent {
client_name: String, client_name: String,
@ -139,7 +139,7 @@ impl Oauth2App {
.into_serde() .into_serde()
.map_err(|e| { .map_err(|e| {
let e_msg = format!("serde error -> {:?}", e); let e_msg = format!("serde error -> {:?}", e);
console::log!(e_msg.as_str()); console::error!(e_msg.as_str());
}) })
.expect_throw("Invalid response type"); .expect_throw("Invalid response type");
match state { match state {
@ -226,7 +226,8 @@ impl Component for Oauth2App {
type Properties = (); type Properties = ();
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
console::log!("oauth2::create"); #[cfg(debug)]
console::debug!("oauth2::create");
// Do we have a query here? // Do we have a query here?
// Did we get sent a valid Oauth2 request? // Did we get sent a valid Oauth2 request?
@ -239,11 +240,11 @@ impl Component for Oauth2App {
.query() .query()
.map_err(|e| { .map_err(|e| {
let e_msg = format!("lstorage error -> {:?}", e); let e_msg = format!("lstorage error -> {:?}", e);
console::log!(e_msg.as_str()); console::error!(e_msg.as_str());
}) })
.ok() .ok()
.or_else(|| { .or_else(|| {
console::log!("pop_oauth2_authorisation_request"); console::error!("pop_oauth2_authorisation_request");
models::pop_oauth2_authorisation_request() models::pop_oauth2_authorisation_request()
}); });
@ -260,7 +261,7 @@ impl Component for Oauth2App {
}; };
let e_msg = format!("{:?}", query); let e_msg = format!("{:?}", query);
console::log!(e_msg.as_str()); console::error!(e_msg.as_str());
// In the query, if this is openid there MAY be a hint // In the query, if this is openid there MAY be a hint
// as to the users name. // as to the users name.
@ -295,12 +296,14 @@ impl Component for Oauth2App {
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("oauth2::change"); #[cfg(debug)]
console::debug!("oauth2::change");
false false
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("oauth2::update"); #[cfg(debug)]
console::debug!("oauth2::update");
match msg { match msg {
Oauth2Msg::LoginProceed => { Oauth2Msg::LoginProceed => {
@ -329,7 +332,7 @@ impl Component for Oauth2App {
State::SubmitAuthReq(token.clone()) State::SubmitAuthReq(token.clone())
} }
_ => { _ => {
console::log!("Invalid state transition"); console::error!("Invalid state transition");
State::ErrInvalidRequest State::ErrInvalidRequest
} }
}; };
@ -350,17 +353,18 @@ impl Component for Oauth2App {
consent_token, consent_token,
}, },
_ => { _ => {
console::log!("Invalid state transition"); console::error!("Invalid state transition");
State::ErrInvalidRequest State::ErrInvalidRequest
} }
}; };
true true
} }
Oauth2Msg::ConsentGranted => { Oauth2Msg::ConsentGranted(_) => {
self.state = match &self.state { self.state = match &self.state {
State::Consent { State::Consent {
token, token,
consent_token, consent_token,
client_name,
.. ..
} => { } => {
let token_c = token.clone(); let token_c = token.clone();
@ -371,10 +375,10 @@ impl Component for Oauth2App {
Err(v) => v.into(), Err(v) => v.into(),
} }
}); });
State::ConsentGranted State::ConsentGranted(client_name.to_string())
} }
_ => { _ => {
console::log!("Invalid state transition"); console::error!("Invalid state transition");
State::ErrInvalidRequest State::ErrInvalidRequest
} }
}; };
@ -383,12 +387,13 @@ impl Component for Oauth2App {
} }
Oauth2Msg::Error { emsg, kopid } => { Oauth2Msg::Error { emsg, kopid } => {
self.state = State::ErrInvalidRequest; self.state = State::ErrInvalidRequest;
console::log!(format!("{:?}", kopid).as_str()); console::error!(format!("{:?}", kopid).as_str());
console::log!(emsg.as_str()); console::error!(emsg.as_str());
true true
} }
Oauth2Msg::Redirect(loc) => { Oauth2Msg::Redirect(loc) => {
console::log!(format!("Redirecting to {}", loc).as_str()); #[cfg(debug)]
console::debug!(format!("Redirecting to {}", loc).as_str());
// Send the location here, and then update will trigger the redir via // Send the location here, and then update will trigger the redir via
// https://docs.rs/web-sys/0.3.51/web_sys/struct.Location.html#method.replace // https://docs.rs/web-sys/0.3.51/web_sys/struct.Location.html#method.replace
// see https://developer.mozilla.org/en-US/docs/Web/API/Location/replace // see https://developer.mozilla.org/en-US/docs/Web/API/Location/replace
@ -400,7 +405,7 @@ impl Component for Oauth2App {
Ok(_) => false, Ok(_) => false,
Err(e) => { Err(e) => {
// Something went bang, opps. // Something went bang, opps.
console::log!(format!("{:?}", e).as_str()); console::error!(format!("{:?}", e).as_str());
self.state = State::ErrInvalidRequest; self.state = State::ErrInvalidRequest;
true true
} }
@ -410,30 +415,35 @@ impl Component for Oauth2App {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
crate::utils::autofocus(); #[cfg(debug)]
console::log!("oauth2::rendered"); console::debug!("oauth2::rendered");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
console::log!("oauth2::view"); #[cfg(debug)]
match &self.state { console::debug!("oauth2::view");
let body_content = match &self.state {
State::LoginRequired => { State::LoginRequired => {
// <body class="html-body form-body"> // <body class="html-body form-body">
html! { html! {
<main class="form-signin">
<form <form
onsubmit={ ctx.link().callback(|e: FocusEvent| { onsubmit={ ctx.link().callback(|e: FocusEvent| {
console::log!("oauth2::view -> LoginRequired - prevent_default()"); console::debug!("oauth2::view -> LoginRequired - prevent_default()");
e.prevent_default(); e.prevent_default();
Oauth2Msg::LoginProceed Oauth2Msg::LoginProceed
} ) } } ) }
action="javascript:void(0);" action="javascript:void(0);"
> >
<h1 class="h3 mb-3 fw-normal">{" Sign in to proceed" }</h1> <h1 class="h3 mb-3 fw-normal">
<button id="autofocus" class="w-100 btn btn-lg btn-primary" type="submit">{ "Sign in" }</button> // TODO: include the domain display name here
{"Sign in to proceed" }
</h1>
<button autofocus=true class="w-100 btn btn-lg btn-primary" type="submit">
{ "Sign in" }
</button>
</form> </form>
</main>
} }
} }
State::Consent { State::Consent {
@ -448,54 +458,86 @@ impl Component for Oauth2App {
let pii_req = if pii_scopes.is_empty() { let pii_req = if pii_scopes.is_empty() {
html! { html! {
<div> <div>
<p>{ "This site will not have access to your personal information" }</p> <p>{ "This site will not have access to your personal information." }</p>
<p>{ "If this site requests personal information in the future we will check with you" }</p> <p>{ "If this site requests personal information in the future we will check with you." }</p>
</div> </div>
} }
} else { } else {
html! { html! {
<div> <div>
<p>{ "This site has requested to see the following personal information" }</p> <p>{ "This site has requested to see the following personal information." }</p>
<ul> <ul>
{ {
pii_scopes.iter().map(|s| html! { <li>{ s }</li> } ).collect::<Html>() pii_scopes.iter().map(|s| html! { <li>{ s }</li> } ).collect::<Html>()
} }
</ul> </ul>
<p>{ "If this site requests different personal information in the future we will check with you again" }</p> <p>{ "If this site requests different personal information in the future we will check with you again." }</p>
</div> </div>
} }
}; };
// <body class="html-body form-body"> // <body class="html-body form-body">
let app_name = client_name.clone();
html! { html! {
<main class="form-signin">
<form <form
onsubmit={ ctx.link().callback(|e: FocusEvent| { onsubmit={ ctx.link().callback(move |e: FocusEvent| {
console::log!("oauth2::view -> Consent - prevent_default()"); console::debug!("oauth2::view -> Consent - prevent_default()");
e.prevent_default(); e.prevent_default();
Oauth2Msg::ConsentGranted Oauth2Msg::ConsentGranted(format!("{}", client_name))
} ) } } ) }
action="javascript:void(0);" action="javascript:void(0);"
> >
<h2 class="h3 mb-3 fw-normal">{"Consent to Proceed to " }{ client_name }</h2> <h2 class="h3 mb-3 fw-normal">{"Consent to Proceed to " }{ app_name }</h2>
{ pii_req } { pii_req }
<button id="autofocus" class="w-100 btn btn-lg btn-primary" type="submit">{ "Proceed" }</button> <div class="text-center">
<button autofocus=true class="w-100 btn btn-lg btn-primary" type="submit">{ "Proceed" }</button>
</div>
</form> </form>
</main>
} }
} }
State::ConsentGranted | State::SubmitAuthReq(_) | State::TokenCheck(_) => { State::ConsentGranted(app_name) => {
html! { <div> <h1>{ " ... " }</h1> </div> } html! {
<div class="alert alert-success" role="alert">
<h2 class="text-center">{ "Taking you to " }{app_name}{" ... " }</h2>
</div>
}
}
State::SubmitAuthReq(_) | State::TokenCheck(_) => {
html! {
<div class="alert alert-light" role="alert">
<h2 class="text-center">{ "Processing ... " }</h2>
</div>
}
} }
State::ErrInvalidRequest => { State::ErrInvalidRequest => {
html! { <div> <h1>{ "" }</h1> </div> } html! {
<div class="alert alert-danger" role="alert">
<h1>{ "Invalid request" } </h1>
<p>
{ "Please close this window and try again again from the beginning." }
</p>
</div>
}
} }
};
html! {
<>
<main class="form-signin">
<center>
<img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/>
</center>
<div class="container">
{ body_content }
</div>
</main>
{ crate::utils::do_footer() }
</>
} }
} }
fn destroy(&mut self, _ctx: &Context<Self>) { fn destroy(&mut self, _ctx: &Context<Self>) {
console::log!("oauth2::destroy"); console::debug!("oauth2::destroy");
remove_body_form_classes!(); remove_body_form_classes!();
} }
} }

View file

@ -3,6 +3,8 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen::{JsCast, UnwrapThrowExt}; use wasm_bindgen::{JsCast, UnwrapThrowExt};
pub use web_sys::InputEvent; pub use web_sys::InputEvent;
use web_sys::{Document, Event, /*HtmlButtonElement,*/ HtmlElement, HtmlInputElement, Window}; use web_sys::{Document, Event, /*HtmlButtonElement,*/ HtmlElement, HtmlInputElement, Window};
use yew::html;
use yew::virtual_dom::VNode;
pub fn window() -> Window { pub fn window() -> Window {
web_sys::window().expect_throw("Unable to retrieve window") web_sys::window().expect_throw("Unable to retrieve window")
@ -18,13 +20,16 @@ pub fn body() -> HtmlElement {
document().body().expect_throw("Unable to retrieve body") document().body().expect_throw("Unable to retrieve body")
} }
pub fn autofocus() { pub fn autofocus(target: &str) {
// Once rendered if an element with id autofocus exists, focus it. // If an element with an id attribute matching 'target' exists, focus it.
let doc = document(); let doc = document();
if let Some(element) = doc.get_element_by_id("autofocus") { if let Some(element) = doc.get_element_by_id(target) {
if let Ok(htmlelement) = element.dyn_into::<web_sys::HtmlElement>() { if let Ok(htmlelement) = element.dyn_into::<web_sys::HtmlElement>() {
if htmlelement.focus().is_err() { if htmlelement.focus().is_err() {
console::log!("unable to autofocus."); console::warn!(
"unable to autofocus element, couldn't find target with id '{}'",
target
);
} }
} }
} }
@ -66,3 +71,14 @@ pub fn get_value_from_element_id(id: &str) -> Option<String> {
extern "C" { extern "C" {
pub fn modal_hide_by_id(m: &str); pub fn modal_hide_by_id(m: &str);
} }
/// Returns the footer node for the UI
pub fn do_footer() -> VNode {
html! {
<footer class="footer mt-auto py-3 bg-light text-end">
<div class="container">
<span class="text-muted">{ "Powered by " }<a href="https://kanidm.com">{ "Kanidm" }</a></span>
</div>
</footer>
}
}

View file

@ -1,3 +1,4 @@
#[cfg(debug)]
use gloo::console; use gloo::console;
use yew::prelude::*; use yew::prelude::*;
@ -12,17 +13,20 @@ impl Component for AppsApp {
type Properties = (); type Properties = ();
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("views::apps::create"); #[cfg(debug)]
console::debug!("views::apps::create");
AppsApp {} AppsApp {}
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("views::apps::changed"); #[cfg(debug)]
console::debug!("views::apps::changed");
false false
} }
fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
console::log!("views::apps::update"); #[cfg(debug)]
console::debug!("views::apps::update");
/* /*
match msg { match msg {
ViewsMsg::Logout => { ViewsMsg::Logout => {
@ -33,7 +37,8 @@ impl Component for AppsApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("views::apps::rendered"); #[cfg(debug)]
console::debug!("views::apps::rendered");
} }
fn view(&self, _ctx: &Context<Self>) -> Html { fn view(&self, _ctx: &Context<Self>) -> Html {

View file

@ -1,6 +1,7 @@
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::*;
@ -70,7 +71,8 @@ impl From<FetchError> for ViewsMsg {
} }
fn switch(route: &ViewRoute) -> Html { fn switch(route: &ViewRoute) -> Html {
console::log!("views::switch"); #[cfg(debug)]
console::debug!("views::switch");
// safety - can't panic because to get to this location we MUST be authenticated! // safety - can't panic because to get to this location we MUST be authenticated!
let token = let token =
@ -91,7 +93,8 @@ impl Component for ViewsApp {
type Properties = (); type Properties = ();
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
console::log!("views::create"); #[cfg(debug)]
console::debug!("views::create");
// Ensure the token is valid before we proceed. Could be // Ensure the token is valid before we proceed. Could be
// due to a session expiry or something else, but we want to make // due to a session expiry or something else, but we want to make
@ -115,12 +118,14 @@ impl Component for ViewsApp {
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("views::changed"); #[cfg(debug)]
console::debug!("views::changed");
false false
} }
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("views::update"); #[cfg(debug)]
console::debug!("views::update");
match msg { match msg {
ViewsMsg::Verified(token) => { ViewsMsg::Verified(token) => {
self.state = State::Authenticated(token); self.state = State::Authenticated(token);
@ -139,7 +144,8 @@ impl Component for ViewsApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("views::rendered"); #[cfg(debug)]
console::debug!("views::rendered");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
@ -177,19 +183,20 @@ impl Component for ViewsApp {
State::Error { emsg, kopid } => { State::Error { emsg, kopid } => {
html! { html! {
<main class="form-signin"> <main class="form-signin">
<div class="container"> <div class="alert alert-danger" role="alert">
<h2>{ "An Error Occured 🥺" }</h2> <h2>{ "An Error Occured 🥺" }</h2>
</div>
<p>{ emsg.to_string() }</p> <p>{ emsg.to_string() }</p>
<p> <p>
{ {
if let Some(opid) = kopid.as_ref() { if let Some(opid) = kopid.as_ref() {
format!("Operation ID: {}", opid) format!("Operation ID: {}", opid)
} else { } else {
"Local Error".to_string() "Error occurred client-side.".to_string()
} }
} }
</p> </p>
</div>
</main> </main>
} }
} }
@ -209,8 +216,6 @@ impl ViewsApp {
<button class="navbar-toggler bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<img src="/pkg/img/favicon.png" /> <img src="/pkg/img/favicon.png" />
</button> </button>
<div class="collapse navbar-collapse" id="navbarCollapse"> <div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0"> <ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="mb-1"> <li class="mb-1">

View file

@ -23,17 +23,20 @@ impl Component for ProfileApp {
type Properties = ViewProps; type Properties = ViewProps;
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("views::profile::create"); #[cfg(debug)]
console::debug!("views::profile::create");
ProfileApp {} ProfileApp {}
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("views::profile::changed"); #[cfg(debug)]
console::debug!("views::profile::changed");
false false
} }
fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool { fn update(&mut self, _ctx: &Context<Self>, _msg: Self::Message) -> bool {
console::log!("views::profile::update"); #[cfg(debug)]
console::debug!("views::profile::update");
/* /*
match msg { match msg {
ViewsMsg::Logout => { ViewsMsg::Logout => {
@ -44,17 +47,20 @@ impl Component for ProfileApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("views::profile::rendered"); #[cfg(debug)]
console::debug!("views::profile::rendered");
} }
/// 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 {
console::log!("views::profile::starting view"); #[cfg(debug)]
console::debug!("views::profile::starting view");
// Submit a req to init the session. // Submit a req to init the session.
// The uuid we want to submit against - hint, it's us. // The uuid we want to submit against - hint, it's us.
let token = ctx.props().token.clone(); let token = ctx.props().token.clone();
console::log!("token: ", &token); #[cfg(debug)]
console::debug!("token: ", &token);
let jwtu = JwsUnverified::from_str(&token).expect_throw("Invalid UAT, unable to parse"); let jwtu = JwsUnverified::from_str(&token).expect_throw("Invalid UAT, unable to parse");
@ -63,15 +69,14 @@ impl Component for ProfileApp {
.expect_throw("Unvalid UAT, unable to release "); .expect_throw("Unvalid UAT, unable to release ");
let id = uat.inner.uuid.to_string(); let id = uat.inner.uuid.to_string();
console::debug!("uuid:", id);
console::log!("uuid:", id);
// let valid_token = ctx.link().send_future(async { // let valid_token = ctx.link().send_future(async {
// match Self::fetch_token_valid(id, token).await { // match Self::fetch_token_valid(id, token).await {
// Ok(v) => v, // Ok(v) => v,
// Err(v) => v.into(), // Err(v) => v.into(),
// } // }
// }); // });
// console::log!("valid_token: {:?}"); // console::debug!("valid_token: {:?}");
html! { html! {
<> <>

View file

@ -6,6 +6,7 @@ use crate::manager::Route;
use crate::views::{ViewProps, ViewRoute}; use crate::views::{ViewProps, ViewRoute};
use compact_jwt::{Jws, JwsUnverified}; use compact_jwt::{Jws, JwsUnverified};
#[cfg(debug)]
use gloo::console; use gloo::console;
use std::str::FromStr; use std::str::FromStr;
use yew::prelude::*; use yew::prelude::*;
@ -56,17 +57,20 @@ impl Component for SecurityApp {
type Properties = ViewProps; type Properties = ViewProps;
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
console::log!("views::security::create"); #[cfg(debug)]
console::debug!("views::security::create");
SecurityApp { state: State::Init } SecurityApp { state: State::Init }
} }
fn changed(&mut self, _ctx: &Context<Self>) -> bool { fn changed(&mut self, _ctx: &Context<Self>) -> bool {
console::log!("views::security::changed"); #[cfg(debug)]
console::debug!("views::security::changed");
false false
} }
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
console::log!("views::security::update"); #[cfg(debug)]
console::debug!("views::security::update");
match msg { match msg {
Msg::RequestCredentialUpdate => { Msg::RequestCredentialUpdate => {
// Submit a req to init the session. // Submit a req to init the session.
@ -113,7 +117,8 @@ impl Component for SecurityApp {
} }
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
console::log!("views::security::rendered"); #[cfg(debug)]
console::debug!("views::security::rendered");
} }
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {

View file

@ -6,10 +6,9 @@ async function main() {
} }
main() main()
// this is used in // this is used in modals
export function modal_hide_by_id(m) { export function modal_hide_by_id(m) {
var elem = document.getElementById(m); var elem = document.getElementById(m);
var modal = bootstrap.Modal.getInstance(elem); var modal = bootstrap.Modal.getInstance(elem);
modal.hide(); modal.hide();
}; };