mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
* feat: shell controller, TabStage, layout and theme plugins with repair view * feat: move the classic layout into its own bundled plugin * feat: move color themes into a bundled themes plugin * fix: plugin tests and type checks work with a linked sdk * fix: home tab, layout switch and missing layout fallbacks for looks * chore: add classic layout and themes to the readme * chore: drop example plugin links from the plugin guide * feat: plugin filters by type, paged browse, hide plugins needing newer termix * feat: move desktop, status, import, audit and branding features into plugins * feat: host lock api so a plugin can load hosts from a config file (#1581) * chore: add hosts config to the plugin table * feat: login policy hook for plugins and optional https redirect (#1584) * chore: add login security to the plugin table * feat: api key socket tickets so a master can open remote sessions (#1654) * feat: sync moves to a plugin, desktop gets multiple accounts (#1580, #1654) * chore: add sync to the plugin table * feat: move the sync screens and wizard out of core into the plugin (#1580) * fix: add a neutral linked desktop hook for plugins (#1580) * fix: show the linked account's name and admin flag in the app (#1580) * feat: ssh transport providers, core ssh relay and plugin folders api * feat: support FIDO2 security keys through ssh agent * feat: add companion panels beside host tabs * feat: mTLS client certificates for desktop server links * feat: native web view for plugins in the desktop app * fix: detect the desktop link window by Electron's default user agent Fixes #1675 * fix: changelog for desktop link fix * fix: keep text primary keys in the plugin data export * fix: ship sharp binaries for Windows arm64 builds * feat: client cert on the sync link types (sdk 1.0.18) * chore: update translations * feat: themed title bar for the desktop app (#1690) * chore: changelog for the themed title bar * fix: draw the title bar in the sidebar color * feat: per host layout for the terminal panel bar * chore: sdk 1.0.19 for the panel bar layout * chore: bundle all 43 plugins for 26.10.3 * chore: update translations --------- Co-authored-by: SirHephaistos <60616553+SirHephaistos@users.noreply.github.com> Co-authored-by: Giovanny Aranda <garanda21@gmail.com>
89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
import type { Request } from "express";
|
|
import type { UserRecord } from "../repositories/user-repository.js";
|
|
|
|
export function isLoopbackRequest(req: Request): boolean {
|
|
// Requests relayed by the bundled nginx always carry X-Real-IP, which
|
|
// nginx overwrites with the actual client address -- so its presence
|
|
// means the caller reached the backend through the reverse proxy and is
|
|
// not a local process, whatever the TCP peer address says (it is nginx
|
|
// itself on loopback). Everything else is judged by the TCP peer
|
|
// address, which no client-supplied header can influence: with
|
|
// `trust proxy = true`, req.ip comes from X-Forwarded-For and would let
|
|
// any remote caller claim to be loopback.
|
|
if (req.headers["x-real-ip"]) return false;
|
|
|
|
const ip = req.socket?.remoteAddress || "";
|
|
return (
|
|
ip === "127.0.0.1" ||
|
|
ip === "::1" ||
|
|
ip === "::ffff:127.0.0.1" ||
|
|
ip.endsWith(":127.0.0.1")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Whether this backend may hand out the desktop auto-session at all: the
|
|
* embedded desktop backend, or a development backend the desktop app points
|
|
* at. A production server never does, whoever asks.
|
|
*/
|
|
export function allowsDesktopAutoSession(
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
): boolean {
|
|
return env.ELECTRON_EMBEDDED === "true" || env.NODE_ENV !== "production";
|
|
}
|
|
|
|
export function extractBearerOrCookieToken(req: Request): string | undefined {
|
|
const cookieToken = (req as Request & { cookies?: Record<string, string> })
|
|
.cookies?.jwt;
|
|
if (cookieToken) return cookieToken;
|
|
|
|
const authHeader = req.headers["authorization"];
|
|
if (authHeader?.startsWith("Bearer ")) {
|
|
return authHeader.slice("Bearer ".length);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function isNativeTokenExportRequest(req: Request): boolean {
|
|
const userAgent = req.get("user-agent") || "";
|
|
return /^(Termix-Mobile|Termix-Desktop)\//.test(userAgent);
|
|
}
|
|
|
|
/**
|
|
* Decides who the desktop auto-session endpoint should silently log in as.
|
|
*
|
|
* The local embedded backend's trust boundary is machine access (loopback),
|
|
* not any individual account's credentials -- anyone who can reach loopback
|
|
* already has full filesystem access to the local, encrypted-at-rest
|
|
* database and its keys. A login form must never appear for the local
|
|
* backend, under any circumstance, including a local database that ended
|
|
* up with more than one user (e.g. from repeated manual registration
|
|
* during testing, or a household sharing one install) -- a user in that
|
|
* state deserves to get into the app they installed, not a confusing,
|
|
* unexplained dead end. So this always returns a single, deterministic
|
|
* user: the account the user switched to if it still exists, else the admin
|
|
* account if one exists, else the earliest-registered account. It never
|
|
* returns null when there are users.
|
|
*/
|
|
export function resolveDesktopAutoSessionUser(
|
|
allUsers: UserRecord[],
|
|
preferredId?: string | null,
|
|
): UserRecord | null {
|
|
if (allUsers.length === 0) return null;
|
|
if (allUsers.length === 1) return allUsers[0];
|
|
|
|
// An account the user switched to on purpose, while it still exists.
|
|
const preferred = preferredId
|
|
? allUsers.find((user) => user.id === preferredId)
|
|
: undefined;
|
|
if (preferred) return preferred;
|
|
|
|
const admin = allUsers.find((user) => user.isAdmin);
|
|
if (admin) return admin;
|
|
|
|
return [...allUsers].sort(
|
|
(a, b) =>
|
|
new Date(a.registeredAt).getTime() - new Date(b.registeredAt).getTime(),
|
|
)[0];
|
|
}
|