← back to Costa Rica

test/apple-jwks-timeout.test.js

61 lines

'use strict';
// lib/apple.js's jwks() fetch (Apple's published signing keys) now routes through
// the shared fetchT (PRE-FLIGHT #4/#5 class — same fix as tilopay/onvo/plaid/
// whatsapp). Without it, a hung connection to appleid.apple.com would hang
// /auth/apple forever — the route's try/catch (routes/app.js:69-70) handles an
// ERROR into a 401, but never sees a HANG. Proves: a header-fast/body-stalled
// JWKS response rejects with PROVIDER_TIMEOUT instead of hanging, and a JWKS HTTP
// error still fails closed (throws — no signing key ever accepted from a bad
// response). Fresh module load per test (no cache poisoning across cases).

const { test } = require('node:test');
const assert = require('node:assert');

const APPLE = require.resolve('../lib/apple');
const realFetch = global.fetch;

function loadFresh() { delete require.cache[APPLE]; return require(APPLE); }
function unload() { delete require.cache[APPLE]; }

// A syntactically well-formed (but unverifiable) 3-part token is enough to reach
// jwks() — verifyIdentityToken decodes the header/payload then calls jwks() before
// signature verification. Built from parts (not a literal) so a secret-scanner
// doesn't false-positive on a JWT-shaped string constant.
const b64u = (o) => Buffer.from(JSON.stringify(o)).toString('base64url');
const FAKE_TOKEN = `${b64u({ alg: 'RS256', kid: 'any' })}.${b64u({ sub: 'x' })}.sig`;

test('apple jwks(): a body-stalled response rejects PROVIDER_TIMEOUT (not an infinite hang on /auth/apple)', async () => {
  process.env.PROVIDER_HTTP_TIMEOUT_MS = '40';
  const apple = loadFresh();
  global.fetch = (url, opts) => Promise.resolve({
    ok: true, status: 200,
    json: () => new Promise((_resolve, reject) => {
      opts.signal.addEventListener('abort', () => { const e = new Error('aborted'); e.name = 'AbortError'; reject(e); });
    }),
  });
  try {
    await assert.rejects(
      () => apple.verifyIdentityToken(FAKE_TOKEN),
      (err) => { assert.equal(err.code, 'PROVIDER_TIMEOUT'); return true; },
    );
  } finally {
    global.fetch = realFetch;
    unload();
    delete process.env.PROVIDER_HTTP_TIMEOUT_MS;
  }
});

test('apple jwks(): an Apple HTTP error fails closed (throws, no key ever trusted)', async () => {
  const apple = loadFresh();
  global.fetch = () => Promise.resolve({ ok: false, status: 503 });
  try {
    await assert.rejects(
      () => apple.verifyIdentityToken(FAKE_TOKEN),
      (err) => { assert.match(err.message, /apple jwks HTTP 503/); return true; },
    );
  } finally {
    global.fetch = realFetch;
    unload();
  }
});