OmniRoute/scripts/dev/tls-options.mjs
Diego Rodrigues de Sa e Souza 78f09c8d9f
Some checks are pending
Publish Fork Image to GHCR / Build and Push Fork Image (push) Waiting to run
CI / Change Classification (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Quality Ratchet (push) Blocked by required conditions
CI / Quality Gates (Extended) (push) Waiting to run
CI / Docs Sync (Strict) (push) Waiting to run
CI / Docs Lint (prose — advisory) (push) Waiting to run
CI / i18n UI Coverage (push) Waiting to run
CI / Build language matrix (push) Waiting to run
CI / i18n Validation (push) Blocked by required conditions
CI / E2E Tests (4/9) (push) Blocked by required conditions
CI / PR Test Policy (push) Waiting to run
CI / Build (push) Blocked by required conditions
CI / Package Artifact (push) Blocked by required conditions
CI / Electron Package Smoke (push) Blocked by required conditions
CI / Unit Tests (1/8) (push) Blocked by required conditions
CI / Unit Tests (2/8) (push) Blocked by required conditions
CI / Unit Tests (3/8) (push) Blocked by required conditions
CI / Unit Tests (4/8) (push) Blocked by required conditions
CI / Unit Tests (5/8) (push) Blocked by required conditions
CI / E2E Tests (5/9) (push) Blocked by required conditions
CI / Unit Tests (6/8) (push) Blocked by required conditions
CI / Unit Tests (7/8) (push) Blocked by required conditions
CI / Unit Tests (8/8) (push) Blocked by required conditions
CI / Vitest (MCP / autoCombo / UI components) (push) Blocked by required conditions
CI / Node 24 Compatibility Tests (1/4) (push) Blocked by required conditions
CI / Node 24 Compatibility Tests (2/4) (push) Blocked by required conditions
CI / Node 24 Compatibility Tests (3/4) (push) Blocked by required conditions
CI / Node 24 Compatibility Tests (4/4) (push) Blocked by required conditions
CI / Node 26 Compatibility Build (push) Blocked by required conditions
CI / Node 26 Compatibility Tests (1/4) (push) Blocked by required conditions
CI / Node 26 Compatibility Tests (2/4) (push) Blocked by required conditions
CI / Node 26 Compatibility Tests (3/4) (push) Blocked by required conditions
CI / Node 26 Compatibility Tests (4/4) (push) Blocked by required conditions
CI / Coverage Shard (1/8) (push) Blocked by required conditions
CI / Coverage Shard (2/8) (push) Blocked by required conditions
CI / Coverage Shard (3/8) (push) Blocked by required conditions
CI / Coverage Shard (4/8) (push) Blocked by required conditions
CI / Coverage Shard (5/8) (push) Blocked by required conditions
CI / Coverage Shard (6/8) (push) Blocked by required conditions
CI / Coverage Shard (7/8) (push) Blocked by required conditions
CI / Coverage Shard (8/8) (push) Blocked by required conditions
CI / Coverage (push) Blocked by required conditions
CI / SonarQube (push) Blocked by required conditions
CI / PR Coverage Comment (push) Blocked by required conditions
CI / E2E Tests (1/9) (push) Blocked by required conditions
CI / E2E Tests (2/9) (push) Blocked by required conditions
CI / E2E Tests (3/9) (push) Blocked by required conditions
CI / E2E Tests (6/9) (push) Blocked by required conditions
CI / E2E Tests (7/9) (push) Blocked by required conditions
CI / E2E Tests (8/9) (push) Blocked by required conditions
CI / E2E Tests (9/9) (push) Blocked by required conditions
CI / Integration Tests (1/2) (push) Blocked by required conditions
CI / Integration Tests (2/2) (push) Blocked by required conditions
CI / Security Tests (push) Blocked by required conditions
CI / CI Dashboard (push) Blocked by required conditions
Publish to Docker Hub / Resolve Docker release metadata (push) Waiting to run
Publish to Docker Hub / Build Docker (linux/amd64) (push) Blocked by required conditions
Publish to Docker Hub / Build Docker (linux/arm64) (push) Blocked by required conditions
Publish to Docker Hub / Publish multi-arch manifests (push) Blocked by required conditions
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
semgrep / semgrep (push) Waiting to run
Wiki Sync / Sync wiki with docs (push) Waiting to run
Release v3.8.41 (#5327)
Release v3.8.41 — 52 commits since v3.8.40 (19 CHANGELOG bullets, 11 contributors).

All gating CI green: Unit×8, Coverage×8, Vitest, Package Artifact, Quality Ratchet, CodeQL, Lint, Docs Sync (Strict), Node 24/26 compat, E2E×9, Integration, Electron smoke.

Advisory checks overridden (main unprotected): PR Test Policy = test-masking heuristic on the cumulative 52-commit assert delta (legitimate dead-code-sweep removals + consolidations, reviewed per-PR); SonarCloud/SonarQube = new-code maintainability/coverage quality gate (CodeQL/Semgrep/Security/npm-audit/Dependabot all clean — not a security finding).
2026-06-29 16:51:03 -03:00

92 lines
4 KiB
JavaScript

// scripts/dev/tls-options.mjs
//
// Pure, dependency-light helpers for OmniRoute's opt-in native HTTPS/TLS serving
// (#5242, Bug 1C). Kept side-effect-free and free of heavy imports so it can be
// imported both by the CLI (bin/cli/commands/serve.mjs) and by the standalone
// server wrapper (standalone-server-ws.mjs), and unit-tested in isolation.
//
// TLS is strictly opt-in: when neither cert nor key is provided the server
// behaves EXACTLY as before (plain HTTP). A misconfiguration (only one of the
// pair, or an unreadable path) NEVER crashes the server — it logs a warning and
// falls back to HTTP, preserving today's behavior and loopback/security posture.
import fs from "node:fs";
import http from "node:http";
import https from "node:https";
/**
* Resolve TLS options from environment variables.
*
* Reads `OMNIROUTE_TLS_CERT` and `OMNIROUTE_TLS_KEY` (filesystem paths). Returns
* `{ cert, key }` (file contents) only when BOTH are provided and readable.
* Otherwise returns `null` so the caller serves plain HTTP — never throwing.
*
* @param {NodeJS.ProcessEnv} [env=process.env]
* @param {{ readFileSync?: typeof fs.readFileSync, warn?: (msg: string) => void }} [deps]
* @returns {{ cert: Buffer|string, key: Buffer|string, certPath: string, keyPath: string } | null}
*/
export function resolveTlsOptions(
env = process.env,
{ readFileSync = fs.readFileSync, warn = (m) => console.warn(m) } = {}
) {
const certPath = typeof env?.OMNIROUTE_TLS_CERT === "string" ? env.OMNIROUTE_TLS_CERT.trim() : "";
const keyPath = typeof env?.OMNIROUTE_TLS_KEY === "string" ? env.OMNIROUTE_TLS_KEY.trim() : "";
// Neither provided → plain HTTP, no warning (the common, default case).
if (!certPath && !keyPath) return null;
// Only one of the pair → never half-enable TLS. Warn + fall back to HTTP.
if (!certPath || !keyPath) {
warn(
`[omniroute][tls] HTTPS not enabled: both OMNIROUTE_TLS_CERT and OMNIROUTE_TLS_KEY ` +
`are required (only ${certPath ? "cert" : "key"} provided). Serving HTTP.`
);
return null;
}
// Both provided → read them. A bad/unreadable path falls back to HTTP rather
// than crashing the server over a TLS misconfiguration.
try {
const cert = readFileSync(certPath);
const key = readFileSync(keyPath);
return { cert, key, certPath, keyPath };
} catch (err) {
warn(
`[omniroute][tls] HTTPS not enabled: could not read TLS cert/key ` +
`(${err?.code || err?.message || String(err)}). Serving HTTP.`
);
return null;
}
}
/**
* Create either an `https.Server` (when `tlsOptions` is provided) or an
* `http.Server` (when it is `null`), forwarding the same request-listener /
* options arguments the caller would otherwise pass to `http.createServer`.
*
* When TLS is enabled, any leading options object is merged with `cert`/`key`
* and the trailing request listener is preserved, so existing wiring (WebSocket
* `upgrade` handling, request wrappers) keeps working over TLS unchanged.
*
* @param {any[]} args - the args originally passed to http.createServer (options?, listener?)
* @param {{ cert: Buffer|string, key: Buffer|string } | null} tlsOptions
* @param {{ createHttp?: Function, createHttps?: Function }} [deps]
* @returns {import("node:http").Server | import("node:https").Server}
*/
export function createServerListener(
args,
tlsOptions,
{ createHttp = http.createServer.bind(http), createHttps = https.createServer.bind(https) } = {}
) {
const argList = Array.isArray(args) ? args : args === undefined ? [] : [args];
if (!tlsOptions) return createHttp(...argList);
const { cert, key } = tlsOptions;
const lastFnIdx = argList.map((a) => typeof a === "function").lastIndexOf(true);
const listener = lastFnIdx >= 0 ? argList[lastFnIdx] : undefined;
const baseOpts =
argList[0] && typeof argList[0] === "object" && !Buffer.isBuffer(argList[0]) ? argList[0] : {};
const merged = { ...baseOpts, cert, key };
return listener ? createHttps(merged, listener) : createHttps(merged);
}