qconnect: Rust backstop validates per-track origins server-side

v2_qconnect_send_command_with_admission trusted a single frontend origin and
never inspected track_ids. Add track_origins[] to the request and
validate_track_origins_for_admission: re-evaluate every track via the core
gate, block if any is non-Qobuz or the list is empty. The per-track check is
only enforced when the caller actually ships per-track origins (bulk queue
loads); single-track play-next/play-later carry no track_origins and stay
gated by the command-level origin alone, so an empty list never over-blocks
them. The existing command-level check and admission_blocked emit are kept.

Frontend loadQconnectQueue now computes per-track origins from its tracks arg
via resolveQconnectTrackOrigin and ships them to the sender.

Defers the optional DB cross-check (LibraryState / has_local_track_by_qobuz_id
defense-in-depth) as a follow-up; this commit is the per-track-origins
validator + wiring + test only.

cc
This commit is contained in:
vicrodh committed 2026-06-01 02:34:42 -06:00
1 parent 82effd68ad
commit 653ded1e72
4 files changed
+67 -5

No files matched your search

+11 -2
View File
@@ -202,12 +202,14 @@ async function resolveQconnectPlayNextInsertAfterFromSnapshots(): Promise<Qconne
async function sendQconnectQueueCommandWithAdmission(
commandType: QconnectQueueCommandType,
origin: QconnectTrackOrigin,
payload: Record<string, unknown>
payload: Record<string, unknown>,
trackOrigins: QconnectTrackOrigin[] = []
): Promise<void> {
await invoke('v2_qconnect_send_command_with_admission', {
request: {
command_type: commandType,
origin,
track_origins: trackOrigins,
payload
}
});
@@ -610,8 +612,15 @@ export async function loadQconnectQueue(
preview_track_ids: trackIds.slice(0, 8),
payload
});
// Per-track origins for the Rust backstop (fix #3): when backend-shaped
// tracks are supplied, ship each track's resolved origin so the server gate
// can re-validate every id, not just the command-level origin. Bare id-only
// callers send none and stay gated by the command-level origin alone.
const trackOrigins = tracks
? tracks.map((tr) => resolveQconnectTrackOrigin(tr, tr.is_local ?? false))
: [];
console.log('[QConnect/LoadQueue] sending queue_load_tracks');
await sendQconnectQueueCommandWithAdmission('queue_load_tracks', origin, payload);
await sendQconnectQueueCommandWithAdmission('queue_load_tracks', origin, payload, trackOrigins);
console.log('[QConnect/LoadQueue] SUCCESS');
// Sync local autoplay preference to QConnect server after queue load