feat(wasm): pack_backup_json / unpack_backup_json
JSON bridge for the SW. Binary fields are base64 in the JSON wrapper; core gets borrowed byte slices.
This commit is contained in:
@@ -305,6 +305,133 @@ pub fn totp_compute(
|
||||
Ok(TotpCode { code, expires_at })
|
||||
}
|
||||
|
||||
// ── Backup container bridge ─────────────────────────────────────────────────
|
||||
|
||||
use relicario_core::backup::{
|
||||
pack_backup as core_pack_backup,
|
||||
unpack_backup as core_unpack_backup,
|
||||
BackupInput, BackupItem, BackupAttachment,
|
||||
};
|
||||
|
||||
/// Pack a vault into a `.relbak` byte vector.
|
||||
///
|
||||
/// `input_json` shape:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "salt": "<base64>",
|
||||
/// "params_json": "...",
|
||||
/// "devices_json": "...",
|
||||
/// "manifest_enc": "<base64>",
|
||||
/// "settings_enc": "<base64>",
|
||||
/// "items": [{"id": "<hex>", "ciphertext": "<base64>"}, ...],
|
||||
/// "attachments": [{"item_id": "<hex>", "attachment_id": "<hex>", "ciphertext": "<base64>"}, ...],
|
||||
/// "reference_jpg": "<base64>" | null,
|
||||
/// "git_archive": "<base64>" | null
|
||||
/// }
|
||||
/// ```
|
||||
#[wasm_bindgen]
|
||||
pub fn pack_backup_json(input_json: &str, passphrase: &str) -> Result<Vec<u8>, JsError> {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct InJson {
|
||||
salt: String,
|
||||
params_json: String,
|
||||
devices_json: String,
|
||||
manifest_enc: String,
|
||||
settings_enc: String,
|
||||
items: Vec<InItem>,
|
||||
attachments: Vec<InAttachment>,
|
||||
reference_jpg: Option<String>,
|
||||
git_archive: Option<String>,
|
||||
}
|
||||
#[derive(serde::Deserialize)]
|
||||
struct InItem { id: String, ciphertext: String }
|
||||
#[derive(serde::Deserialize)]
|
||||
struct InAttachment { item_id: String, attachment_id: String, ciphertext: String }
|
||||
|
||||
let parsed: InJson = serde_json::from_str(input_json)
|
||||
.map_err(|e| JsError::new(&format!("backup input: {e}")))?;
|
||||
|
||||
let b64 = base64::engine::general_purpose::STANDARD;
|
||||
let salt = b64.decode(&parsed.salt).map_err(|e| JsError::new(&e.to_string()))?;
|
||||
let manifest = b64.decode(&parsed.manifest_enc).map_err(|e| JsError::new(&e.to_string()))?;
|
||||
let settings = b64.decode(&parsed.settings_enc).map_err(|e| JsError::new(&e.to_string()))?;
|
||||
let items_bytes: Vec<(String, Vec<u8>)> = parsed.items.iter()
|
||||
.map(|i| {
|
||||
let ct = b64.decode(&i.ciphertext).map_err(|e| JsError::new(&e.to_string()))?;
|
||||
Ok((i.id.clone(), ct))
|
||||
})
|
||||
.collect::<Result<Vec<_>, JsError>>()?;
|
||||
let attach_bytes: Vec<(String, String, Vec<u8>)> = parsed.attachments.iter()
|
||||
.map(|a| {
|
||||
let ct = b64.decode(&a.ciphertext).map_err(|e| JsError::new(&e.to_string()))?;
|
||||
Ok((a.item_id.clone(), a.attachment_id.clone(), ct))
|
||||
})
|
||||
.collect::<Result<Vec<_>, JsError>>()?;
|
||||
|
||||
let ref_bytes = parsed.reference_jpg.as_deref()
|
||||
.map(|s| b64.decode(s))
|
||||
.transpose()
|
||||
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||
let git_bytes = parsed.git_archive.as_deref()
|
||||
.map(|s| b64.decode(s))
|
||||
.transpose()
|
||||
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||
|
||||
let items_refs: Vec<BackupItem> = items_bytes.iter()
|
||||
.map(|(id, ct)| BackupItem { id: id.clone(), ciphertext: ct })
|
||||
.collect();
|
||||
let attach_refs: Vec<BackupAttachment> = attach_bytes.iter()
|
||||
.map(|(iid, aid, ct)| BackupAttachment {
|
||||
item_id: iid.clone(),
|
||||
attachment_id: aid.clone(),
|
||||
ciphertext: ct,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let input = BackupInput {
|
||||
salt: &salt,
|
||||
params_json: &parsed.params_json,
|
||||
devices_json: &parsed.devices_json,
|
||||
manifest_enc: &manifest,
|
||||
settings_enc: &settings,
|
||||
items: items_refs,
|
||||
attachments: attach_refs,
|
||||
reference_jpg: ref_bytes.as_deref(),
|
||||
git_archive: git_bytes.as_deref(),
|
||||
};
|
||||
core_pack_backup(input, passphrase).map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Unpack `.relbak` bytes; returns the JSON shape that mirrors `BackupOutput`,
|
||||
/// with binary fields base64-encoded.
|
||||
#[wasm_bindgen]
|
||||
pub fn unpack_backup_json(bytes: &[u8], passphrase: &str) -> Result<String, JsError> {
|
||||
let out = core_unpack_backup(bytes, passphrase)
|
||||
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||
|
||||
let b64 = base64::engine::general_purpose::STANDARD;
|
||||
let json = serde_json::json!({
|
||||
"salt": b64.encode(out.salt),
|
||||
"params_json": out.params_json,
|
||||
"devices_json": out.devices_json,
|
||||
"manifest_enc": b64.encode(&out.manifest_enc),
|
||||
"settings_enc": b64.encode(&out.settings_enc),
|
||||
"items": out.items.iter().map(|i| serde_json::json!({
|
||||
"id": i.id,
|
||||
"ciphertext": b64.encode(&i.ciphertext),
|
||||
})).collect::<Vec<_>>(),
|
||||
"attachments": out.attachments.iter().map(|a| serde_json::json!({
|
||||
"item_id": a.item_id,
|
||||
"attachment_id": a.attachment_id,
|
||||
"ciphertext": b64.encode(&a.ciphertext),
|
||||
})).collect::<Vec<_>>(),
|
||||
"reference_jpg": out.reference_jpg.as_ref().map(|b| b64.encode(b)),
|
||||
"git_archive": out.git_archive.as_ref().map(|b| b64.encode(b)),
|
||||
"created_at": out.created_at,
|
||||
});
|
||||
Ok(json.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod session_tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user