- Rust host: spawns storebound serve --stdio with fixed args, hands over a per-session secret, mints confirm tokens only after a native dialog showing the summary and the dry run - every app command re-checks window label and page origin; one capability (main window, app commands + event listen only, no plugin API in the webview); strict CSP; tests/security.rs pins it - store keys picked in native dialogs, kept in the OS keychain, passed per call in memory only - plugins: shell (Rust only), single-instance, deep-link storebound://, window-state, opener, dialog, notification, updater (placeholder endpoint and key until signing), process - app-centric UI: Portfolio, App (Releases, Catalog, Listings, Health, Reviews, TestFlight), App folders, Accounts, Dependencies, Settings; light / dark with the storebound.dev tokens, reduced motion, home folder shown as ~ - macOS menu: Cmd+Q quits, Edit menu, Window menu keeps the main window; Linux DMABUF fix - Bun workspace (npm files unchanged), ci runs desktop typecheck, build, cargo test and clippy; scrub covers home paths; docs/desktop.md (protocol, safety, dependency sources and licenses)
43 lines
2.1 KiB
Rust
43 lines
2.1 KiB
Rust
//! The host mints exactly the tokens the sidecar verifies: same canonical JSON, same HMAC, same shape as
|
|
//! cli/lib/confirm-token.ts. The vector is shared with tests/serve.test.ts in the repository root.
|
|
use serde_json::json;
|
|
use storebound_desktop::confirm::*;
|
|
|
|
fn secret() -> Vec<u8> {
|
|
(0u8..32).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn shared_vector() {
|
|
let p = json!({ "project": "/tmp/game", "args": { "only": "en-US,tr", "b": [1, true, null, "ü\"\n"] } });
|
|
assert_eq!(canonical(&p).unwrap(), "{\"args\":{\"b\":[1,true,null,\"ü\\\"\\n\"],\"only\":\"en-US,tr\"},\"project\":\"/tmp/game\"}");
|
|
assert_eq!(mac(&secret(), "play.listing", &p, 1_700_000_060_000, "AAAAAAAAAAAAAAAAAAAAAA").unwrap(), "JhSxUL2ej4cm4yrmlcA23rTCPq3YMKJthbB7n6aixxw");
|
|
}
|
|
|
|
#[test]
|
|
fn canonical_rules() {
|
|
assert_eq!(canonical(&json!({ "b": 1, "a": [true, null, "x"], "c": { "z": "" } })).unwrap(), "{\"a\":[true,null,\"x\"],\"b\":1,\"c\":{\"z\":\"\"}}");
|
|
assert!(canonical(&json!({ "f": 0.5 })).is_err());
|
|
assert!(canonical(&json!({ "f": 9_007_199_254_740_992u64 })).is_err());
|
|
assert_eq!(canonical(&json!(-9_007_199_254_740_991i64)).unwrap(), "-9007199254740991");
|
|
// keys sort by UTF-16 code units like JavaScript: "\u{e9}" (U+00E9) after "z", astral after BMP private use
|
|
assert_eq!(canonical(&json!({ "é": 1, "z": 2, "A": 3 })).unwrap(), "{\"A\":3,\"z\":2,\"é\":1}");
|
|
assert_eq!(canonical(&json!("\u{1}\t</")).unwrap(), "\"\\u0001\\t</\"");
|
|
}
|
|
|
|
#[test]
|
|
fn minted_tokens_have_the_sidecar_shape() {
|
|
let p = json!({ "project": "/p", "args": {} });
|
|
let t = mint(&secret(), "play.listing", &p, 1_000, "AAAAAAAAAAAAAAAAAAAAAA").unwrap();
|
|
let parts: Vec<&str> = t.split('.').collect();
|
|
assert_eq!(parts.len(), 4);
|
|
assert_eq!(parts[0], "sbt1");
|
|
assert_eq!(parts[1], (1_000 + TTL_MS).to_string());
|
|
assert_eq!(parts[3].len(), 43);
|
|
assert_eq!(parts[3], mac(&secret(), "play.listing", &p, 1_000 + TTL_MS, "AAAAAAAAAAAAAAAAAAAAAA").unwrap());
|
|
let n = new_nonce().unwrap();
|
|
assert_eq!(n.len(), 22);
|
|
assert_ne!(n, new_nonce().unwrap());
|
|
assert_ne!(new_secret().unwrap(), new_secret().unwrap());
|
|
assert_eq!(hex(&[0, 15, 255]), "000fff");
|
|
}
|