feat(wasm): add generate_device_keypair + get_field_history bindings
generate_device_keypair returns an ed25519 keypair as JSON with hex pubkey and base64 private key. get_field_history extracts tracked field history from a decrypted item for the popup's history view. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -162,6 +162,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.8.3"
|
version = "1.8.3"
|
||||||
@@ -1557,8 +1563,12 @@ dependencies = [
|
|||||||
name = "relicario-wasm"
|
name = "relicario-wasm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"ed25519-dalek",
|
||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
|
"hex",
|
||||||
"image",
|
"image",
|
||||||
|
"rand",
|
||||||
"relicario-core",
|
"relicario-core",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-wasm-bindgen",
|
"serde-wasm-bindgen",
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ serde_json = "1"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
zeroize = "1"
|
zeroize = "1"
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
|
ed25519-dalek = { version = "2", features = ["rand_core"] }
|
||||||
|
base64 = "0.22"
|
||||||
|
hex = "0.4"
|
||||||
|
rand = "0.8"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3"
|
wasm-bindgen-test = "0.3"
|
||||||
|
|||||||
@@ -196,6 +196,64 @@ pub fn rate_passphrase(p: &str) -> Result<JsValue, JsError> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use ed25519_dalek::SigningKey;
|
||||||
|
use base64::Engine;
|
||||||
|
|
||||||
|
/// Generate an ed25519 keypair for device registration.
|
||||||
|
/// Returns JSON: { "public_key_hex": "...", "private_key_base64": "..." }
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn generate_device_keypair() -> Result<JsValue, JsError> {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let signing_key = SigningKey::generate(&mut rng);
|
||||||
|
let verifying_key = signing_key.verifying_key();
|
||||||
|
|
||||||
|
let public_hex = hex::encode(verifying_key.as_bytes());
|
||||||
|
let private_b64 = base64::engine::general_purpose::STANDARD.encode(signing_key.as_bytes());
|
||||||
|
|
||||||
|
js_value_for(&serde_json::json!({
|
||||||
|
"public_key_hex": public_hex,
|
||||||
|
"private_key_base64": private_b64,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract field history from a decrypted item JSON.
|
||||||
|
/// Returns JSON array of { field_id, field_name, current_value, entries: [{ value, changed_at }] }
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn get_field_history(item_json: &str) -> Result<JsValue, JsError> {
|
||||||
|
let item: Item = serde_json::from_str(item_json)
|
||||||
|
.map_err(|e| JsError::new(&format!("item json: {e}")))?;
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
// Only section fields are tracked in field_history (set_field_value operates on sections).
|
||||||
|
for section in &item.sections {
|
||||||
|
for field in §ion.fields {
|
||||||
|
if field.value.is_history_tracked() {
|
||||||
|
if let Some(entries) = item.field_history.get(&field.id) {
|
||||||
|
if !entries.is_empty() {
|
||||||
|
let current = match &field.value {
|
||||||
|
relicario_core::FieldValue::Password(v) => v.as_str().to_owned(),
|
||||||
|
relicario_core::FieldValue::Concealed(v) => v.as_str().to_owned(),
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
results.push(serde_json::json!({
|
||||||
|
"field_id": field.id.as_str(),
|
||||||
|
"field_name": &field.label,
|
||||||
|
"current_value": current,
|
||||||
|
"entries": entries.iter().map(|e| serde_json::json!({
|
||||||
|
"value": e.value.as_str(),
|
||||||
|
"changed_at": e.replaced_at,
|
||||||
|
})).collect::<Vec<_>>(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
js_value_for(&results)
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn extract_image_secret(image_bytes: &[u8]) -> Result<Vec<u8>, JsError> {
|
pub fn extract_image_secret(image_bytes: &[u8]) -> Result<Vec<u8>, JsError> {
|
||||||
let s = imgsecret::extract(image_bytes).map_err(|e| JsError::new(&e.to_string()))?;
|
let s = imgsecret::extract(image_bytes).map_err(|e| JsError::new(&e.to_string()))?;
|
||||||
|
|||||||
Reference in New Issue
Block a user