Files
zitadel/apps/login/dockerized/otel/mock-zitadel-server.js
T
fca2589fc3 fix(login): http 2 memory leak's (#11830)
# Which Problems Are Solved
All http 2 based connect-es clients caused memory leaks in our testing
using the login v2 application as the host.

# How the Problems Are Solved
Switch to connect-rpc based http 1.1 transport for the login v2

# Additional Changes
Improved the otel interceptor implementation and tests.

# Additional Context
- connect-rpc is only enabled for v2 API's so we have to make sure to
not use any v1 apis in the login but this should be the case anyways.
- Discussion #10562

---------

Co-authored-by: Max Peintner <[email protected]>
2026-03-17 14:29:21 +00:00

108 lines
3.7 KiB
JavaScript

/**
* Mock Zitadel ConnectRPC server for testing trace propagation.
* Captures incoming headers and writes them to a file for test assertions.
*/
const http = require("http");
const fs = require("fs");
const path = require("path");
const PORT = process.env.PORT || 8080;
const OUTPUT_DIR = process.env.OUTPUT_DIR || "/tmp/otel";
const HEADERS_FILE = path.join(OUTPUT_DIR, "captured-headers.json");
/**
* Safely stringify a value to JSON with XSS-preventing character escapes.
*
* Escapes `<`, `>`, `&`, and `/` to their Unicode equivalents to prevent
* script injection when the output is reflected in HTTP responses or
* embedded in HTML contexts.
*
* @param {unknown} value - The value to stringify
* @returns {string} JSON string with dangerous characters escaped
*/
function safeJsonStringify(value) {
return JSON.stringify(value, null, 2)
.replace(/</g, "\\u003c")
.replace(/>/g, "\\u003e")
.replace(/&/g, "\\u0026")
.replace(/\//g, "\\/");
}
// Store captured requests
const capturedRequests = [];
const server = http.createServer((req, res) => {
const { method, url } = req;
// Capture trace headers
const traceHeaders = {
timestamp: new Date().toISOString(),
method,
url,
traceparent: req.headers["traceparent"] || null,
tracestate: req.headers["tracestate"] || null,
baggage: req.headers["baggage"] || null,
contentType: req.headers["content-type"] || null,
};
capturedRequests.push(traceHeaders);
// Write to file for test assertions
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
fs.writeFileSync(HEADERS_FILE, JSON.stringify(capturedRequests, null, 2));
console.log(`[${traceHeaders.timestamp}] ${method} ${url}`);
console.log(` traceparent: ${traceHeaders.traceparent || "(none)"}`);
const sendResponse = (statusCode, contentType, body) => {
res.writeHead(statusCode, { "Content-Type": contentType });
res.end(body);
};
// Health check
if (url.includes("/health") || url === "/") {
sendResponse(200, "application/json", safeJsonStringify({ status: "ok" }));
return;
}
// Return captured headers (for debugging)
if (url === "/captured-headers") {
sendResponse(200, "application/json", safeJsonStringify(capturedRequests));
return;
}
// Mock ConnectRPC responses
// These are minimal responses to prevent the login app from crashing
if (url.includes("GetLoginSettings") || url.includes("SettingsService")) {
sendResponse(200, "application/json", safeJsonStringify({ settings: {} }));
} else if (url.includes("GetBrandingSettings")) {
sendResponse(200, "application/json", safeJsonStringify({ settings: {} }));
} else if (url.includes("SessionService") || url.includes("Session")) {
sendResponse(200, "application/json", safeJsonStringify({ session: {} }));
} else if (url.includes("UserService") || url.includes("User")) {
sendResponse(200, "application/json", safeJsonStringify({ user: {} }));
} else if (url.includes("OrganizationService") || url.includes("Organization")) {
sendResponse(200, "application/json", safeJsonStringify({ result: [] }));
} else if (url.includes("IdentityProviderService") || url.includes("IdentityProvider")) {
sendResponse(200, "application/json", safeJsonStringify({ identityProviders: [] }));
} else {
sendResponse(200, "application/json", safeJsonStringify({}));
}
});
server.on("error", (err) => console.error("Server error:", err));
server.listen(PORT, "0.0.0.0", () => {
console.log(`Mock Zitadel server listening on port ${PORT}`);
console.log(`Captured headers will be written to: ${HEADERS_FILE}`);
});
// Graceful shutdown
process.on("SIGTERM", () => {
console.log("Shutting down mock server...");
server.close(() => {
console.log("Mock server stopped");
process.exit(0);
});
});