mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
feat: move opkssh and warpgate into plugins (C3)
This commit is contained in:
1 parent
925da4a661
commit
98d367d7e2
336 files changed
+22380
-4746
No files matched your search
@@ -2,9 +2,9 @@
|
||||
* The Docker panel's interactive connect.
|
||||
*
|
||||
* Core's pipeline does the handshake; this only bridges its prompt channel to
|
||||
* HTTP. A connect that needs a person (a TOTP code, a Warpgate sign-in, a
|
||||
* HTTP. A connect that needs a person (a TOTP code, a browser sign-in, a
|
||||
* password the host does not store) answers the request that is waiting with
|
||||
* what to ask, and the next request (connect-totp, connect-warpgate) carries
|
||||
* what to ask, and the next request (connect-totp, connect-browser-sign-in) carries
|
||||
* the answer back and waits for whatever comes after it: the session, another
|
||||
* prompt, or an error.
|
||||
*/
|
||||
@@ -31,10 +31,10 @@ export interface ConnectStep {
|
||||
}
|
||||
|
||||
const TOTP_WAIT_MS = 3 * 60 * 1000;
|
||||
const WARPGATE_WAIT_MS = 5 * 60 * 1000;
|
||||
const BROWSER_SIGN_IN_WAIT_MS = 5 * 60 * 1000;
|
||||
const STEP_WAIT_MS = 90 * 1000;
|
||||
// Covers the longest a person may take on a prompt, plus the handshake.
|
||||
const CONNECT_TIMEOUT_MS = WARPGATE_WAIT_MS + 30 * 1000;
|
||||
const CONNECT_TIMEOUT_MS = BROWSER_SIGN_IN_WAIT_MS + 30 * 1000;
|
||||
|
||||
const PASSWORD_PROMPT = /password/i;
|
||||
|
||||
@@ -47,7 +47,7 @@ export class PendingConnect {
|
||||
|
||||
expiresAt = Date.now() + TOTP_WAIT_MS;
|
||||
/** What the open prompt expects, or null when nothing is being asked. */
|
||||
awaiting: "totp" | "warpgate" | null = null;
|
||||
awaiting: "totp" | "browser" | null = null;
|
||||
abandoned = false;
|
||||
private waiter: ((step: ConnectStep) => void) | null = null;
|
||||
private queued: ConnectStep | null = null;
|
||||
@@ -87,7 +87,7 @@ export class PendingConnect {
|
||||
});
|
||||
}
|
||||
|
||||
ask(kind: "totp" | "warpgate", waitMs: number): Promise<string | null> {
|
||||
ask(kind: "totp" | "browser", waitMs: number): Promise<string | null> {
|
||||
this.awaiting = kind;
|
||||
this.expiresAt = Date.now() + waitMs;
|
||||
return new Promise((resolve) => {
|
||||
@@ -203,22 +203,23 @@ export async function startConnect(
|
||||
switch (request.kind) {
|
||||
case "totp":
|
||||
return parkTotp(request.prompt, request.retry ? { retry: true } : {});
|
||||
case "warpgate": {
|
||||
case "browser": {
|
||||
logs.push(
|
||||
connectionLog(
|
||||
"info",
|
||||
"docker_auth",
|
||||
"Warpgate authentication required",
|
||||
`${request.label} sign-in required`,
|
||||
),
|
||||
);
|
||||
const answer = pending.ask("warpgate", WARPGATE_WAIT_MS);
|
||||
const answer = pending.ask("browser", BROWSER_SIGN_IN_WAIT_MS);
|
||||
pending.emit({
|
||||
status: 200,
|
||||
body: {
|
||||
requires_warpgate: true,
|
||||
requires_browser_sign_in: true,
|
||||
sessionId,
|
||||
label: request.label,
|
||||
url: request.url,
|
||||
securityKey: request.securityKey,
|
||||
code: request.code,
|
||||
connectionLogs: logs,
|
||||
},
|
||||
});
|
||||
@@ -378,7 +379,7 @@ export async function answerConnect(
|
||||
sessions: DockerSessions,
|
||||
sessionId: string,
|
||||
userId: string,
|
||||
kind: "totp" | "warpgate",
|
||||
kind: "totp" | "browser",
|
||||
value: string,
|
||||
): Promise<ConnectStep> {
|
||||
const pending = sessions.getPending(sessionId);
|
||||
@@ -393,8 +394,8 @@ export async function answerConnect(
|
||||
status: 400,
|
||||
body: {
|
||||
error:
|
||||
kind === "warpgate"
|
||||
? "Session is not a Warpgate session"
|
||||
kind === "browser"
|
||||
? "Session is not waiting for a browser sign-in"
|
||||
: "Session is not waiting for a code",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -104,7 +104,7 @@ export function registerRoutes(router: Router, { ctx, sessions, log }: Deps) {
|
||||
* /plugin-api/docker/ssh/connect:
|
||||
* post:
|
||||
* summary: Establish SSH session for Docker
|
||||
* description: Opens an SSH session to a host for Docker operations. When the host asks for a code, a Warpgate sign-in or a password, the reply says so and the session waits for connect-totp or connect-warpgate.
|
||||
* description: Opens an SSH session to a host for Docker operations. When the host asks for a code, a browser sign-in or a password, the reply says so and the session waits for connect-totp or connect-browser-sign-in.
|
||||
* tags:
|
||||
* - Docker
|
||||
* requestBody:
|
||||
@@ -311,10 +311,10 @@ export function registerRoutes(router: Router, { ctx, sessions, log }: Deps) {
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /plugin-api/docker/ssh/connect-warpgate:
|
||||
* /plugin-api/docker/ssh/connect-browser-sign-in:
|
||||
* post:
|
||||
* summary: Complete Warpgate authentication
|
||||
* description: Continues a connect after the user finished the Warpgate sign-in in the browser.
|
||||
* summary: Continue after a browser sign-in
|
||||
* description: Continues a connect after the user finished signing in in the browser (an SSH gateway's approval, for example).
|
||||
* tags:
|
||||
* - Docker
|
||||
* requestBody:
|
||||
@@ -332,13 +332,13 @@ export function registerRoutes(router: Router, { ctx, sessions, log }: Deps) {
|
||||
* 200:
|
||||
* description: Connected, or another prompt.
|
||||
* 400:
|
||||
* description: Session ID required, or the session is not a Warpgate session.
|
||||
* description: Session ID required, or the session is not waiting for a browser sign-in.
|
||||
* 404:
|
||||
* description: The connect expired.
|
||||
* 408:
|
||||
* description: The host did not answer in time.
|
||||
*/
|
||||
router.post("/ssh/connect-warpgate", async (req, res) => {
|
||||
router.post("/ssh/connect-browser-sign-in", async (req, res) => {
|
||||
const userId = userOf(res);
|
||||
if (!userId) return;
|
||||
const { sessionId } = (req.body ?? {}) as Record<string, unknown>;
|
||||
@@ -349,7 +349,7 @@ export function registerRoutes(router: Router, { ctx, sessions, log }: Deps) {
|
||||
sessions,
|
||||
sessionId,
|
||||
userId,
|
||||
"warpgate",
|
||||
"browser",
|
||||
"",
|
||||
);
|
||||
return res.status(step.status).json(step.body);
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface DockerSession {
|
||||
cancelIdle?: () => void;
|
||||
}
|
||||
|
||||
/** A connect still waiting on a person (TOTP, Warpgate). */
|
||||
/** A connect still waiting on a person (TOTP, a browser sign-in). */
|
||||
export interface PendingEntry {
|
||||
userId: string;
|
||||
hostId: number;
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
Select2,
|
||||
Separator,
|
||||
TOTPDialog,
|
||||
WarpgateDialog,
|
||||
BrowserSignInDialog,
|
||||
logActivity,
|
||||
useAdaptivePolling,
|
||||
useAreaPreferences,
|
||||
@@ -57,12 +57,13 @@ interface DockerManagerProps {
|
||||
}
|
||||
|
||||
interface PromptState {
|
||||
kind: "totp" | "warpgate";
|
||||
kind: "totp" | "browser";
|
||||
sessionId: string;
|
||||
prompt?: string;
|
||||
isPassword?: boolean;
|
||||
url?: string;
|
||||
securityKey?: string;
|
||||
code?: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
function newSessionId(): string {
|
||||
@@ -193,12 +194,13 @@ function DockerManagerInner({
|
||||
) => {
|
||||
if (result.connectionLogs) setLogs(result.connectionLogs);
|
||||
|
||||
if (result.requires_warpgate) {
|
||||
if (result.requires_browser_sign_in) {
|
||||
setPrompt({
|
||||
kind: "warpgate",
|
||||
kind: "browser",
|
||||
sessionId: sid,
|
||||
url: result.url || "",
|
||||
securityKey: result.securityKey || "",
|
||||
code: result.code || "",
|
||||
label: result.label || "",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -357,15 +359,19 @@ function DockerManagerInner({
|
||||
}
|
||||
};
|
||||
|
||||
const handleWarpgateContinue = async () => {
|
||||
if (!prompt || prompt.kind !== "warpgate") return;
|
||||
const handleBrowserSignInContinue = async () => {
|
||||
if (!prompt || prompt.kind !== "browser") return;
|
||||
const sid = prompt.sessionId;
|
||||
setPrompt(null);
|
||||
setIsConnecting(true);
|
||||
try {
|
||||
await applyConnectResult(sid, await docker.continueWarpgate(sid), false);
|
||||
await applyConnectResult(
|
||||
sid,
|
||||
await docker.continueBrowserSignIn(sid),
|
||||
false,
|
||||
);
|
||||
} catch (error) {
|
||||
reportFailure(error, t("docker.warpgateVerificationFailed"));
|
||||
reportFailure(error, t("docker.browserSignInFailed"));
|
||||
} finally {
|
||||
setIsConnecting(false);
|
||||
}
|
||||
@@ -379,7 +385,7 @@ function DockerManagerInner({
|
||||
closeTab();
|
||||
};
|
||||
|
||||
const handleWarpgateOpenUrl = () => {
|
||||
const handleBrowserSignInOpenUrl = () => {
|
||||
if (prompt?.url) window.open(prompt.url, "_blank", "noopener,noreferrer");
|
||||
};
|
||||
|
||||
@@ -475,13 +481,14 @@ function DockerManagerInner({
|
||||
onSubmit={handleTotpSubmit}
|
||||
onCancel={handlePromptCancel}
|
||||
/>
|
||||
<WarpgateDialog
|
||||
isOpen={prompt?.kind === "warpgate"}
|
||||
<BrowserSignInDialog
|
||||
isOpen={prompt?.kind === "browser"}
|
||||
label={prompt?.label ?? ""}
|
||||
url={prompt?.url ?? ""}
|
||||
securityKey={prompt?.securityKey ?? ""}
|
||||
onContinue={handleWarpgateContinue}
|
||||
code={prompt?.code ?? ""}
|
||||
onContinue={handleBrowserSignInContinue}
|
||||
onCancel={handlePromptCancel}
|
||||
onOpenUrl={handleWarpgateOpenUrl}
|
||||
onOpenUrl={handleBrowserSignInOpenUrl}
|
||||
/>
|
||||
{currentHost && (
|
||||
<SSHAuthDialog
|
||||
|
||||
@@ -28,9 +28,10 @@ export interface ConnectResult {
|
||||
prompt?: string;
|
||||
isPassword?: boolean;
|
||||
retry?: boolean;
|
||||
requires_warpgate?: boolean;
|
||||
requires_browser_sign_in?: boolean;
|
||||
url?: string | null;
|
||||
securityKey?: string;
|
||||
code?: string;
|
||||
label?: string;
|
||||
connectionLogs?: ApiConnectionLog[];
|
||||
}
|
||||
|
||||
@@ -101,9 +102,9 @@ export function createDockerApi(api: PluginApiClient) {
|
||||
api.post("/ssh/connect-totp", { sessionId, totpCode }),
|
||||
),
|
||||
|
||||
continueWarpgate: (sessionId: string) =>
|
||||
call<ConnectResult>("complete Warpgate authentication", () =>
|
||||
api.post("/ssh/connect-warpgate", { sessionId }),
|
||||
continueBrowserSignIn: (sessionId: string) =>
|
||||
call<ConnectResult>("continue after a browser sign-in", () =>
|
||||
api.post("/ssh/connect-browser-sign-in", { sessionId }),
|
||||
),
|
||||
|
||||
disconnect: (sessionId: string) =>
|
||||
|
||||
Reference in new issue
Block a user