mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +00:00
81 lines
3.1 KiB
TypeScript
81 lines
3.1 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 admin account if one exists, else the earliest-registered
|
|
* account. It never returns null.
|
|
*/
|
|
export function resolveDesktopAutoSessionUser(
|
|
allUsers: UserRecord[],
|
|
): UserRecord | null {
|
|
if (allUsers.length === 0) return null;
|
|
if (allUsers.length === 1) return allUsers[0];
|
|
|
|
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];
|
|
}
|