orama/sdk/tests/unit/http/proxy-jwt-bug-149.test.ts
anonpenguin23 b0526e560e release: 0.122.77
- fix(gateway): /v1/auth/token trusts internal-auth context on namespace
  gateways instead of re-querying the empty namespace api_keys table, so
  API-key->JWT exchange works end-to-end (bugboard #147/#148 regression)
- fix(sdk): attach wallet JWT on /v1/proxy/* (http.ts getAuthHeaders was
  api-key-only; layer-1 requires the user JWT) (bugboard #149)
- feat(rqlite): namespace schema isolation — namespace gateways apply core
  migrations under an isolated orama_schema_migrations tracker and free the
  generic tenant names (schema_migrations, dead pubsub subscriptions),
  auto-remediating already-polluted namespace DBs (bugboard #150)
2026-07-04 23:52:49 +03:00

60 lines
2.5 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
import { HttpClient } from "../../../src/core/http";
/**
* Bugboard #149 — the SDK dropped the wallet JWT on /v1/proxy/*.
*
* The gateway enforces a per-user (SIWE wallet) JWT on proxy operations
* (layer-1, #148): an API key alone is rejected 401 "requires a logged-in
* user". HttpClient.getAuthHeaders() bucketed /v1/proxy/ into the api-key-ONLY
* branch (alongside rqlite/pubsub/cache), so proxyAnon went out without the
* Authorization header even when a valid wallet JWT was set — while /v1/storage/*
* (which is NOT in that branch) correctly sent both. Same client, same JWT,
* attached on storage, dropped on proxy. This locks in that proxy sends BOTH.
*/
describe("Bug #149 — HttpClient attaches wallet JWT on /v1/proxy/*", () => {
function captureHeaders() {
const seen: Record<string, any> = {};
const fetchImpl = vi.fn(async (url: any, options: any) => {
seen.url = String(url);
seen.headers = options?.headers ?? {};
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
});
const client = new HttpClient({
baseURL: "https://gw.example",
maxRetries: 0,
timeout: 5000,
fetch: fetchImpl as any,
});
client.setApiKey("ak_runtime:anchat-test");
client.setJwt("eyJhbGciOi.wallet.jwt");
return { client, seen };
}
it("sends BOTH X-API-Key and Bearer JWT on POST /v1/proxy/anon", async () => {
const { client, seen } = captureHeaders();
await client.post("/v1/proxy/anon", { url: "https://x" });
expect(seen.headers["X-API-Key"]).toBe("ak_runtime:anchat-test");
expect(seen.headers["Authorization"]).toBe("Bearer eyJhbGciOi.wallet.jwt");
});
it("storage (the working reference) also sends both — proxy must match it", async () => {
const { client, seen } = captureHeaders();
await client.post("/v1/storage/upload", { data: "x" });
expect(seen.headers["X-API-Key"]).toBe("ak_runtime:anchat-test");
expect(seen.headers["Authorization"]).toBe("Bearer eyJhbGciOi.wallet.jwt");
});
it("rqlite/pubsub/cache stay api-key-only (JWT intentionally NOT attached)", async () => {
for (const path of ["/v1/rqlite/query", "/v1/pubsub/publish", "/v1/cache/get"]) {
const { client, seen } = captureHeaders();
await client.post(path, {});
expect(seen.headers["X-API-Key"]).toBe("ak_runtime:anchat-test");
expect(seen.headers["Authorization"]).toBeUndefined();
}
});
});