Skip to content

feat(auth): wallet sign-in and authFetch with single-flight refresh - #1355

Open
ValJnr-dev1 wants to merge 1 commit into
CalloraOrg:mainfrom
ValJnr-dev1:feat/wallet-sign-in
Open

ValJnr-dev1 wants to merge 1 commit into
CalloraOrg:mainfrom
ValJnr-dev1:feat/wallet-sign-in

Conversation

@ValJnr-dev1

Copy link
Copy Markdown

Closes #1317

Summary

Implements wallet-based sign-in via Freighter and a single authenticated fetch helper (authFetch) so the rest of the app has a standard way to call requireAuth endpoints.

Changes

File What changed
src/services/authService.ts New service: signIn(), signOut(), getAccessToken(), refreshAccessToken()
src/services/authFetch.ts New helper: authFetch(path, init) with single-flight refresh
src/App.tsx WalletSignInButton added to topbar; AppSessionExpiryBanner wired
src/pages/ApiUsage.tsx Usage fetch migrated from plain fetch() to authFetch()
docs/Configuration.md New §\ Session model — token storage trade-offs, httpOnly upgrade path
src/services/authService.test.ts 25 tests (all 9 issue scenarios)
src/services/authFetch.test.ts 14 tests

Sign-in sequence

Browser                   Freighter            Callora API
  |                          |                     |
  | requestAccess()          |                     |
  |------------------------->|                     |
  |<---- address ------------|                     |
  |                          |                     |
  | signMessage(challenge)   |                     |
  |   • app origin           |                     |
  |   • wallet address       |                     |
  |   • ISO timestamp        |                     |
  |   • expiry +5 min        |                     |
  |   • 128-bit nonce        |                     |
  |------------------------->|                     |
  |<---- signedMessage -------|                     |
  |                          |                     |
  | POST /api/auth/wallet    |                     |
  |-------------------------------------------->  |
  |<-- { accessToken, refreshToken } --------------|
  |                          |                     |
  | access token → memory    |                     |
  | refresh token → sessionStorage                 |

Refresh and expiry

On a 401, authFetch calls refreshAccessToken(). Concurrent 401s share a single refresh promise (no stampede). If the refresh also fails, signOut() clears local state and signalExpiry() fires so SessionExpiryBanner appears. An AbortSignal from the caller is forwarded to both the initial request and the retry.

Token storage

  • Access token — module-level variable, never persisted.
  • Refresh token — sessionStorage, cleared when the tab closes.
  • localStorage is never written to (verified by test Scenario 8).

The docs section explains the trade-off and the path to upgrade to httpOnly cookies once the backend adds Set-Cookie.

Test output

Test Files  146 passed (146)
     Tests  2226 passed (2226)

Coverage for new services:

  • authService.ts: 93.57% stmts / 83.56% branches / 100% funcs
  • authFetch.ts: 94.79% stmts / 92.30% branches / 83.33% funcs

Implements CalloraOrg#1317.

Changes:
- src/services/authService.ts: signIn() uses requestAccess + signMessage
  (Freighter v6), builds an anti-replay message (origin, address, ISO
  timestamp, +5 min expiry, 16-byte nonce), POSTs to POST /api/auth/wallet.
  signOut() calls POST /api/auth/revoke. Access token kept in memory only;
  refresh token in sessionStorage (guarded with try/catch).

- src/services/authFetch.ts: authFetch(path, init) prefixes with apiUrl(),
  injects Authorization: Bearer, refreshes once on 401 with a single shared
  promise so concurrent 401s never stampede. Abort signals cancel both
  attempts. Refresh failures clear the session and emit signalExpiry().

- src/App.tsx: WalletSignInButton added to the topbar (next to
  AccountSwitcher). AppSessionExpiryBanner wires useSessionExpiry() to
  SessionExpiryBanner inside the main content area.

- src/pages/ApiUsage.tsx: usage fetch migrated from plain fetch() to
  authFetch() — the Authorization header is now sent.

- docs/Configuration.md: new "Session model" section documents token
  storage trade-offs, sign-in sequence, refresh/retry flow, and the
  httpOnly-cookie upgrade path.

- src/services/authService.test.ts: 25 tests covering all 9 issue scenarios
  (happy path, rejection, concurrent refresh, abort, localStorage isolation).

- src/services/authFetch.test.ts: 14 tests.

Coverage: authService 93.57% stmts / 83.56% branches / 100% funcs;
          authFetch   94.79% stmts / 92.30% branches / 83.33% funcs.
All 2226 tests pass; typecheck clean.
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@ValJnr-dev1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sign in with Freighter through /api/auth/wallet and attach bearer tokens to API calls

1 participant