feat(wasm,ext): org_manifest_decrypt/encrypt over WASM + faithful OrgManifest TS mirror
- Add `org_manifest_decrypt` + `org_manifest_encrypt` to relicario-wasm/src/lib.rs,
placed after `org_unwrap_key`; mirror the existing manifest_decrypt/manifest_encrypt
pattern exactly (need_key guard, session::with, js_value_for). Core import path:
relicario_core::decrypt_org_manifest / relicario_core::encrypt_org_manifest (crate root).
- Add round-trip test `org_manifest_round_trip_via_handle` in org_tests: encrypt via
WASM wrapper, decrypt via core directly (avoids serde_wasm_bindgen off-wasm).
Asserts collection/title/tags/schema_version survive the round-trip; asserts nonce
uniqueness. TDD: RED (compile error) → GREEN confirmed.
- Declare org_manifest_decrypt/org_manifest_encrypt in extension/src/wasm.d.ts.
- Add faithful/lean OrgManifestEntry + OrgManifest TS interfaces to
extension/src/shared/types.ts: {id, type, title, tags[], modified, trashed_at?,
collection} — no personal-only fields; collection required. Mirrors org.rs:184-202.
- Plan-doc: rewrite Task 2 body (re-scope rationale + checked steps), fix File
Structure + Architecture lines, amend Hand-off contract (org_list_items ->
OrgManifestEntry[]; org items remain personal Item).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
This commit is contained in:
@@ -602,12 +602,73 @@ pub fn org_unwrap_key(
|
||||
Ok(SessionHandle(handle))
|
||||
}
|
||||
|
||||
/// Decrypt an org manifest blob (manifest.enc in the org repo) with an org
|
||||
/// session handle. The org manifest is a dedicated OrgManifest type (NOT the
|
||||
/// personal Manifest); core's decrypt_org_manifest deserializes it.
|
||||
#[wasm_bindgen]
|
||||
pub fn org_manifest_decrypt(handle: &SessionHandle, encrypted: &[u8]) -> Result<JsValue, JsError> {
|
||||
need_key(handle)?;
|
||||
let out = session::with(handle.0, |k| relicario_core::decrypt_org_manifest(encrypted, k))
|
||||
.unwrap()
|
||||
.map_err(|e| JsError::new(&e.to_string()))?;
|
||||
js_value_for(&out)
|
||||
}
|
||||
|
||||
/// Encrypt an OrgManifest (JSON) with an org session handle, for the write track
|
||||
/// (Dev-C never touches WASM — this is exposed here so they don't have to).
|
||||
#[wasm_bindgen]
|
||||
pub fn org_manifest_encrypt(handle: &SessionHandle, manifest_json: &str) -> Result<Vec<u8>, JsError> {
|
||||
need_key(handle)?;
|
||||
let m: relicario_core::OrgManifest = serde_json::from_str(manifest_json)
|
||||
.map_err(|e| JsError::new(&format!("org manifest json: {e}")))?;
|
||||
session::with(handle.0, |k| relicario_core::encrypt_org_manifest(&m, k))
|
||||
.unwrap()
|
||||
.map_err(|e| JsError::new(&e.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod org_tests {
|
||||
use super::*;
|
||||
use relicario_core::org::wrap_org_key;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
#[test]
|
||||
fn org_manifest_round_trip_via_handle() {
|
||||
use relicario_core::{OrgManifest, OrgManifestEntry, ItemType, ItemId, decrypt_org_manifest};
|
||||
session::clear();
|
||||
let key_bytes = [0xABu8; 32];
|
||||
let h = session::insert(Zeroizing::new(key_bytes), Zeroizing::new([0u8; 32]));
|
||||
let handle = SessionHandle(h);
|
||||
let key = Zeroizing::new(key_bytes);
|
||||
|
||||
let mut manifest = OrgManifest::new();
|
||||
manifest.entries.push(OrgManifestEntry {
|
||||
id: ItemId::new(),
|
||||
r#type: ItemType::SecureNote,
|
||||
title: "Org Test Item".into(),
|
||||
tags: vec!["tag1".into()],
|
||||
modified: 1_234_567_890,
|
||||
trashed_at: None,
|
||||
collection: "prod".into(),
|
||||
});
|
||||
|
||||
let manifest_json = serde_json::to_string(&manifest).unwrap();
|
||||
let encrypted = org_manifest_encrypt(&handle, &manifest_json).unwrap();
|
||||
assert!(!encrypted.is_empty());
|
||||
|
||||
// Decrypt via core directly (avoids js-sys/serde_wasm_bindgen on native).
|
||||
let parsed: OrgManifest = decrypt_org_manifest(&encrypted, &key).unwrap();
|
||||
assert_eq!(parsed.entries.len(), 1);
|
||||
assert_eq!(parsed.entries[0].collection, "prod");
|
||||
assert_eq!(parsed.entries[0].title, "Org Test Item");
|
||||
assert_eq!(parsed.entries[0].tags, vec!["tag1".to_string()]);
|
||||
assert_eq!(parsed.schema_version, 1);
|
||||
|
||||
// Random nonces: two encryptions of the same plaintext must differ.
|
||||
let encrypted2 = org_manifest_encrypt(&handle, &manifest_json).unwrap();
|
||||
assert_ne!(encrypted, encrypted2, "nonces must differ");
|
||||
}
|
||||
|
||||
/// Returns (public_openssh, private_openssh) using the real core keypair generator.
|
||||
///
|
||||
/// Device private keys are produced by `relicario_core::device::generate_keypair()`
|
||||
|
||||
Reference in New Issue
Block a user