proxy-chain can run one Server instance per listening port. Capture that listener's policy in its authentication callback:

import { Server } from 'proxy-chain';

const listeners = [
  { port: 8001, user: userA, password: passwordA, upstream: upstreamA },
  { port: 8002, user: userB, password: passwordB, upstream: upstreamB },
];
const servers = listeners.map(rule => new Server({
  host: '127.0.0.1',
  port: rule.port,
  prepareRequestFunction: ({ username, password }) => ({
    requestAuthentication:
      username !== rule.user || password !== rule.password,
    upstreamProxyUrl: rule.upstream,
  }),
}));
await Promise.all(servers.map(server => server.listen()));

The application supplies the account values and upstream URLs through its normal configuration. Each callback retains its own rule, so accepting the account on port 8001 does not accept that account on port 8002.

requestAuthentication true returns a Basic proxy challenge. When authentication is required, upstreamProxyUrl is ignored for that request. A successful inbound account and the upstream account are distinct identities.

Use a supported URL for each upstream, including properly encoded credentials when required. For an unauthenticated public HTTP upstream, choose a candidate from the HTTP proxy list and build its URL without credentials. An omitted upstream selects direct forwarding rather than a different automatic proxy.

The example binds loopback for local clients. Bind the intended interface deliberately when another machine must connect, with the corresponding access policy.

Keep the servers alive for their clients' workload. Their owner should close each server through its supported close method on shutdown; the example does not define that application's full lifecycle.

Erthyglow kevrennek