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.
51 lines
2.1 KiB
TypeScript
51 lines
2.1 KiB
TypeScript
import { invoke } from "@tauri-apps/api/core";
|
|
|
|
// Thin wrappers over the generic Rust bridge (community_api.rs): the client
|
|
// passes the server's own `/api/v1/...` path, Rust resolves the base URL +
|
|
// signs the request with the same auth every other client call uses. See
|
|
// desktop/src-tauri/src/community_api.rs for why this is generic rather than
|
|
// one typed Tauri command per endpoint.
|
|
//
|
|
// On a non-2xx response the Rust side rejects with a stringified
|
|
// RemoteAccessError::InvalidResponse whose message is the server's own
|
|
// `statusMessage`/`message` -- callers can show `(e as string)` directly.
|
|
|
|
export function apiGet<T = any>(
|
|
path: string,
|
|
query?: Record<string, string | number | boolean | undefined>,
|
|
): Promise<T> {
|
|
const q = query
|
|
? Object.entries(query)
|
|
.filter(([, v]) => v !== undefined)
|
|
.map(([k, v]) => [k, String(v)] as [string, string])
|
|
: undefined;
|
|
return invoke<T>("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<T = any>(
|
|
path: string,
|
|
query?: Record<string, string | number | boolean | undefined>,
|
|
): Promise<T> {
|
|
const q = query
|
|
? Object.entries(query)
|
|
.filter(([, v]) => v !== undefined)
|
|
.map(([k, v]) => [k, String(v)] as [string, string])
|
|
: undefined;
|
|
return invoke<T>("api_get_jwt", { path, query: q });
|
|
}
|
|
|
|
export function apiPost<T = any>(path: string, body?: unknown): Promise<T> {
|
|
return invoke<T>("api_post", { path, body: body ?? {} });
|
|
}
|
|
|
|
export function apiDelete<T = any>(path: string): Promise<T> {
|
|
return invoke<T>("api_delete", { path });
|
|
}
|