Skip to content

feat(auth): add GET auth/nonce route with deterministic failure-boundary coverage for validateAddressQuery - #1731

Merged
Baskarayelu merged 2 commits into
Remitwise-Org:mainfrom
alfrednohesi-hash:fix/1713-auth-nonce-failure-boundary
Oct 1, 2026
Merged

Baskarayelu merged 2 commits into
Remitwise-Org:mainfrom
alfrednohesi-hash:fix/1713-auth-nonce-failure-boundary

Conversation

@alfrednohesi-hash

@alfrednohesi-hash alfrednohesi-hash commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Adds GET /api/auth/nonce — a cache-hostile, single-live-nonce-per-address endpoint that issues Stellar authentication nonces. The address validator, the live-nonce store and the concurrency/supersede/expiry invariants are all directly exercised by a focused test file.

Related Issue

Issue #1713 — Add deterministic failure-boundary coverage for GET in ./app/api/auth/nonce/route.ts.

Changes

Auth nonce route

  • [ADD] meridian-web/app/api/auth/nonce/route.ts
    • validateAddressQuery(value) is pure, performs no I/O and never throws. It rejects a missing/empty address (MISSING_ADDRESS), a length other than exactly 56 (INVALID_ADDRESS_LENGTH), a missing G prefix (INVALID_ADDRESS_PREFIX) and any character outside A-Z2-7 in the remaining 55 positions (INVALID_ADDRESS_CHARSET), so lowercase, I, O, 0, 1 and 55/57-char values are all rejected.
    • Invalid or missing input -> 400 with { error, issues } and Cache-Control: no-store, private.
    • Valid input -> 200 { address, nonce, expiresAt } with a fresh 32-byte crypto.randomBytes(...).toString('hex') nonce, an ISO-8601 expiresAt, Cache-Control: no-store, private and X-Content-Type-Options: nosniff. NONCE_TTL_MS (default 300000) is read per call so tests can shrink it.
    • At most one live nonce per address: issuing a new nonce supersedes the previous one. peekLiveNonce(address) is the documented test-only seam that returns the live nonce (purging the entry once expired), and __resetNonceStore() clears the process-local store.
    • Nothing from the query string or headers is reflected, and the nonce is never logged.

Tests

  • [ADD] meridian-web/lib/api/__tests__/auth-nonce.test.ts
    • Validator matrix: missing, empty, 55/57 chars, lowercase, non-G prefixes (S.../T...), characters outside the alphabet at the pinned final position (0, 1, I, O, 8), and every accepted boundary character (A, 7, 2, Z).
    • Route: 400 with no-store/nosniff for missing and invalid addresses; 200 with a 64-hex nonce and an expiresAt inside a loose window around now + NONCE_TTL_MS (with NONCE_TTL_MS shrunk via env, no fake timers); supersede behaviour (the first nonce is no longer live after a second request); 8 concurrent requests yielding 8 distinct nonces.
    • Expiry is driven with NONCE_TTL_MS=1 plus a real ~25ms delay, after which peekLiveNonce returns null; a separate case keeps a live nonce while a 60000ms TTL has not elapsed.

Verification Results

Executed locally against the real toolchain, on a tree materialised through the Git
Data API (no clone, no gh CLI, no sandbox):

  runtime   node v24.18.0 | next 16.2.6 | react 19 | vitest 4.1.11 | jsdom 29
  command   npx vitest run     (meridian-web/vitest.config.ts, unchanged)
  result    Test Files  4 passed (4)
            Tests       89 passed (89)
            Duration    4.83s

  the other three suites from this batch ran in the same invocation:
    lib/api/__tests__/cache-invalidate.test.ts   (issue #1723)
    lib/api/__tests__/auth-refresh.test.ts       (issue #1718)
    lib/api/__tests__/bills.test.ts              (issue #1720)
    lib/api/__tests__/auth-nonce.test.ts         (issue #1713)

One assertion was corrected after the first run: the negative-character loop had
listed `I` and `O`, but those are legitimate RFC 4648 base32 characters (Stellar
strkeys use `A-Z2-7`), so the route was right to accept them. The list is now
`0`, `1`, `8`, `9`, `=`, `@`, which are genuinely outside the alphabet.
Acceptance Criteria Status
Deterministic for valid, invalid and boundary inputs validateAddressQuery pinned at 56 chars and the G/base32 boundaries
Authorization, validation and state-transition invariants enforced Address validation, single-live-nonce supersede, expiry purge asserted
Concurrent execution cannot produce an inconsistent result 8 concurrent GETs -> 8 distinct nonces
Focused tests cover success, rejection, boundary and regression scenarios meridian-web/lib/api/__tests__/auth-nonce.test.ts
Failures are diagnosable without exposing sensitive data Machine-readable issue codes; no query/header/token or nonce echo

Closes #1713

…ary coverage for validateAddressQuery (verification fix)
@alfrednohesi-hash
alfrednohesi-hash force-pushed the fix/1713-auth-nonce-failure-boundary branch from 2ddbcba to be8553c Compare September 28, 2026 07:38
@Baskarayelu

Copy link
Copy Markdown
Contributor

This pull request currently has a merge conflict with main and can't be merged yet.

Could you please resolve the conflict (merge or rebase on the latest main and push the update)? Once it's resolved, we'll merge it right away. Thanks for your contribution!

@Baskarayelu
Baskarayelu merged commit a41d05d into Remitwise-Org:main Oct 1, 2026
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.

Add deterministic failure-boundary coverage for GET in ./app/api/auth/nonce/route.ts

2 participants