import http from 'node:http'; const PROTECTED_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']); function increment(counter, method) { counter[method] = (counter[method] || 0) + 1; } function basicAuthorization(username, password) { const token = Buffer.from(`${username}:${password}`, 'utf8').toString('base64'); return `Basic ${token}`; } function unauthorized(response) { response.writeHead(401, { 'Content-Type': 'application/json; charset=utf-8', 'WWW-Authenticate': 'Basic realm="Wegwichtel local test"' }); response.end(JSON.stringify({ error: 'Unauthorized', message: 'Für schreibende Operationen sind Zugangsdaten erforderlich.' })); } function badGateway(response, error) { if (response.headersSent) { response.destroy(error); return; } response.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' }); response.end(JSON.stringify({ error: 'BadGateway', message: `Lokaler Testserver nicht erreichbar: ${error.message}` })); } export async function startLocalWriteAuthProxy({ listenHost = '127.0.0.1', listenPort = 47145, targetHost = '127.0.0.1', targetPort, username, password }) { if (!targetPort) throw new TypeError('targetPort ist erforderlich.'); if (!username || !password) throw new TypeError('Test-Credentials sind erforderlich.'); const expectedAuthorization = basicAuthorization(username, password); const stats = { unauthorizedWrites: {}, authorizedWrites: {}, forwardedReads: 0, authenticatedReads: 0 }; const server = http.createServer((request, response) => { const method = (request.method || 'GET').toUpperCase(); const isProtected = PROTECTED_METHODS.has(method); const suppliedAuthorization = request.headers.authorization; if (isProtected && suppliedAuthorization !== expectedAuthorization) { increment(stats.unauthorizedWrites, method); request.resume(); unauthorized(response); return; } if (isProtected) { increment(stats.authorizedWrites, method); } else { stats.forwardedReads += 1; if (suppliedAuthorization) stats.authenticatedReads += 1; } const headers = { ...request.headers, host: `${targetHost}:${targetPort}` }; delete headers.authorization; const upstream = http.request({ host: targetHost, port: targetPort, method, path: request.url, headers }, upstreamResponse => { response.writeHead(upstreamResponse.statusCode || 502, upstreamResponse.headers); upstreamResponse.pipe(response); }); upstream.on('error', error => badGateway(response, error)); request.pipe(upstream); }); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(listenPort, listenHost, () => { server.off('error', reject); resolve(); }); }); return { stats, async close() { if (!server.listening) return; await new Promise((resolve, reject) => { server.close(error => error ? reject(error) : resolve()); }); } }; }