Files
Termix/electron/linked-server.cjs
T

99 lines
3.0 KiB
JavaScript

// The server this desktop is linked to, as the embedded backend reports it.
// The backend owns the link (and its secrets); main only keeps what it needs
// to reach that one origin: the proxy headers and basic auth a reverse proxy
// in front of it wants, whether its certificate may be self-signed, and the
// session for requests main makes itself (C2S tunnels).
let linked = null;
function getOrigin(url) {
try {
return new URL(url).origin;
} catch {
return null;
}
}
function setLinkedServer(config) {
if (!config || !config.origin) {
linked = null;
return;
}
linked = {
origin: config.origin,
serverUrl: config.serverUrl || config.origin,
token: config.token || null,
headers: Array.isArray(config.headers) ? config.headers : [],
basicAuth:
config.basicAuth && config.basicAuth.username ? config.basicAuth : null,
allowInvalidCertificate: !!config.allowInvalidCertificate,
};
}
function getLinkedServer() {
return linked;
}
function isLinkedOrigin(url) {
return !!linked && getOrigin(url) === linked.origin;
}
function basicAuthValue(basicAuth) {
return `Basic ${Buffer.from(
`${basicAuth.username}:${basicAuth.password || ""}`,
).toString("base64")}`;
}
// Adds the link's proxy headers to a request bound for the linked origin.
// With basic auth in front, Authorization belongs to the proxy, so a Termix
// bearer token moves into the jwt cookie, which Termix reads first.
function applyLinkHeaders(url, headers) {
if (!isLinkedOrigin(url)) return headers;
const out = { ...headers };
for (const header of linked.headers) {
if (header && header.name && header.value) out[header.name] = header.value;
}
if (linked.basicAuth) {
const authKey = Object.keys(out).find(
(key) => key.toLowerCase() === "authorization",
);
const current = authKey ? String(out[authKey]) : "";
if (authKey) delete out[authKey];
const bearer = current.match(/^Bearer\s+(.+)$/i);
if (bearer) {
const cookieKey =
Object.keys(out).find((key) => key.toLowerCase() === "cookie") ||
"Cookie";
const existing = out[cookieKey] ? `${out[cookieKey]}; ` : "";
out[cookieKey] = `${existing}jwt=${bearer[1]}`;
}
out.Authorization = basicAuthValue(linked.basicAuth);
}
return out;
}
// Headers for a request main itself makes to the linked server.
function linkedRequestHeaders(extra = {}) {
if (!linked) return { ...extra };
const base = { "X-Electron-App": "true", ...extra };
if (linked.token) base.Authorization = `Bearer ${linked.token}`;
return applyLinkHeaders(linked.origin, base);
}
// Answers a basic auth challenge from the linked origin's proxy.
function answerLogin(authInfo, url) {
if (!linked || !linked.basicAuth) return null;
if (authInfo && authInfo.isProxy) return null;
if (!isLinkedOrigin(url)) return null;
return linked.basicAuth;
}
module.exports = {
setLinkedServer,
getLinkedServer,
isLinkedOrigin,
applyLinkHeaders,
linkedRequestHeaders,
answerLogin,
};