Add SelfFreezoneClient wrapper for Self components
- Created SelfFreezoneClient in Self components
- Wraps SDK FreezoneScriptClient for Self-specific operations
- Implements send_verification_email method
- Uses Rhai script template for email verification
- Includes template variable substitution
- Added serde-wasm-bindgen dependency
Usage:
let client = SelfFreezoneClient::builder()
.supervisor_url("http://localhost:8080")
.secret("my-secret")
.build()?;
client.send_verification_email(
"user@example.com",
"123456",
"https://verify.com/abc"
).await?;
This commit is contained in:
@@ -9,7 +9,10 @@ crate-type = ["cdylib"]
|
||||
[dependencies]
|
||||
self-components = { path = "../components" }
|
||||
yew = { workspace = true, features = ["csr"] }
|
||||
yew-router = "0.18"
|
||||
wasm-bindgen = { workspace = true }
|
||||
wasm-bindgen-futures = { workspace = true }
|
||||
web-sys = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
6
app/dist/index.html
vendored
6
app/dist/index.html
vendored
@@ -105,7 +105,7 @@
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
</style>
|
||||
<link rel="modulepreload" href="/self-app-ea91d85454088543.js" crossorigin="anonymous" integrity="sha384-LtoStOLfGudTKD5QLifQMTi8an1aLEqMXZ1KhnQoVNPjpl8QZyBepu9D2sElroww"><link rel="preload" href="/self-app-ea91d85454088543_bg.wasm" crossorigin="anonymous" integrity="sha384-Ms0zVmkquqd/C5lI7dHomP8BqQnZQkKfzd+xAPLra7T1Z8ZAH59VCtXnUxWzzriT" as="fetch" type="application/wasm"></head>
|
||||
<link rel="modulepreload" href="/self-app-b25013f584ee50e5.js" crossorigin="anonymous" integrity="sha384-1u18adVyfNdbwYr68l3Y+7ogP5yHPkK1snT5bpYfKGB/90poijA0I51N9oeEWm2E"><link rel="preload" href="/self-app-b25013f584ee50e5_bg.wasm" crossorigin="anonymous" integrity="sha384-sJLOBlXwGx/LmAiZtmypjzSbazD/ES2YkNdRfDm+qHUmAk4OJ6S7jIdZ5q9iVApF" as="fetch" type="application/wasm"></head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -113,8 +113,8 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script type="module">
|
||||
import init, * as bindings from '/self-app-ea91d85454088543.js';
|
||||
const wasm = await init({ module_or_path: '/self-app-ea91d85454088543_bg.wasm' });
|
||||
import init, * as bindings from '/self-app-b25013f584ee50e5.js';
|
||||
const wasm = await init({ module_or_path: '/self-app-b25013f584ee50e5_bg.wasm' });
|
||||
|
||||
|
||||
window.wasmBindings = bindings;
|
||||
|
||||
@@ -125,6 +125,11 @@ function passStringToWasm0(arg, malloc, realloc) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function getArrayU8FromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
|
||||
function debugString(val) {
|
||||
// primitive types
|
||||
const type = typeof val;
|
||||
@@ -198,28 +203,6 @@ state => {
|
||||
}
|
||||
);
|
||||
|
||||
function makeClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
try {
|
||||
return f(state.a, state.b, ...args);
|
||||
} finally {
|
||||
if (--state.cnt === 0) {
|
||||
wasm.__wbindgen_export_7.get(state.dtor)(state.a, state.b); state.a = 0;
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
}
|
||||
}
|
||||
};
|
||||
real.original = state;
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
}
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
@@ -246,28 +229,134 @@ function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
return real;
|
||||
}
|
||||
|
||||
function makeClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
try {
|
||||
return f(state.a, state.b, ...args);
|
||||
} finally {
|
||||
if (--state.cnt === 0) {
|
||||
wasm.__wbindgen_export_7.get(state.dtor)(state.a, state.b); state.a = 0;
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
}
|
||||
}
|
||||
};
|
||||
real.original = state;
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
}
|
||||
|
||||
export function run_app() {
|
||||
wasm.run_app();
|
||||
}
|
||||
|
||||
function takeFromExternrefTable0(idx) {
|
||||
const value = wasm.__wbindgen_export_2.get(idx);
|
||||
wasm.__externref_table_dealloc(idx);
|
||||
return value;
|
||||
}
|
||||
function __wbg_adapter_6(arg0, arg1, arg2) {
|
||||
wasm.closure293_externref_shim(arg0, arg1, arg2);
|
||||
wasm.closure538_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_9(arg0, arg1, arg2) {
|
||||
wasm.closure223_externref_shim(arg0, arg1, arg2);
|
||||
wasm.closure608_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_14(arg0, arg1, arg2) {
|
||||
wasm.closure286_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_17(arg0, arg1) {
|
||||
wasm.wasm_bindgen__convert__closures_____invoke__h5eb5ae71b8ec87bb(arg0, arg1);
|
||||
function __wbg_adapter_16(arg0, arg1, arg2) {
|
||||
wasm.closure604_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
const __wbindgen_enum_RequestMode = ["same-origin", "no-cors", "cors", "navigate"];
|
||||
|
||||
const VaultJsFinalization = (typeof FinalizationRegistry === 'undefined')
|
||||
? { register: () => {}, unregister: () => {} }
|
||||
: new FinalizationRegistry(ptr => wasm.__wbg_vaultjs_free(ptr >>> 0, 1));
|
||||
|
||||
export class VaultJs {
|
||||
|
||||
__destroy_into_raw() {
|
||||
const ptr = this.__wbg_ptr;
|
||||
this.__wbg_ptr = 0;
|
||||
VaultJsFinalization.unregister(this);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
free() {
|
||||
const ptr = this.__destroy_into_raw();
|
||||
wasm.__wbg_vaultjs_free(ptr, 0);
|
||||
}
|
||||
/**
|
||||
* @param {string} private_key
|
||||
* @param {string} password
|
||||
*/
|
||||
static storePrivateKey(private_key, password) {
|
||||
const ptr0 = passStringToWasm0(private_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.vaultjs_storePrivateKey(ptr0, len0, ptr1, len1);
|
||||
if (ret[1]) {
|
||||
throw takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} password
|
||||
* @returns {string}
|
||||
*/
|
||||
static retrievePrivateKey(password) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.vaultjs_retrievePrivateKey(ptr0, len0);
|
||||
var ptr2 = ret[0];
|
||||
var len2 = ret[1];
|
||||
if (ret[3]) {
|
||||
ptr2 = 0; len2 = 0;
|
||||
throw takeFromExternrefTable0(ret[2]);
|
||||
}
|
||||
deferred3_0 = ptr2;
|
||||
deferred3_1 = len2;
|
||||
return getStringFromWasm0(ptr2, len2);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static hasStoredKey() {
|
||||
const ret = wasm.vaultjs_hasStoredKey();
|
||||
return ret !== 0;
|
||||
}
|
||||
static clearStoredKey() {
|
||||
const ret = wasm.vaultjs_clearStoredKey();
|
||||
if (ret[1]) {
|
||||
throw takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} password
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static verifyPassword(password) {
|
||||
const ptr0 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.vaultjs_verifyPassword(ptr0, len0);
|
||||
if (ret[2]) {
|
||||
throw takeFromExternrefTable0(ret[1]);
|
||||
}
|
||||
return ret[0] !== 0;
|
||||
}
|
||||
}
|
||||
|
||||
const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']);
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
@@ -325,6 +414,10 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.call(arg1);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_call_f53f0647ceb9c567 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = arg0.call(arg1, arg2);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_cancelBubble_ba3496c52eac50f9 = function(arg0) {
|
||||
const ret = arg0.cancelBubble;
|
||||
return ret;
|
||||
@@ -333,10 +426,6 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.childNodes;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_clearTimeout_5a54f8841c30079a = function(arg0) {
|
||||
const ret = clearTimeout(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_clipboard_cb646efa8bab83fa = function(arg0) {
|
||||
const ret = arg0.clipboard;
|
||||
return ret;
|
||||
@@ -364,6 +453,10 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.createTextNode(getStringFromWasm0(arg1, arg2));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_crypto_574e78ad8b13b65f = function(arg0) {
|
||||
const ret = arg0.crypto;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_data_d1e564c046e31ed9 = function(arg0) {
|
||||
const ret = arg0.data;
|
||||
return ret;
|
||||
@@ -399,6 +492,16 @@ function __wbg_get_imports() {
|
||||
const ret = Array.from(arg0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_getItem_7083c7e8090b4e20 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
const ret = arg1.getItem(getStringFromWasm0(arg2, arg3));
|
||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len1 = WASM_VECTOR_LEN;
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_getRandomValues_b8f5dbd5f3995a9e = function() { return handleError(function (arg0, arg1) {
|
||||
arg0.getRandomValues(arg1);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_get_59c6316d15f9f1d0 = function(arg0, arg1) {
|
||||
const ret = arg0[arg1 >>> 0];
|
||||
return ret;
|
||||
@@ -421,6 +524,16 @@ function __wbg_get_imports() {
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Response_0ab386c6818f788a = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = arg0 instanceof Response;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_ShadowRoot_ae4bc62016938ece = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
@@ -445,6 +558,10 @@ function __wbg_get_imports() {
|
||||
const ret = Object.is(arg0, arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_json_dbaa85a926a80ed5 = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.json();
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_lastChild_5847fcd93bd5162a = function(arg0) {
|
||||
const ret = arg0.lastChild;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
@@ -453,13 +570,25 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_length_904c0910ed998bf3 = function(arg0) {
|
||||
const ret = arg0.length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_listenerid_ed1678830a5b97ec = function(arg0) {
|
||||
const ret = arg0.__yew_listener_id;
|
||||
return isLikeNone(ret) ? 0x100000001 : (ret) >>> 0;
|
||||
};
|
||||
imports.wbg.__wbg_localStorage_3e7df12e18c45ecc = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.localStorage;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_log_f3c04200b995730f = function(arg0) {
|
||||
console.log(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) {
|
||||
const ret = arg0.msCrypto;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_namespaceURI_16a9ca763a61b64a = function(arg0, arg1) {
|
||||
const ret = arg1.namespaceURI;
|
||||
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
@@ -471,6 +600,10 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.navigator;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new0_85cc856927102294 = function() {
|
||||
const ret = new Date();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_12588505388d0897 = function() { return handleError(function () {
|
||||
const ret = new Headers();
|
||||
return ret;
|
||||
@@ -491,6 +624,10 @@ function __wbg_get_imports() {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newwithlength_ed0ee6c1edca86fc = function(arg0) {
|
||||
const ret = new Uint8Array(arg0 >>> 0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newwithstrandinit_e8e22e9851f3c2fe = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = new Request(getStringFromWasm0(arg0, arg1), arg2);
|
||||
return ret;
|
||||
@@ -499,6 +636,18 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.nextSibling;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_node_905d3e251edff8a2 = function(arg0) {
|
||||
const ret = arg0.node;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_now_e3057dd824ca0191 = function() {
|
||||
const ret = Date.now();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_ok_3f777ee8a1c6baeb = function(arg0) {
|
||||
const ret = arg0.ok;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_outerHTML_7306ec658b1ed630 = function(arg0, arg1) {
|
||||
const ret = arg1.outerHTML;
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
@@ -514,6 +663,16 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.parentNode;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_preventDefault_9f90a0a7802591fd = function(arg0) {
|
||||
arg0.preventDefault();
|
||||
};
|
||||
imports.wbg.__wbg_process_dc0fbacc7c1c06f7 = function(arg0) {
|
||||
const ret = arg0.process;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_prototypesetcall_c5f74efd31aea86b = function(arg0, arg1, arg2) {
|
||||
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
||||
};
|
||||
imports.wbg.__wbg_queueMicrotask_bcc6e26d899696db = function(arg0) {
|
||||
const ret = arg0.queueMicrotask;
|
||||
return ret;
|
||||
@@ -521,6 +680,9 @@ function __wbg_get_imports() {
|
||||
imports.wbg.__wbg_queueMicrotask_f24a794d09c42640 = function(arg0) {
|
||||
queueMicrotask(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) {
|
||||
arg0.randomFillSync(arg1);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_removeAttribute_6930c6c8a4db23d2 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
arg0.removeAttribute(getStringFromWasm0(arg1, arg2));
|
||||
}, arguments) };
|
||||
@@ -531,6 +693,13 @@ function __wbg_get_imports() {
|
||||
imports.wbg.__wbg_removeEventListener_b25c194da9564efa = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
arg0.removeEventListener(getStringFromWasm0(arg1, arg2), arg3, arg4 !== 0);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_removeItem_3accb1e04073ade4 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
arg0.removeItem(getStringFromWasm0(arg1, arg2));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () {
|
||||
const ret = module.require;
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_resolve_5775c0ef9222f556 = function(arg0) {
|
||||
const ret = Promise.resolve(arg0);
|
||||
return ret;
|
||||
@@ -538,9 +707,8 @@ function __wbg_get_imports() {
|
||||
imports.wbg.__wbg_setAttribute_6a3ee9b5deb88ed3 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
arg0.setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_setTimeout_db2dbaeefb6f39c7 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = setTimeout(arg0, arg1);
|
||||
return ret;
|
||||
imports.wbg.__wbg_setItem_328cbcd44fb5d487 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
arg0.setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_set_2df374478acad331 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
arg0.set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
@@ -617,6 +785,18 @@ function __wbg_get_imports() {
|
||||
const ret = typeof window === 'undefined' ? null : window;
|
||||
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
||||
};
|
||||
imports.wbg.__wbg_status_31874648c8651949 = function(arg0) {
|
||||
const ret = arg0.status;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_stringify_1f41b6198e0932e0 = function() { return handleError(function (arg0) {
|
||||
const ret = JSON.stringify(arg0);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_subarray_a219824899e59712 = function(arg0, arg1, arg2) {
|
||||
const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_subtreeid_e65dfcc52d403fd9 = function(arg0) {
|
||||
const ret = arg0.__yew_subtree_id;
|
||||
return isLikeNone(ret) ? 0x100000001 : (ret) >>> 0;
|
||||
@@ -632,6 +812,10 @@ function __wbg_get_imports() {
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg_text_42c080764c927da6 = function() { return handleError(function (arg0) {
|
||||
const ret = arg0.text();
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_then_8d2fcccde5380a03 = function(arg0, arg1, arg2) {
|
||||
const ret = arg0.then(arg1, arg2);
|
||||
return ret;
|
||||
@@ -640,6 +824,10 @@ function __wbg_get_imports() {
|
||||
const ret = arg0.then(arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_toISOString_61f131e920833685 = function(arg0) {
|
||||
const ret = arg0.toISOString();
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_value_1ae15635193fdbb5 = function(arg0, arg1) {
|
||||
const ret = arg1.value;
|
||||
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
@@ -654,6 +842,10 @@ function __wbg_get_imports() {
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg_versions_c01dfd4722a88165 = function(arg0) {
|
||||
const ret = arg0.versions;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_wbindgencbdrop_a85ed476c6a370b9 = function(arg0) {
|
||||
const obj = arg0.original;
|
||||
if (obj.cnt-- == 1) {
|
||||
@@ -674,6 +866,15 @@ function __wbg_get_imports() {
|
||||
const ret = typeof(arg0) === 'function';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_wbindgenisobject_dfe064a121d87553 = function(arg0) {
|
||||
const val = arg0;
|
||||
const ret = typeof(val) === 'object' && val !== null;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_wbindgenisstring_4b74e4111ba029e6 = function(arg0) {
|
||||
const ret = typeof(arg0) === 'string';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_wbindgenisundefined_71f08a6ade4354e7 = function(arg0) {
|
||||
const ret = arg0 === undefined;
|
||||
return ret;
|
||||
@@ -698,24 +899,24 @@ function __wbg_get_imports() {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_33ed6ce34b165f3b = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 222, function: Function { arguments: [Ref(NamedExternref("Event"))], shim_idx: 223, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, 222, __wbg_adapter_9);
|
||||
imports.wbg.__wbindgen_cast_4d7be94769e4ba26 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 603, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 604, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, 603, __wbg_adapter_16);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_52db8efac9e5598a = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 292, function: Function { arguments: [Externref], shim_idx: 293, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, 292, __wbg_adapter_6);
|
||||
imports.wbg.__wbindgen_cast_96deb231e670e363 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 537, function: Function { arguments: [Ref(NamedExternref("Event"))], shim_idx: 538, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, 537, __wbg_adapter_6);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_8783041643bd3db7 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 285, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 286, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, 285, __wbg_adapter_14);
|
||||
imports.wbg.__wbindgen_cast_cb9088102bce6b30 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Ref(Slice(U8)) -> NamedExternref("Uint8Array")`.
|
||||
const ret = getArrayU8FromWasm0(arg0, arg1);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_8b1432b6c48212a6 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 287, function: Function { arguments: [], shim_idx: 288, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, 287, __wbg_adapter_17);
|
||||
imports.wbg.__wbindgen_cast_edabf52bd3bd4133 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 607, function: Function { arguments: [Externref], shim_idx: 608, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, 607, __wbg_adapter_9);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_init_externref_table = function() {
|
||||
BIN
app/dist/self-app-b25013f584ee50e5_bg.wasm
vendored
Normal file
BIN
app/dist/self-app-b25013f584ee50e5_bg.wasm
vendored
Normal file
Binary file not shown.
BIN
app/dist/self-app-ea91d85454088543_bg.wasm
vendored
BIN
app/dist/self-app-ea91d85454088543_bg.wasm
vendored
Binary file not shown.
412
app/src/lib.rs
412
app/src/lib.rs
@@ -1,44 +1,398 @@
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use self_components::{Registration, RegistrationConfig};
|
||||
use self_components::{Registration, RegistrationConfig, Login, LoginConfig, Identity, IdentityConfig, IdentityData, Sign, SignConfig, VaultManager, VaultConfig};
|
||||
|
||||
#[function_component(App)]
|
||||
fn app() -> Html {
|
||||
let registration_complete = Callback::from(|data: (String, String)| {
|
||||
let (email, public_key) = data;
|
||||
web_sys::console::log_1(&format!("Registration completed for {} with public key: {}", email, public_key).into());
|
||||
});
|
||||
mod pages;
|
||||
use pages::{Landing, Documentation, AppPage};
|
||||
|
||||
let server_url = option_env!("SERVER_URL")
|
||||
.unwrap_or("http://localhost:8080")
|
||||
.to_string();
|
||||
|
||||
let config = RegistrationConfig {
|
||||
server_url,
|
||||
app_name: "Self-Sovereign Identity".to_string(),
|
||||
};
|
||||
#[derive(Clone, Routable, PartialEq)]
|
||||
enum Route {
|
||||
#[at("/")]
|
||||
Home,
|
||||
#[at("/docs")]
|
||||
Docs,
|
||||
#[at("/docs/:section")]
|
||||
DocsSection { section: String },
|
||||
#[at("/app")]
|
||||
App,
|
||||
#[at("/app/:view")]
|
||||
AppView { view: String },
|
||||
#[not_found]
|
||||
#[at("/404")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
html! {
|
||||
<div class="app-container" style="min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2rem 0;">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-8">
|
||||
<div class="text-center mb-4">
|
||||
<h1 class="display-4 text-white mb-3">{"Self"}</h1>
|
||||
<p class="lead text-white-50">{"Sovereign Entity Local Framework"}</p>
|
||||
#[derive(Clone, PartialEq)]
|
||||
enum AppView {
|
||||
Login,
|
||||
Register,
|
||||
Identity,
|
||||
Sign,
|
||||
Vault,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
current_view: AppView,
|
||||
user_identity: Option<IdentityData>,
|
||||
jwt_token: Option<String>,
|
||||
}
|
||||
|
||||
pub enum AppMsg {
|
||||
ShowLogin,
|
||||
ShowRegister,
|
||||
ShowIdentity,
|
||||
ShowSign,
|
||||
ShowVault,
|
||||
LoginSuccess(String), // JWT token
|
||||
RegistrationSuccess(String, String), // (email, public_key) - registration still uses this
|
||||
IdentityFetched(IdentityData),
|
||||
IdentityFetchFailed(String),
|
||||
Logout,
|
||||
}
|
||||
|
||||
impl Component for App {
|
||||
type Message = AppMsg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
current_view: AppView::Login,
|
||||
user_identity: None,
|
||||
jwt_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
AppMsg::ShowLogin => {
|
||||
self.current_view = AppView::Login;
|
||||
true
|
||||
}
|
||||
AppMsg::ShowRegister => {
|
||||
self.current_view = AppView::Register;
|
||||
true
|
||||
}
|
||||
AppMsg::ShowIdentity => {
|
||||
self.current_view = AppView::Identity;
|
||||
true
|
||||
}
|
||||
AppMsg::ShowSign => {
|
||||
self.current_view = AppView::Sign;
|
||||
true
|
||||
}
|
||||
AppMsg::ShowVault => {
|
||||
self.current_view = AppView::Vault;
|
||||
true
|
||||
}
|
||||
AppMsg::LoginSuccess(jwt_token) => {
|
||||
self.jwt_token = Some(jwt_token.clone());
|
||||
// Fetch user identity from server using JWT token
|
||||
self.fetch_user_identity(_ctx, jwt_token);
|
||||
true
|
||||
}
|
||||
AppMsg::IdentityFetched(identity_data) => {
|
||||
self.user_identity = Some(identity_data);
|
||||
true
|
||||
}
|
||||
AppMsg::IdentityFetchFailed(error) => {
|
||||
web_sys::console::log_1(&format!("Failed to fetch identity: {}", error).into());
|
||||
// Clear JWT token on failure
|
||||
self.jwt_token = None;
|
||||
true
|
||||
}
|
||||
AppMsg::RegistrationSuccess(email, public_key) => {
|
||||
self.user_identity = Some(IdentityData {
|
||||
email,
|
||||
public_key: public_key.clone(),
|
||||
name: "User".to_string(),
|
||||
created_at: None,
|
||||
});
|
||||
// Stay on register view instead of auto-redirecting
|
||||
true
|
||||
}
|
||||
AppMsg::Logout => {
|
||||
self.user_identity = None;
|
||||
self.jwt_token = None;
|
||||
self.current_view = AppView::Login;
|
||||
|
||||
// Clear JWT token from localStorage
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Ok(Some(storage)) = window.local_storage() {
|
||||
let _ = storage.remove_item("jwt_token");
|
||||
web_sys::console::log_1(&"JWT token cleared from localStorage".into());
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let _link = ctx.link();
|
||||
|
||||
html! {
|
||||
<div class="app-container" style="min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||
{self.render_navbar(ctx)}
|
||||
<div style="padding: 2rem 0;">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-8">
|
||||
<div class="text-center mb-4">
|
||||
<h1 class="display-4 text-white mb-3">{"Self"}</h1>
|
||||
<p class="lead text-white-50">{"Sovereign Entity Local Framework"}</p>
|
||||
</div>
|
||||
|
||||
{self.render_current_view(ctx)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Registration
|
||||
config={config}
|
||||
on_complete={registration_complete}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn render_navbar(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
|
||||
html! {
|
||||
<nav class="navbar navbar-expand-lg" style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px);">
|
||||
<div class="container">
|
||||
<a class="navbar-brand text-white fw-bold" href="#">
|
||||
<i class="bi bi-shield-check me-2"></i>
|
||||
{"Self"}
|
||||
</a>
|
||||
|
||||
<div class="navbar-nav ms-auto">
|
||||
{if self.jwt_token.is_some() {
|
||||
// Show authenticated user navigation
|
||||
html! {
|
||||
<>
|
||||
<button type="button"
|
||||
class={format!("btn me-2 {}", if self.current_view == AppView::Identity { "btn-light" } else { "btn-outline-light" })}
|
||||
onclick={link.callback(|_| AppMsg::ShowIdentity)}>
|
||||
<i class="bi bi-person-badge me-1"></i>
|
||||
{"Identity"}
|
||||
</button>
|
||||
<button type="button"
|
||||
class={format!("btn me-2 {}", if self.current_view == AppView::Vault { "btn-light" } else { "btn-outline-light" })}
|
||||
onclick={link.callback(|_| AppMsg::ShowVault)}>
|
||||
<i class="bi bi-shield-lock me-1"></i>
|
||||
{"Vault"}
|
||||
</button>
|
||||
<button type="button"
|
||||
class={format!("btn me-2 {}", if self.current_view == AppView::Sign { "btn-light" } else { "btn-outline-light" })}
|
||||
onclick={link.callback(|_| AppMsg::ShowSign)}>
|
||||
<i class="bi bi-pen me-1"></i>
|
||||
{"Sign"}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-outline-danger"
|
||||
onclick={link.callback(|_| AppMsg::Logout)}>
|
||||
<i class="bi bi-box-arrow-right me-1"></i>
|
||||
{"Logout"}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
} else {
|
||||
// Show unauthenticated user navigation
|
||||
html! {
|
||||
<>
|
||||
<button type="button"
|
||||
class={format!("btn me-2 {}", if self.current_view == AppView::Login { "btn-light" } else { "btn-outline-light" })}
|
||||
onclick={link.callback(|_| AppMsg::ShowLogin)}>
|
||||
<i class="bi bi-box-arrow-in-right me-1"></i>
|
||||
{"Login"}
|
||||
</button>
|
||||
<button type="button"
|
||||
class={format!("btn {}", if self.current_view == AppView::Register { "btn-light" } else { "btn-outline-light" })}
|
||||
onclick={link.callback(|_| AppMsg::ShowRegister)}>
|
||||
<i class="bi bi-person-plus me-1"></i>
|
||||
{"Register"}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
fn render_current_view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
let server_url = option_env!("SERVER_URL")
|
||||
.unwrap_or("http://localhost:8080")
|
||||
.to_string();
|
||||
|
||||
match self.current_view {
|
||||
AppView::Login => {
|
||||
let login_config = LoginConfig {
|
||||
server_url,
|
||||
};
|
||||
|
||||
html! {
|
||||
<Login
|
||||
config={login_config}
|
||||
on_login_success={link.callback(|jwt_token| AppMsg::LoginSuccess(jwt_token))}
|
||||
/>
|
||||
}
|
||||
}
|
||||
AppView::Register => {
|
||||
let registration_config = RegistrationConfig {
|
||||
server_url,
|
||||
app_name: "Self-Sovereign Identity".to_string(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<Registration
|
||||
config={registration_config}
|
||||
on_complete={link.callback(|(email, public_key)| AppMsg::RegistrationSuccess(email, public_key))}
|
||||
/>
|
||||
}
|
||||
}
|
||||
AppView::Identity => {
|
||||
if let Some(identity_data) = &self.user_identity {
|
||||
let identity_config = IdentityConfig {
|
||||
server_url,
|
||||
app_name: "Self-Sovereign Identity".to_string(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<Identity
|
||||
config={identity_config}
|
||||
on_logout={link.callback(|_| AppMsg::Logout)}
|
||||
/>
|
||||
}
|
||||
} else {
|
||||
// Fallback to login if no identity data
|
||||
html! { <div>{"Error: No identity data available"}</div> }
|
||||
}
|
||||
}
|
||||
AppView::Sign => {
|
||||
let sign_config = SignConfig {
|
||||
server_url,
|
||||
app_name: "Self-Sovereign Identity".to_string(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<Sign
|
||||
config={sign_config}
|
||||
on_signature_complete={link.callback(|(plaintext, signature)| {
|
||||
web_sys::console::log_1(&format!("Signature created for text: {} -> {}", plaintext, signature).into());
|
||||
AppMsg::ShowSign // Stay on sign view
|
||||
})}
|
||||
/>
|
||||
}
|
||||
}
|
||||
AppView::Vault => {
|
||||
let vault_config = VaultConfig {
|
||||
server_url,
|
||||
app_name: "Self-Sovereign Identity".to_string(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<VaultManager
|
||||
config={vault_config}
|
||||
/>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_user_identity(&self, ctx: &Context<Self>, jwt_token: String) {
|
||||
let server_url = option_env!("SERVER_URL")
|
||||
.unwrap_or("http://localhost:8080")
|
||||
.to_string();
|
||||
let link = ctx.link().clone();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
match Self::fetch_identity_from_server(&server_url, &jwt_token).await {
|
||||
Ok(identity_data) => {
|
||||
link.send_message(AppMsg::IdentityFetched(identity_data));
|
||||
}
|
||||
Err(e) => {
|
||||
link.send_message(AppMsg::IdentityFetchFailed(e));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn fetch_identity_from_server(server_url: &str, jwt_token: &str) -> Result<IdentityData, String> {
|
||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.mode(RequestMode::Cors);
|
||||
|
||||
let headers = js_sys::Object::new();
|
||||
js_sys::Reflect::set(&headers, &"Authorization".into(), &format!("Bearer {}", jwt_token).into()).unwrap();
|
||||
js_sys::Reflect::set(&headers, &"Content-Type".into(), &"application/json".into()).unwrap();
|
||||
opts.headers(&headers);
|
||||
|
||||
let url = format!("{}/oauth/userinfo", server_url);
|
||||
let request = Request::new_with_str_and_init(&url, &opts)
|
||||
.map_err(|_| "Failed to create request")?;
|
||||
|
||||
let window = web_sys::window().ok_or("No window object")?;
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request))
|
||||
.await
|
||||
.map_err(|_| "Network request failed")?;
|
||||
|
||||
let resp: Response = resp_value.dyn_into()
|
||||
.map_err(|_| "Failed to cast response")?;
|
||||
|
||||
if !resp.ok() {
|
||||
return Err(format!("Failed to fetch user info with status: {}", resp.status()));
|
||||
}
|
||||
|
||||
let json = JsFuture::from(resp.json().map_err(|_| "Failed to get response JSON")?)
|
||||
.await
|
||||
.map_err(|_| "Failed to parse JSON response")?;
|
||||
|
||||
let response_text = js_sys::JSON::stringify(&json)
|
||||
.map_err(|_| "Failed to stringify response")?
|
||||
.as_string()
|
||||
.ok_or("Failed to convert response to string")?;
|
||||
|
||||
let user_info: serde_json::Value = serde_json::from_str(&response_text)
|
||||
.map_err(|_| "Failed to parse response JSON")?;
|
||||
|
||||
// Extract user information from OpenID Connect userinfo response
|
||||
let email = user_info["email"]
|
||||
.as_str()
|
||||
.unwrap_or("unknown@example.com")
|
||||
.to_string();
|
||||
|
||||
let name = user_info["name"]
|
||||
.as_str()
|
||||
.unwrap_or("Unknown User")
|
||||
.to_string();
|
||||
|
||||
let public_key = user_info["sub"] // Subject is typically the public key in our case
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let created_at = user_info["created_at"]
|
||||
.as_str()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
Ok(IdentityData {
|
||||
email,
|
||||
name,
|
||||
public_key,
|
||||
created_at,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(start)]
|
||||
pub fn run_app() {
|
||||
yew::Renderer::<App>::new().render();
|
||||
|
||||
279
app/src/pages/landing.rs
Normal file
279
app/src/pages/landing.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
use crate::Route;
|
||||
|
||||
#[function_component(Landing)]
|
||||
pub fn landing() -> Html {
|
||||
let navigator = use_navigator().unwrap();
|
||||
|
||||
let on_get_started = {
|
||||
let navigator = navigator.clone();
|
||||
Callback::from(move |_| {
|
||||
navigator.push(&Route::App);
|
||||
})
|
||||
};
|
||||
|
||||
let on_view_docs = {
|
||||
let navigator = navigator.clone();
|
||||
Callback::from(move |_| {
|
||||
navigator.push(&Route::Docs);
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class="landing-page">
|
||||
// Hero Section
|
||||
<section class="hero-section" style="min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; display: flex; align-items: center;">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 mb-5 mb-lg-0">
|
||||
<div class="hero-content" style="animation: fadeInUp 0.8s ease-out;">
|
||||
<h1 class="display-2 fw-bold mb-4" style="font-size: 4rem;">
|
||||
{"Self"}
|
||||
</h1>
|
||||
<p class="lead mb-4" style="font-size: 1.5rem; opacity: 0.95;">
|
||||
{"Sovereign Entity Local Framework"}
|
||||
</p>
|
||||
<p class="fs-5 mb-5" style="opacity: 0.9; line-height: 1.8;">
|
||||
{"Take control of your digital identity. No passwords, no central authority, just you and your cryptographic keys."}
|
||||
</p>
|
||||
<div class="d-flex gap-3 flex-wrap">
|
||||
<button onclick={on_get_started} class="btn btn-light btn-lg px-5 py-3" style="border-radius: 50px; font-weight: 600; box-shadow: 0 8px 20px rgba(0,0,0,0.2);">
|
||||
<i class="bi bi-rocket-takeoff me-2"></i>
|
||||
{"Get Started"}
|
||||
</button>
|
||||
<button onclick={on_view_docs} class="btn btn-outline-light btn-lg px-5 py-3" style="border-radius: 50px; font-weight: 600; border-width: 2px;">
|
||||
<i class="bi bi-book me-2"></i>
|
||||
{"Documentation"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="hero-visual" style="animation: fadeInUp 0.8s ease-out 0.2s both;">
|
||||
<div class="p-5" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 24px; border: 2px solid rgba(255,255,255,0.2);">
|
||||
<div class="text-center mb-4">
|
||||
<i class="bi bi-shield-check" style="font-size: 6rem; opacity: 0.9;"></i>
|
||||
</div>
|
||||
<div class="d-flex justify-content-around text-center">
|
||||
<div>
|
||||
<i class="bi bi-key-fill fs-2 mb-2 d-block"></i>
|
||||
<small>{"Your Keys"}</small>
|
||||
</div>
|
||||
<div>
|
||||
<i class="bi bi-lock-fill fs-2 mb-2 d-block"></i>
|
||||
<small>{"Your Data"}</small>
|
||||
</div>
|
||||
<div>
|
||||
<i class="bi bi-person-check-fill fs-2 mb-2 d-block"></i>
|
||||
<small>{"Your Identity"}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// Features Section
|
||||
<section class="features-section py-5" style="background: #f8f9fa;">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-4 fw-bold mb-3">{"Why Self?"}</h2>
|
||||
<p class="lead text-muted">{"True digital sovereignty through cryptographic identity"}</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="feature-card card h-100 border-0 shadow-sm" style="transition: transform 0.3s;">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="feature-icon mb-3" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="bi bi-shield-lock-fill text-white" style="font-size: 2.5rem;"></i>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-3">{"Self-Sovereign"}</h4>
|
||||
<p class="text-muted">{"You generate and control your own cryptographic keys. No central authority."}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="feature-card card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="feature-icon mb-3" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="bi bi-fingerprint text-white" style="font-size: 2.5rem;"></i>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-3">{"Passwordless"}</h4>
|
||||
<p class="text-muted">{"Authenticate using digital signatures, not passwords. More secure and convenient."}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="feature-card card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="feature-icon mb-3" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="bi bi-cpu-fill text-white" style="font-size: 2.5rem;"></i>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-3">{"Client-Side Crypto"}</h4>
|
||||
<p class="text-muted">{"All cryptographic operations happen in your browser. Your keys never leave your device."}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="feature-card card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center p-4">
|
||||
<div class="feature-icon mb-3" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); border-radius: 20px; display: flex; align-items: center; justify-content: center;">
|
||||
<i class="bi bi-diagram-3-fill text-white" style="font-size: 2.5rem;"></i>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-3">{"OAuth Compatible"}</h4>
|
||||
<p class="text-muted">{"Works with existing OAuth 2.0 and OpenID Connect infrastructure."}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// How It Works Section
|
||||
<section class="how-it-works-section py-5" style="background: white;">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-4 fw-bold mb-3">{"How It Works"}</h2>
|
||||
<p class="lead text-muted">{"Simple, secure, and sovereign"}</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-5">
|
||||
<div class="col-lg-4">
|
||||
<div class="text-center">
|
||||
<div class="step-number mb-4" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; font-weight: bold;">
|
||||
{"1"}
|
||||
</div>
|
||||
<h4 class="fw-bold mb-3">{"Generate Keys"}</h4>
|
||||
<p class="text-muted">{"Create your cryptographic key pair locally in your browser. Your private key is encrypted with a password you choose."}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="text-center">
|
||||
<div class="step-number mb-4" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; font-weight: bold;">
|
||||
{"2"}
|
||||
</div>
|
||||
<h4 class="fw-bold mb-3">{"Register Identity"}</h4>
|
||||
<p class="text-muted">{"Verify your email and register your public key. The server never sees your private key or password."}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="text-center">
|
||||
<div class="step-number mb-4" style="width: 80px; height: 80px; margin: 0 auto; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; font-weight: bold;">
|
||||
{"3"}
|
||||
</div>
|
||||
<h4 class="fw-bold mb-3">{"Authenticate"}</h4>
|
||||
<p class="text-muted">{"Sign challenges with your private key to prove your identity. No passwords needed."}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// Technology Section
|
||||
<section class="tech-section py-5" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<div class="container py-5">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="display-4 fw-bold mb-3">{"Built with Modern Technology"}</h2>
|
||||
<p class="lead" style="opacity: 0.9;">{"Rust, WebAssembly, and cutting-edge cryptography"}</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 text-center">
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="tech-item p-4" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.2);">
|
||||
<i class="bi bi-code-square fs-1 mb-3 d-block"></i>
|
||||
<h5 class="fw-bold">{"Rust + WASM"}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="tech-item p-4" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.2);">
|
||||
<i class="bi bi-shield-fill-check fs-1 mb-3 d-block"></i>
|
||||
<h5 class="fw-bold">{"AES-256-GCM"}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="tech-item p-4" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.2);">
|
||||
<i class="bi bi-key-fill fs-1 mb-3 d-block"></i>
|
||||
<h5 class="fw-bold">{"Secp256k1"}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-6">
|
||||
<div class="tech-item p-4" style="background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); border-radius: 16px; border: 1px solid rgba(255,255,255,0.2);">
|
||||
<i class="bi bi-diagram-3-fill fs-1 mb-3 d-block"></i>
|
||||
<h5 class="fw-bold">{"OAuth 2.0"}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// CTA Section
|
||||
<section class="cta-section py-5" style="background: #f8f9fa;">
|
||||
<div class="container py-5">
|
||||
<div class="text-center">
|
||||
<h2 class="display-4 fw-bold mb-4">{"Ready to Take Control?"}</h2>
|
||||
<p class="lead text-muted mb-5">{"Start using Self today and experience true digital sovereignty"}</p>
|
||||
<div class="d-flex gap-3 justify-content-center flex-wrap">
|
||||
<button onclick={on_get_started.clone()} class="btn btn-primary btn-lg px-5 py-3" style="border-radius: 50px; font-weight: 600; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none;">
|
||||
<i class="bi bi-rocket-takeoff me-2"></i>
|
||||
{"Launch App"}
|
||||
</button>
|
||||
<a href="https://github.com/herocode/self" target="_blank" class="btn btn-outline-dark btn-lg px-5 py-3" style="border-radius: 50px; font-weight: 600; border-width: 2px;">
|
||||
<i class="bi bi-github me-2"></i>
|
||||
{"View on GitHub"}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
// Footer
|
||||
<footer class="py-4" style="background: #343a40; color: white;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6 text-center text-md-start mb-3 mb-md-0">
|
||||
<p class="mb-0">{"© 2025 Self - Sovereign Entity Local Framework"}</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center text-md-end">
|
||||
<a href="#" class="text-white text-decoration-none me-3">{"Privacy"}</a>
|
||||
<a href="#" class="text-white text-decoration-none me-3">{"Terms"}</a>
|
||||
<a href="https://github.com/herocode/self" target="_blank" class="text-white text-decoration-none">{"GitHub"}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
{r#"
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 12px 24px rgba(0,0,0,0.15) !important;
|
||||
}
|
||||
|
||||
.tech-item:hover {
|
||||
background: rgba(255,255,255,0.2) !important;
|
||||
}
|
||||
"#}
|
||||
</style>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
7
app/src/pages/mod.rs
Normal file
7
app/src/pages/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod landing;
|
||||
mod documentation;
|
||||
mod app_page;
|
||||
|
||||
pub use landing::Landing;
|
||||
pub use documentation::Documentation;
|
||||
pub use app_page::AppPage;
|
||||
Reference in New Issue
Block a user