Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2dc4bf3
feat(auth): per-request EdDSA JWT builder for mobile (#865)
piyalbasu Jul 7, 2026
df7ba5a
feat(auth): attach per-request JWT + retry-once-on-401 to freighterBa…
piyalbasu Jul 7, 2026
41de33f
fix(auth): register JWT interceptors before apiFactory normalizer so …
piyalbasu Jul 7, 2026
d5178b7
test(auth): gated E2E round-trip vs staging whoami (#865)
piyalbasu Jul 7, 2026
4d7593d
feat(auth): migrate v2 POST call sites to string body + query-in-url …
piyalbasu Jul 7, 2026
5e84968
test(auth): read whoami { data } envelope; E2E verified green vs stag…
piyalbasu Jul 7, 2026
369c436
fix(auth): idempotent error normalizer (preserve 401 on retry) + skip…
piyalbasu Jul 8, 2026
151b053
feat(auth): enforce string-only V2 request bodies at compile time so …
piyalbasu Jul 8, 2026
2cd79e6
fix(auth): fold config.params into the signed JWT path; refresh body/…
piyalbasu Jul 8, 2026
ff64a1a
fix(auth): strip stale Authorization header on locked/anonymous reque…
piyalbasu Jul 8, 2026
0c5e585
fix(auth): handle URLSearchParams when folding params into signed pat…
piyalbasu Jul 8, 2026
664cd17
Merge remote-tracking branch 'origin/feat/864-derive-auth-keypair' in…
piyalbasu Jul 9, 2026
0c32320
perf(auth): short-TTL memo + in-flight dedup for isSessionAuthValid (…
piyalbasu Jul 9, 2026
53fc76a
fix(auth): address review — central body serialization, clock-skew fa…
piyalbasu Jul 10, 2026
b77874f
refactor(api): drop the now-inert TWriteBody generic from createApiSe…
piyalbasu Jul 10, 2026
0ab214b
refactor(auth): derive signed JWT path from axios getUri, not hand-ro…
piyalbasu Jul 10, 2026
46d15cd
revert(auth): drop the anonymous 401 fallback; match the extension's …
piyalbasu Jul 10, 2026
6eb4356
fix(auth): sign the path without RN's non-spec URL; clear memo on unl…
piyalbasu Jul 10, 2026
0b8322b
fix(api): redact Authorization in the logRequests debug logger (#865)
piyalbasu Jul 10, 2026
29e5084
refactor(backend): pass v2 network via { params } now that getUri sig…
piyalbasu Jul 10, 2026
96c2ae9
docs(api): explain the `unknown` body type on post/put (#865)
piyalbasu Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions __tests__/ducks/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
appendAccounts,
clearAccountData,
isSessionAuthValid,
clearSessionAuthValidMemo,
SESSION_AUTH_VALID_TTL_MS,
getActiveMnemonicPhrase,
} from "ducks/auth";
import { useBalancesStore } from "ducks/balances";
Expand Down Expand Up @@ -2426,6 +2428,11 @@ describe("auth duck", () => {
(secureDataStorage.setItem as jest.Mock).mockResolvedValue(undefined);
};

// The memo is module-scoped state that would otherwise leak across tests.
beforeEach(() => {
clearSessionAuthValidMemo();
});

it("returns true when the funnel reports AUTHENTICATED (accounts + valid hash + within timer)", async () => {
mockFunnelStorage();
expect(await isSessionAuthValid()).toBe(true);
Expand Down Expand Up @@ -2467,6 +2474,40 @@ describe("auth duck", () => {
});
expect(await isSessionAuthValid()).toBe(false);
});

it("dedupes a concurrent burst into a single funnel run (in-flight promise shared)", async () => {
mockFunnelStorage();
(getHashKey as jest.Mock).mockClear();

const results = await Promise.all([
isSessionAuthValid(),
isSessionAuthValid(),
isSessionAuthValid(),
]);

expect(results).toEqual([true, true, true]);
// getAuthStatus (→ getHashKey once per run) ran a single time for all three.
expect(getHashKey).toHaveBeenCalledTimes(1);
});

it("memoizes within the TTL and re-runs the funnel once it expires", async () => {
const nowSpy = jest.spyOn(Date, "now").mockReturnValue(1_000_000);
mockFunnelStorage();
(getHashKey as jest.Mock).mockClear();

await isSessionAuthValid();
await isSessionAuthValid();
await isSessionAuthValid();
// Second and third calls are served from the memo — one funnel run.
expect(getHashKey).toHaveBeenCalledTimes(1);

// Advance past the TTL: the memo expires and the funnel runs again.
nowSpy.mockReturnValue(1_000_000 + SESSION_AUTH_VALID_TTL_MS + 1);
await isSessionAuthValid();
expect(getHashKey).toHaveBeenCalledTimes(2);

nowSpy.mockRestore();
});
});

describe("getActiveMnemonicPhrase lock race (TOCTOU)", () => {
Expand Down
6 changes: 6 additions & 0 deletions __tests__/services/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ jest.mock("services/apiFactory", () => ({
createApiService: jest.fn(() => ({
get: jest.fn(),
post: jest.fn(),
getInstance: jest.fn(() => ({
interceptors: {
request: { use: jest.fn() },
response: { use: jest.fn() },
},
})),
})),
isRequestCanceled: jest.fn(),
}));
Expand Down
Loading
Loading