fix(pairing): accept non-string NIP-46 params (Amber sends requested_perms as a JSON object)
RawRequest used a strict Vec<String>, so Amber's connect request — whose params[1] is the requested_perms grant object — failed to parse and was silently dropped. The signer saw a live session; we saw nothing. Params now coerce non-string values to their JSON text, with regression tests for both shapes.
This commit is contained in:
parent
759b5ddfc9
commit
aedde8ffb8
1 changed files with 57 additions and 4 deletions
|
|
@ -589,9 +589,8 @@ impl Nip46ClientSigner {
|
|||
// Local-only debug capture (never leaves this machine): keep the
|
||||
// full event so a failed handshake can be dissected offline.
|
||||
{
|
||||
let path = std::path::Path::new(
|
||||
"/home/avi/Tools/keynctr-debug/pairing-capture.jsonl",
|
||||
);
|
||||
let path =
|
||||
std::path::Path::new("/home/avi/Tools/keynctr-debug/pairing-capture.jsonl");
|
||||
if let Some(dir) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(dir);
|
||||
}
|
||||
|
|
@ -1861,10 +1860,29 @@ impl Signer for Nip46ClientSigner {
|
|||
struct RawRequest {
|
||||
id: String,
|
||||
method: String,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "lenient_params")]
|
||||
params: Vec<String>,
|
||||
}
|
||||
|
||||
/// NIP-46 params are conventionally strings, but real signers embed raw JSON
|
||||
/// objects (Amber sends its requested_perms grant object as `connect`
|
||||
/// params[1]). A strict `Vec<String>` would reject the entire request and
|
||||
/// silently drop the handshake, so non-string params are coerced to their
|
||||
/// JSON text instead.
|
||||
fn lenient_params<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let values: Vec<serde_json::Value> = Vec::deserialize(deserializer)?;
|
||||
Ok(values
|
||||
.into_iter()
|
||||
.map(|v| match v {
|
||||
serde_json::Value::String(s) => s,
|
||||
other => other.to_string(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn response_ok(id: &str, result: String) -> String {
|
||||
json!({ "id": id, "result": result, "error": null }).to_string()
|
||||
}
|
||||
|
|
@ -1934,3 +1952,38 @@ fn percent_decode(raw: &str) -> Option<String> {
|
|||
}
|
||||
String::from_utf8(out).ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod raw_request_tests {
|
||||
use super::RawRequest;
|
||||
|
||||
#[test]
|
||||
fn parses_amber_style_connect_with_object_params() {
|
||||
// Real signers (Amber) send requested_perms as a raw JSON object in
|
||||
// params[1]; a strict Vec<String> used to reject the whole request
|
||||
// and silently drop the pairing handshake.
|
||||
let raw = format!(
|
||||
r#"{{"id":"1","method":"connect","params":["{}",{{"sign_event":[1,7],"nip04_encrypt":true}}]}}"#,
|
||||
"a".repeat(64)
|
||||
);
|
||||
let req: RawRequest = serde_json::from_str(&raw).expect("must parse");
|
||||
assert_eq!(req.method, "connect");
|
||||
assert_eq!(req.params.len(), 2);
|
||||
assert_eq!(req.params[0], "a".repeat(64));
|
||||
assert!(
|
||||
req.params[1].starts_with('{'),
|
||||
"object coerced to JSON text"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_string_params_unchanged() {
|
||||
let req: RawRequest =
|
||||
serde_json::from_str(r#"{"id":"2","method":"get_public_key","params":[]}"#).unwrap();
|
||||
assert!(req.params.is_empty());
|
||||
let req: RawRequest =
|
||||
serde_json::from_str(r#"{"id":"3","method":"nip44_decrypt","params":["aa","bb"]}"#)
|
||||
.unwrap();
|
||||
assert_eq!(req.params, vec!["aa".to_string(), "bb".to_string()]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue