All checks were successful
Sonarqube Scanner / Build and analyze (push) Successful in 1m57s
111 lines
3.0 KiB
JavaScript
111 lines
3.0 KiB
JavaScript
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());
|
|
});
|
|
}
|
|
};
|
|
}
|