mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07: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>
99 lines
3.5 KiB
TypeScript
99 lines
3.5 KiB
TypeScript
import type { AuthenticatedRequest } from "../../../types/index.js";
|
|
import type { RequestHandler, Router } from "express";
|
|
import type { AuthManager } from "../../utils/auth-manager.js";
|
|
import { authLogger } from "../../utils/logger.js";
|
|
import { isCookieOriginAllowed } from "../../utils/ws-auth.js";
|
|
|
|
type UserSocketTicketRoutesDeps = {
|
|
authenticateJWT: RequestHandler;
|
|
authManager: AuthManager;
|
|
};
|
|
|
|
export function registerUserSocketTicketRoutes(
|
|
router: Router,
|
|
{ authenticateJWT, authManager }: UserSocketTicketRoutesDeps,
|
|
): void {
|
|
/**
|
|
* @openapi
|
|
* /users/ws-ticket:
|
|
* post:
|
|
* summary: Get a WebSocket ticket
|
|
* description: Returns a token that is valid for one minute and only opens plugin WebSockets. The web app sends it as a subprotocol, so sockets work behind a proxy that rewrites the Host header. Only a page on this server can get one.
|
|
* tags:
|
|
* - Users
|
|
* responses:
|
|
* 200:
|
|
* description: The ticket.
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* ticket:
|
|
* type: string
|
|
* 401:
|
|
* description: Not authenticated, or not a login session.
|
|
* 403:
|
|
* description: The request came from a page on another site.
|
|
* 500:
|
|
* description: Failed to issue the ticket.
|
|
*/
|
|
router.post("/ws-ticket", authenticateJWT, async (req, res) => {
|
|
const authReq = req as AuthenticatedRequest;
|
|
const sessionId = authReq.sessionId;
|
|
if (!sessionId || authReq.apiKeyId) {
|
|
return res.status(401).json({ error: "A login session is required" });
|
|
}
|
|
// The cookie rides along from sibling subdomains too, so the ticket only
|
|
// goes to a page the browser says is this server.
|
|
if (req.cookies?.jwt && !isCookieOriginAllowed(req)) {
|
|
return res.status(403).json({ error: "Not allowed from this origin" });
|
|
}
|
|
try {
|
|
const ticket = await authManager.issueSocketTicket(
|
|
authReq.userId,
|
|
sessionId,
|
|
);
|
|
res.setHeader("Cache-Control", "no-store");
|
|
res.json({ ticket });
|
|
} catch (err) {
|
|
authLogger.error("Failed to issue a socket ticket", err);
|
|
res.status(500).json({ error: "Failed to issue a socket ticket" });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @openapi
|
|
* /users/ws-ticket/api-key:
|
|
* post:
|
|
* summary: Get a WebSocket ticket for an API key
|
|
* description: For another Termix server that manages this one with an API key. Returns a token that is valid for one minute and only opens plugin WebSockets, as the key's user. It cannot call any other route.
|
|
* tags:
|
|
* - Users
|
|
* responses:
|
|
* 200:
|
|
* description: The ticket.
|
|
* 401:
|
|
* description: The request did not use an API key.
|
|
* 500:
|
|
* description: Failed to issue the ticket.
|
|
*/
|
|
router.post("/ws-ticket/api-key", authenticateJWT, async (req, res) => {
|
|
const authReq = req as AuthenticatedRequest;
|
|
if (!authReq.apiKeyId) {
|
|
return res.status(401).json({ error: "An API key is required" });
|
|
}
|
|
try {
|
|
const ticket = await authManager.issueApiKeySocketTicket(
|
|
authReq.userId,
|
|
authReq.apiKeyId,
|
|
);
|
|
res.setHeader("Cache-Control", "no-store");
|
|
res.json({ ticket });
|
|
} catch (err) {
|
|
authLogger.error("Failed to issue an API key socket ticket", err);
|
|
res.status(500).json({ error: "Failed to issue a socket ticket" });
|
|
}
|
|
});
|
|
}
|