OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / rpc-origin-address.test.js

53 lines · javascript ref:
// ctx.origin.remoteAddress must survive the /rpc/ path.
//
// bare-http1 nulls req._socket when the request message ENDs
// (lib/server-connection.js, the END case), and hyper-svc's HTTP transport
// only builds its ctx AFTER `await collectBody(req)`. So on /rpc/ the socket
// was always gone by the time httpCtxFactory read it and remoteAddress landed
// as null — the one field you most want when reconstructing who called an RPC.
// SSR routes were never affected: they build ctx before consuming the body.
//
// The router's request listener now stamps req._peerAddress synchronously
// during emit('request'), before the parser reaches END, and httpCtxFactory
// falls back to it. Verified in the failing direction: null before, the real
// peer address after.

import test from 'brittle'
import process from '#node:process'
import { mkdtempSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import http from '#node:http'
import b4a from 'b4a'
import { startPearGit } from '../index.js'
process.env.PGV3_USE_RAW_IDENTITY = '1'
const tmp = (p) => mkdtempSync(join(tmpdir(), p))

test('origin.remoteAddress is populated on the /rpc/ path', async (t) => {
  const node = await startPearGit({
    storage: tmp('addr-'), uds: join(tmp('addr-s-'), 'svc.sock'),
    http: { port: 0 }, swarm: false, autoImportSSH: false
  })
  t.teardown(async () => { try { await node.close() } catch {} })
  const port = node.svc.http.server.address().port

  let seen = 'NOT-CALLED'
  // Opt in to anonymous dispatch (org #12): this probe is called without
  // credentials on purpose, since the whole point is to observe the ctx an
  // ANONYMOUS /rpc/ caller produces.
  node.svc.handle('probe-origin', async (ctx) => {
    seen = ctx.origin ? ctx.origin.remoteAddress : 'no-origin'
    return { ok: true }
  }, { anonymous: true })

  const body = JSON.stringify({})
  await new Promise((resolve, reject) => {
    const req = http.request({ method: 'POST', hostname: '127.0.0.1', port, path: '/rpc/probe-origin',
      headers: { 'Content-Type': 'application/json', 'Content-Length': b4a.byteLength(body) } },
      (res) => { res.on('data', () => {}); res.on('end', resolve) })
    req.on('error', reject); req.write(body); req.end()
  })
  t.ok(seen && seen !== 'NOT-CALLED' && seen !== 'no-origin', 'handler saw an origin')
  t.ok(typeof seen === 'string' && seen.includes('127.0.0.1'), 'remoteAddress captured: ' + seen)
})