fix(desktop): one duplicate-param policy for deep links - #6080
fix(desktop): one duplicate-param policy for deep links#6080Chessing234 wants to merge 4 commits into
Conversation
deep_link.rs carried three different policies for a duplicated query parameter. parse_entity_deep_link refuses the URL, parse_websocket_relay_param and optional_non_empty_param take the first value, and parse_message_deep_link and parse_join_deep_link assigned in a loop, so they took the last. The in-app parsers take the first: messageLink.ts reads searchParams.get, and entityLink.ts refuses outright. So the same URL could route one way when clicked inside the app and another when handed to the app by the OS, and a join link could display one code while carrying another. single_param is now the one reader: absent, empty or repeated all resolve to None, and every query-param parser in the file goes through it. That matches what our own builders emit, which is never a duplicate. Signed-off-by: Taksh <takshkothari09@gmail.com>
parse_channel_deep_link, twenty lines up, parses the channel UUID and
requires the message id to be 64 hex characters, lowercasing it. The query
form of the same two identifiers accepted anything non-empty, so a link with
a malformed channel or event id produced a payload the frontend could never
route, and one whose id differed only in case looked like a different
message.
Both are now checked the same way. An absent or empty thread param still
reads as absent, as before; a present but malformed one is a rejection.
The four tests that used placeholder values ("abc", "xyz", "root1") now use a
real UUID and real event ids. They were asserting which params are required,
not that malformed values are accepted — the parser beside them has always
refused those exact strings.
Signed-off-by: Taksh <takshkothari09@gmail.com>
Three message cases and two join cases, each the shape the old loop resolved to its last value: a second channel, a second id, a second thread, a second code, a second relay. The join pair is the one worth having — a link that shows one code and carries another is the whole reason to prefer refusing over picking. Signed-off-by: Taksh <takshkothari09@gmail.com>
themiguelamador
left a comment
There was a problem hiding this comment.
I found two remaining duplicate-parameter paths:
single_paramreturned the sameNonefor “absent” and “repeated.” That works for required parameters, but optionalpolicy_receiptand add-communitynametreated a duplicate as a valid absence and continued parsing.- Nostr-bind required fields still used the old first-value reader, while optional
callback_urlhad the same absent/repeated ambiguity. This left security-sensitive bind links outside the PR's stated single policy.
I fixed these in commit 6c1438be1 on Complear:review/pr-6080-fix. The shared reader now distinguishes valid absence/empty from invalid repetition, all parser families consume that distinction, and Nostr-bind reports an explicit repeated <param> error.
Verification:
- focused Tauri deep-link suite — 54 passed
cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warningscargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all -- --check- desktop Tauri pre-commit format hook
git diff --check
…ms too Review finding (P1, themiguelamador on block#6080): two families were still outside the policy this PR claims to establish. - `single_param` returned the same `None` for "absent" and "repeated". That is fine for a required param, where both fail the link, but optional `policy_receipt` and add-community `name` read a duplicate as a valid absence and carried on parsing — the link was accepted with the field silently dropped, which is exactly the ambiguity the policy removes. - Nostr-bind's required fields still went through the old first-value reader, and its optional `callback_url` had the same absent/repeated ambiguity. That one is security-sensitive: the callback is checked against `origin` only when present, so a repeated `callback_url` reading as absent hands back a bind payload whose callback never met `validate_nostr_bind_callback_url`. `read_single_param` now returns three outcomes — `Absent`, `Present`, `Repeated` — and every reader is a projection of it: required params fail on absent and repeated alike, optional params keep absence valid and reject repetition, and nostr-bind reports `repeated <param>` by name rather than the misleading `missing <param>`. Signed-off-by: Taksh <takshkothari09@gmail.com>
|
Both confirmed and fixed in You've identified the actual shape of the mistake: The nostr-bind gap is the more serious half, and for the reason you give:
Verification: focused deep-link suite (55 passed), full Tauri lib suite (2,446 passed, 15 ignored), workspace/all-target clippy with |
deep_link.rscarries four query-param parsers with three different policies for a repeated parameter:parse_entity_deep_linkparse_websocket_relay_param,optional_non_empty_paramparse_message_deep_link,parse_join_deep_linkThe in-app parsers take the first —
messageLink.tsreadssearchParams.get,entityLink.tsrefuses outright. So the same URL routes one way when clicked inside the app and another when handed to the app by the OS, andbuzz://join?relay=…&code=abc&code=evildisplays one code while carrying another.single_paramis now the one reader — absent, empty and repeated all resolve toNone— and every query-param parser goes through it. Refusing rather than picking is the right call here because our own builders never emit a duplicate, so any URL with one is malformed by construction.Second commit: the message parser now validates what its neighbours validate.
parse_channel_deep_link, twenty lines up, parses the channel UUID and requires the message id to be 64 hex characters, lowercasing it. The query form of the same two identifiers accepted anything non-empty, so a malformed link produced a payload the frontend could never route, and an id differing only in case looked like a different message.Four existing tests change, and I want that visible. They used placeholder values —
channel=abc&id=xyz,thread=root1— and now use a real UUID and real event ids. They assert which params are required, not that malformed values are accepted; the parser beside them has always refused those exact strings.thread=empty still reads as absent, as before; present-but-malformed is now a rejection.New coverage: three repeated-param cases for
message(secondchannel,id,thread), two forjoin(secondcode, secondrelay), and the shape cases. The join pair is the one worth having.Not changed: unknown params are still ignored on the non-entity parsers. Entity links reject them, but a join or add-community link picking up a tracking param is plausible enough that tightening it belongs in its own change.
Verified locally: complete Tauri library suite 2443 passed / 15 ignored / 0 failed (52 in the deep-link module, 49 before),
cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --workspace --all-targets -- -D warningsclean,cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all -- --check,git diff --check. Not run: the OS-level handoff itself — these are the parsers, exercised directly.