feat(wasm): expose generate_recovery_qr and unwrap_recovery_qr bindings

This commit is contained in:
adlee-was-taken
2026-05-03 20:57:55 -04:00
parent 42b746f9af
commit ada00895d4

View File

@@ -486,6 +486,42 @@ pub fn parse_lastpass_csv_json(csv_bytes: &[u8]) -> Result<String, JsError> {
Ok(json.to_string()) Ok(json.to_string())
} }
// ── Recovery QR bindings ─────────────────────────────────────────────────────
use relicario_core::{generate_recovery_qr, recovery_qr_to_svg, unwrap_recovery_qr};
/// Generate a recovery QR SVG for the current session.
/// Returns the SVG string. The passphrase wraps the image_secret under a
/// separate key (domain-separated from the master key derivation).
#[wasm_bindgen]
pub fn wasm_generate_recovery_qr(
handle: &SessionHandle,
passphrase: &str,
) -> Result<String, JsError> {
let image_secret_bytes = session::with_image_secret(handle.0, |s| s.to_vec())
.ok_or_else(|| JsError::new("invalid or locked session handle"))?;
let image_secret: &[u8; 32] = image_secret_bytes.as_slice().try_into()
.map_err(|_| JsError::new("image_secret must be 32 bytes"))?;
let payload = generate_recovery_qr(passphrase, image_secret)
.map_err(|e| JsError::new(&e.to_string()))?;
Ok(recovery_qr_to_svg(&payload))
}
/// Unwrap a recovery QR payload (base64-encoded 109-byte blob) using the passphrase.
/// Returns the raw image_secret bytes (32 bytes).
#[wasm_bindgen]
pub fn wasm_unwrap_recovery_qr(
payload_b64: &str,
passphrase: &str,
) -> Result<Vec<u8>, JsError> {
use base64::{engine::general_purpose::STANDARD, Engine};
let bytes = STANDARD.decode(payload_b64)
.map_err(|e| JsError::new(&format!("base64: {e}")))?;
let recovered = unwrap_recovery_qr(&bytes, passphrase)
.map_err(|e| JsError::new(&e.to_string()))?;
Ok(recovered.to_vec())
}
#[cfg(test)] #[cfg(test)]
mod session_tests { mod session_tests {
use super::*; use super::*;