From c7ffc2b994a08526546d3d6fa07eb72f7148b7e6 Mon Sep 17 00:00:00 2001 From: wdunn001 Date: Mon, 3 Aug 2026 23:46:04 -0400 Subject: [PATCH] desktop: route News through the client-JWT path; bump to 0.4.4 News called /api/v1/client/news through apiGet(), which sends the webtoken Bearer obtained from POST /api/v1/client/user/webtoken. But that route is a defineClientEventHandler -- it verifies the clients own short-lived signed JWT and never consults the webtokens ACLs at all, so it rejected the request outright. Store and Library have always worked because they already use the JWT path. Adds api_get_jwt on the Rust side and apiGetClient() in the composable, and points News at it. Verified working against the live server. Two auth mechanisms coexist here and picking the wrong one yields a clean 403 that looks like a permissions problem: /api/v1/client/* wants the client JWT, while routes gated by aclManager.getUserIdACL want the webtoken plus the right ACL. --- desktop/main/composables/api.ts | 20 +++++++ desktop/main/pages/news.vue | 12 ++-- desktop/src-tauri/src/community_api.rs | 83 ++++++++++++++++++++------ desktop/src-tauri/src/lib.rs | 1 + desktop/src-tauri/tauri.conf.json | 2 +- 5 files changed, 96 insertions(+), 22 deletions(-) diff --git a/desktop/main/composables/api.ts b/desktop/main/composables/api.ts index 19d5d14a..352cf02b 100644 --- a/desktop/main/composables/api.ts +++ b/desktop/main/composables/api.ts @@ -22,6 +22,26 @@ export function apiGet( return invoke("api_get", { path, query: q }); } +// Same as apiGet, but for `/api/v1/client/*` routes built on +// `defineClientEventHandler` (news, library, game manifests, ...). Those +// routes authenticate with the desktop client's own short-lived JWT +// (Rust's generate_authorization_header), NOT the aclManager webtoken +// apiGet's api_get command mints -- sending them a webtoken 403s no matter +// what ACLs it carries, since defineClientEventHandler never looks at +// ACLs at all. See desktop/src-tauri/src/community_api.rs's module header +// for the full explanation of why there are two auth paths here. +export function apiGetClient( + path: string, + query?: Record, +): Promise { + const q = query + ? Object.entries(query) + .filter(([, v]) => v !== undefined) + .map(([k, v]) => [k, String(v)] as [string, string]) + : undefined; + return invoke("api_get_jwt", { path, query: q }); +} + export function apiPost(path: string, body?: unknown): Promise { return invoke("api_post", { path, body: body ?? {} }); } diff --git a/desktop/main/pages/news.vue b/desktop/main/pages/news.vue index 87b3595e..b059a0f6 100644 --- a/desktop/main/pages/news.vue +++ b/desktop/main/pages/news.vue @@ -100,7 +100,7 @@