fix: replace frontend with Rust OPAQUE API + Flutter keypad UI

- Full OPAQUE auth flow via WASM client SDK (client-wasm crate)
- New user: Key Register → Key Login → Code Register (icon selection) → done
- Existing user: Key Login → get login-data → icon keypad → Code Login → done
- Icon-based keypad matching Flutter design:
  - 2 cols portrait, 3 cols landscape
  - Key tiles with 3-col sub-grid of icons
  - Navy border press feedback
  - Dot display with backspace + submit
- SVGs rendered as-is (no color manipulation)
- SusiPage with Login/Signup tabs
- LoginKeypadPage and SignupKeypadPage for code flows
- Secret key display/copy on signup
- Unit tests for Keypad component
- WASM pkg bundled locally (no external dep)
This commit is contained in:
2026-01-29 17:05:32 +00:00
parent 7494bf7520
commit 5c3217e3d5
36 changed files with 2045 additions and 1149 deletions

View File

@@ -96,6 +96,14 @@ function getStringFromWasm0(ptr, len) {
return decodeText(ptr, len);
}
let cachedUint32ArrayMemory0 = null;
function getUint32ArrayMemory0() {
if (cachedUint32ArrayMemory0 === null || cachedUint32ArrayMemory0.byteLength === 0) {
cachedUint32ArrayMemory0 = new Uint32Array(wasm.memory.buffer);
}
return cachedUint32ArrayMemory0;
}
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
@@ -145,6 +153,13 @@ function makeMutClosure(arg0, arg1, dtor, f) {
return real;
}
function passArray32ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 4, 4) >>> 0;
getUint32ArrayMemory0().set(arg, ptr / 4);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1, 1) >>> 0;
getUint8ArrayMemory0().set(arg, ptr / 1);
@@ -189,6 +204,12 @@ function passStringToWasm0(arg, malloc, realloc) {
return ptr;
}
function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_externrefs.get(idx);
wasm.__externref_table_dealloc(idx);
return value;
}
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
const MAX_SAFARI_DECODE_BYTES = 2146435072;
@@ -400,6 +421,56 @@ export class NKodeClient {
const ret = wasm.nkodeclient_updateLoginData(this.__wbg_ptr, ptr0, len0);
return ret;
}
/**
* Decipher key selections into OPAQUE passcode bytes for code login.
* Call this after the user taps their nKode sequence on the keypad.
*
* @param userId - The user's ID
* @param secretKeyHex - The user's secret key as hex
* @param loginDataBytes - Raw JSON bytes of login data (from server)
* @param keySelections - Array of key indices the user tapped
* @returns Uint8Array - The passcode bytes to pass to loginCode()
* @param {string} secret_key_hex
* @param {string} login_data_json
* @param {Uint32Array} key_selections
* @returns {Uint8Array}
*/
decipherSelection(secret_key_hex, login_data_json, key_selections) {
const ptr0 = passStringToWasm0(secret_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(login_data_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passArray32ToWasm0(key_selections, wasm.__wbindgen_malloc);
const len2 = WASM_VECTOR_LEN;
const ret = wasm.nkodeclient_decipherSelection(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2);
if (ret[3]) {
throw takeFromExternrefTable0(ret[2]);
}
var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v4;
}
/**
* Prepare for code login: fetch login data, reconstruct keypad, and fetch icons.
* Returns the keypad configuration with icons for UI display.
*
* Also stores the raw login data JSON internally for decipherSelection().
*
* @param userId - The user's ID
* @param secretKeyHex - The user's secret key as hex string
* @returns Promise<CodeLoginData> - { keypadIndices, propertiesPerKey, numberOfKeys, mask, icons, loginDataJson }
* @param {string} user_id
* @param {string} secret_key_hex
* @returns {Promise<any>}
*/
prepareCodeLogin(user_id, secret_key_hex) {
const ptr0 = passStringToWasm0(user_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(secret_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.nkodeclient_prepareCodeLogin(this.__wbg_ptr, ptr0, len0, ptr1, len1);
return ret;
}
/**
* Generate a new random 16-byte secret key, returned as a hex string (32 chars).
* @returns {string}
@@ -416,6 +487,52 @@ export class NKodeClient {
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
}
}
/**
* Prepare icons for code registration (requires active key session).
* Fetches icons from the server and randomizes their names via ChaCha20.
* Stores intermediate state internally for completeCodeRegistration().
*
* @returns Promise<IconsResponse> - JSON: { icons: [{ file_name, file_type, img_data }] }
* @returns {Promise<any>}
*/
prepareCodeRegistration() {
const ret = wasm.nkodeclient_prepareCodeRegistration(this.__wbg_ptr);
return ret;
}
/**
* Complete code registration after icon selection.
* Enciphers the selection, registers OPAQUE code auth, and stores login data.
*
* @param selectedIndices - Array of icon indices the user selected (global indices, not key indices)
* @returns Promise<void>
* @param {Uint32Array} selected_indices
* @returns {Promise<void>}
*/
completeCodeRegistration(selected_indices) {
const ptr0 = passArray32ToWasm0(selected_indices, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.nkodeclient_completeCodeRegistration(this.__wbg_ptr, ptr0, len0);
return ret;
}
/**
* Complete code registration with email (full version).
* Enciphers the selection, registers OPAQUE code auth, and stores login data on server.
*
* @param email - User's email address
* @param selectedIndices - Uint32Array of icon indices the user selected
* @returns Promise<void>
* @param {string} email
* @param {Uint32Array} selected_indices
* @returns {Promise<void>}
*/
completeCodeRegistrationWithEmail(email, selected_indices) {
const ptr0 = passStringToWasm0(email, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passArray32ToWasm0(selected_indices, wasm.__wbindgen_malloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.nkodeclient_completeCodeRegistrationWithEmail(this.__wbg_ptr, ptr0, len0, ptr1, len1);
return ret;
}
/**
* Create a new client pointed at the given nKode server base URL.
* @param {string} base_url
@@ -567,6 +684,10 @@ export function __wbg_fetch_8119fbf8d0e4f4d1(arg0, arg1) {
return ret;
};
export function __wbg_getRandomValues_1c61fac11405ffdc() { return handleError(function (arg0, arg1) {
globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1));
}, arguments) };
export function __wbg_getRandomValues_b8f5dbd5f3995a9e() { return handleError(function (arg0, arg1) {
arg0.getRandomValues(arg1);
}, arguments) };
@@ -816,12 +937,6 @@ export function __wbg_versions_c01dfd4722a88165(arg0) {
return ret;
};
export function __wbindgen_cast_151ffb1b798ab8ff(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 76, function: Function { arguments: [Externref], shim_idx: 77, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h28b97059ae600264, wasm_bindgen__convert__closures_____invoke__h8f97ce5df83102bb);
return ret;
};
export function __wbindgen_cast_2241b6af4c4b2941(arg0, arg1) {
// Cast intrinsic for `Ref(String) -> Externref`.
const ret = getStringFromWasm0(arg0, arg1);
@@ -852,6 +967,12 @@ export function __wbindgen_cast_d6cd19b81560fd6e(arg0) {
return ret;
};
export function __wbindgen_cast_f2cc0f2a96e2ef5b(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { dtor_idx: 115, function: Function { arguments: [Externref], shim_idx: 116, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h28b97059ae600264, wasm_bindgen__convert__closures_____invoke__h8f97ce5df83102bb);
return ret;
};
export function __wbindgen_init_externref_table() {
const table = wasm.__wbindgen_externrefs;
const offset = table.grow(4);