feat: move opkssh and warpgate into plugins (C3)

This commit is contained in:
LukeGus committed 2026-09-24 19:28:19 -05:00
1 parent 925da4a661
commit 98d367d7e2
336 files changed
+22380 -4746

No files matched your search

+1 -1
View File
@@ -316,7 +316,7 @@
"connecting": "Connecting...",
"connectedSuccessfully": "Connected successfully",
"totpVerificationFailed": "TOTP verification failed",
"warpgateVerificationFailed": "Warpgate authentication failed",
"browserSignInFailed": "Sign-in failed. Please try again.",
"authenticationFailed": "Authentication failed",
"incorrectPassphrase": "Incorrect passphrase. Please try again.",
"verificationCodePrompt": "Verification code:",
@@ -290,7 +290,6 @@
"connecting": "Verbind...",
"connectedSuccessfully": "Suksesvol gekoppel",
"totpVerificationFailed": "TOTP-verifikasie het misluk",
"warpgateVerificationFailed": "Warpgate-verifikasie het misluk",
"authenticationFailed": "Verifikasie het misluk",
"incorrectPassphrase": "Verkeerde wagwoordfrase. Probeer asseblief weer.",
"verificationCodePrompt": "Verifikasiekode:",
@@ -290,7 +290,6 @@
"connecting": "جارٍ الاتصال...",
"connectedSuccessfully": "تم الاتصال بنجاح",
"totpVerificationFailed": "فشل التحقق من TOTP",
"warpgateVerificationFailed": "فشلت مصادقة Warpgate",
"authenticationFailed": "فشلت المصادقة",
"incorrectPassphrase": "عبارة المرور غير صحيحة. يرجى المحاولة مرة أخرى.",
"verificationCodePrompt": "رمز التحقق:",
@@ -290,7 +290,6 @@
"connecting": "Свързване...",
"connectedSuccessfully": "Свързаването е осъществено успешно",
"totpVerificationFailed": "Проверката на TOTP не бе успешна",
"warpgateVerificationFailed": "Удостоверяването на Warpgate не беше успешно",
"authenticationFailed": "Удостоверяването не бе успешно",
"incorrectPassphrase": "Неправилна парола. Моля, опитайте отново.",
"verificationCodePrompt": "Код за потвърждение:",
@@ -290,7 +290,6 @@
"connecting": "সংযোগ স্থাপন...",
"connectedSuccessfully": "সফলভাবে সংযুক্ত হয়েছে",
"totpVerificationFailed": "TOTP যাচাইকরণ ব্যর্থ হয়েছে",
"warpgateVerificationFailed": "ওয়ার্পগেট প্রমাণীকরণ ব্যর্থ হয়েছে",
"authenticationFailed": "প্রমাণীকরণ ব্যর্থ হয়েছে",
"incorrectPassphrase": "ভুল পাসফ্রেজ। অনুগ্রহ করে আবার চেষ্টা করুন।",
"verificationCodePrompt": "যাচাইকরণ কোড:",
@@ -290,7 +290,6 @@
"connecting": "Connectant...",
"connectedSuccessfully": "Connexió correcta",
"totpVerificationFailed": "La verificació TOTP ha fallat",
"warpgateVerificationFailed": "L'autenticació de Warpgate ha fallat",
"authenticationFailed": "L'autenticació ha fallat",
"incorrectPassphrase": "Contrasenya incorrecta. Si us plau, torneu-ho a intentar.",
"verificationCodePrompt": "Codi de verificació:",
@@ -290,7 +290,6 @@
"connecting": "Spojovací...",
"connectedSuccessfully": "Úspěšné připojení",
"totpVerificationFailed": "Ověření TOTP se nezdařilo",
"warpgateVerificationFailed": "Ověření Warpgate selhalo",
"authenticationFailed": "Ověření se nezdařilo",
"incorrectPassphrase": "Nesprávné heslo. Zkuste to prosím znovu.",
"verificationCodePrompt": "Ověřovací kód:",
@@ -290,7 +290,6 @@
"connecting": "Forbinder...",
"connectedSuccessfully": "Forbindelsen er oprettet",
"totpVerificationFailed": "TOTP-bekræftelse mislykkedes",
"warpgateVerificationFailed": "Warpgate-godkendelse mislykkedes",
"authenticationFailed": "Godkendelse mislykkedes",
"incorrectPassphrase": "Forkert adgangskode. Prøv igen.",
"verificationCodePrompt": "Bekræftelseskode:",
@@ -290,7 +290,6 @@
"connecting": "Verbindung wird hergestellt...",
"connectedSuccessfully": "Erfolgreich verbunden",
"totpVerificationFailed": "TOTP-Überprüfung fehlgeschlagen",
"warpgateVerificationFailed": "Warpgate-Authentifizierung fehlgeschlagen",
"authenticationFailed": "Authentifizierung fehlgeschlagen",
"incorrectPassphrase": "Falsche Passphrase. Bitte versuchen Sie es erneut.",
"verificationCodePrompt": "Verifizierungscode:",
@@ -290,7 +290,6 @@
"connecting": "Συνδετικός...",
"connectedSuccessfully": "Η σύνδεση ολοκληρώθηκε με επιτυχία",
"totpVerificationFailed": "Η επαλήθευση TOTP απέτυχε.",
"warpgateVerificationFailed": "Η πιστοποίηση Warpgate απέτυχε.",
"authenticationFailed": "Η επαλήθευση ταυτότητας απέτυχε",
"incorrectPassphrase": "Λανθασμένη φράση πρόσβασης. Δοκιμάστε ξανά.",
"verificationCodePrompt": "Κωδικός επαλήθευσης:",
@@ -290,7 +290,6 @@
"connecting": "Conectando...",
"connectedSuccessfully": "Conectado correctamente",
"totpVerificationFailed": "Error en la verificación TOTP",
"warpgateVerificationFailed": "Error de autenticación de Warpgate",
"authenticationFailed": "Error de autenticación",
"incorrectPassphrase": "Frase de contraseña incorrecta. Inténtelo de nuevo.",
"verificationCodePrompt": "Código de verificación:",
@@ -290,7 +290,6 @@
"connecting": "Yhdistetään...",
"connectedSuccessfully": "Yhteys onnistui",
"totpVerificationFailed": "TOTP-vahvistus epäonnistui",
"warpgateVerificationFailed": "Warpgate-todennus epäonnistui",
"authenticationFailed": "Todennus epäonnistui",
"incorrectPassphrase": "Virheellinen salasana. Yritä uudelleen.",
"verificationCodePrompt": "Vahvistuskoodi:",
@@ -290,7 +290,6 @@
"connecting": "Connexion en cours...",
"connectedSuccessfully": "Connecté avec succès",
"totpVerificationFailed": "Échec de la vérification TOTP",
"warpgateVerificationFailed": "Échec de l'authentification Warpgate",
"authenticationFailed": "Échec de l'authentification",
"incorrectPassphrase": "Phrase secrète incorrecte. Veuillez réessayer.",
"verificationCodePrompt": "Code de vérification :",
@@ -290,7 +290,6 @@
"connecting": "מְקַשֵׁר...",
"connectedSuccessfully": "התחבר בהצלחה",
"totpVerificationFailed": "אימות TOTP נכשל",
"warpgateVerificationFailed": "אימות Warpgate נכשל",
"authenticationFailed": "האימות נכשל",
"incorrectPassphrase": "סיסמה שגויה. אנא נסה שוב.",
"verificationCodePrompt": "קוד אימות:",
@@ -290,7 +290,6 @@
"connecting": "कनेक्ट हो रहा है...",
"connectedSuccessfully": "सफलतापूर्वक कनेक्ट हुआ",
"totpVerificationFailed": "TOTP सत्यापन विफल",
"warpgateVerificationFailed": "Warpgate सत्यापन विफल",
"authenticationFailed": "प्रमाणीकरण विफल",
"incorrectPassphrase": "गलत पासफ़्रेज़। कृपया पुनः प्रयास करें।",
"verificationCodePrompt": "सत्यापन कोड:",
@@ -290,7 +290,6 @@
"connecting": "Kapcsolódás...",
"connectedSuccessfully": "Sikeres csatlakozás",
"totpVerificationFailed": "TOTP-ellenőrzés sikertelen",
"warpgateVerificationFailed": "Warpgate hitelesítés sikertelen",
"authenticationFailed": "Hitelesítés sikertelen",
"incorrectPassphrase": "Hibás jelszó. Kérjük, próbálja újra.",
"verificationCodePrompt": "Ellenőrző kód:",
@@ -290,7 +290,6 @@
"connecting": "Menghubungkan...",
"connectedSuccessfully": "Terhubung berhasil",
"totpVerificationFailed": "Verifikasi TOTP gagal",
"warpgateVerificationFailed": "Autentikasi Warpgate gagal",
"authenticationFailed": "Autentikasi gagal",
"incorrectPassphrase": "Kata sandi salah. Silakan coba lagi.",
"verificationCodePrompt": "Kode verifikasi:",
@@ -290,7 +290,6 @@
"connecting": "Connessione in corso...",
"connectedSuccessfully": "Connesso con successo",
"totpVerificationFailed": "Verifica TOTP non riuscita",
"warpgateVerificationFailed": "Autenticazione Warpgate non riuscita",
"authenticationFailed": "Autenticazione non riuscita",
"incorrectPassphrase": "Frase di accesso non corretta. Riprova.",
"verificationCodePrompt": "Codice di verifica:",
@@ -290,7 +290,6 @@
"connecting": "接続中...",
"connectedSuccessfully": "接続しました",
"totpVerificationFailed": "TOTP検証に失敗しました",
"warpgateVerificationFailed": "Warpgate認証に失敗しました",
"authenticationFailed": "認証に失敗しました",
"incorrectPassphrase": "パスフレーズが間違っています。もう一度お試しください。",
"verificationCodePrompt": "認証コード:",
@@ -290,7 +290,6 @@
"connecting": "연결 중...",
"connectedSuccessfully": "연결 성공",
"totpVerificationFailed": "TOTP 인증 실패",
"warpgateVerificationFailed": "Warpgate 인증 실패",
"authenticationFailed": "인증 실패",
"incorrectPassphrase": "잘못된 암호입니다. 다시 시도하세요.",
"verificationCodePrompt": "인증 코드:",
@@ -290,7 +290,6 @@
"connecting": "Verbinden...",
"connectedSuccessfully": "Verbinding succesvol tot stand gebracht.",
"totpVerificationFailed": "TOTP-verificatie mislukt",
"warpgateVerificationFailed": "Warpgate-authenticatie mislukt",
"authenticationFailed": "Authenticatie mislukt",
"incorrectPassphrase": "Onjuiste wachtzin. Probeer het opnieuw.",
"verificationCodePrompt": "Verificatiecode:",
@@ -290,7 +290,6 @@
"connecting": "Kobler til...",
"connectedSuccessfully": "Tilkoblet",
"totpVerificationFailed": "TOTP-verifisering mislyktes",
"warpgateVerificationFailed": "Warpgate-autentisering mislyktes",
"authenticationFailed": "Autentisering mislyktes",
"incorrectPassphrase": "Feil passordfrase. Prøv på nytt.",
"verificationCodePrompt": "Verifiseringskode:",
@@ -290,7 +290,6 @@
"connecting": "Łączenie...",
"connectedSuccessfully": "Połączono pomyślnie",
"totpVerificationFailed": "Weryfikacja TOTP nie powiodła się",
"warpgateVerificationFailed": "Uwierzytelnianie Warpgate nie powiodło się",
"authenticationFailed": "Uwierzytelnianie nie powiodło się",
"incorrectPassphrase": "Nieprawidłowe hasło klucza. Spróbuj ponownie.",
"verificationCodePrompt": "Kod weryfikacyjny:",
@@ -290,7 +290,6 @@
"connecting": "Conectando...",
"connectedSuccessfully": "Conexão estabelecida com sucesso.",
"totpVerificationFailed": "A verificação TOTP falhou.",
"warpgateVerificationFailed": "A autenticação do portal de guerra falhou.",
"authenticationFailed": "A autenticação falhou.",
"incorrectPassphrase": "Senha incorreta. Tente novamente.",
"verificationCodePrompt": "Código de verificação:",
@@ -290,7 +290,6 @@
"connecting": "A ligar...",
"connectedSuccessfully": "Ligado com sucesso",
"totpVerificationFailed": "Falha na verificação TOTP",
"warpgateVerificationFailed": "Falha na autenticação Warpgate",
"authenticationFailed": "Falha na autenticação",
"incorrectPassphrase": "Frase-passe incorreta. Tente novamente.",
"verificationCodePrompt": "Código de verificação:",
@@ -290,7 +290,6 @@
"connecting": "Se conectează...",
"connectedSuccessfully": "Conectare reușită",
"totpVerificationFailed": "Verificarea TOTP a eșuat",
"warpgateVerificationFailed": "Autentificarea Warpgate a eșuat",
"authenticationFailed": "Autentificarea a eșuat",
"incorrectPassphrase": "Parolă incorectă. Vă rugăm să încercați din nou.",
"verificationCodePrompt": "Cod de verificare:",
@@ -290,7 +290,6 @@
"connecting": "Подключение...",
"connectedSuccessfully": "Подключено успешно",
"totpVerificationFailed": "Ошибка проверки TOTP",
"warpgateVerificationFailed": "Ошибка аутентификации Warpgate",
"authenticationFailed": "Ошибка аутентификации",
"incorrectPassphrase": "Неверная парольная фраза. Попробуйте еще раз.",
"verificationCodePrompt": "Код подтверждения:",
@@ -290,7 +290,6 @@
"connecting": "Повезивање...",
"connectedSuccessfully": "Успешно повезано",
"totpVerificationFailed": "Верификација TOTP-а није успела",
"warpgateVerificationFailed": "Аутентификација Варпгејта није успела",
"authenticationFailed": "Аутентификација није успела",
"incorrectPassphrase": "Нетачна лозинка. Молимо покушајте поново.",
"verificationCodePrompt": "Верификациони код:",
@@ -290,7 +290,6 @@
"connecting": "Ansluter...",
"connectedSuccessfully": "Ansluten",
"totpVerificationFailed": "TOTP-verifiering misslyckades",
"warpgateVerificationFailed": "Warpgate-autentisering misslyckades",
"authenticationFailed": "Autentiseringen misslyckades",
"incorrectPassphrase": "Felaktigt lösenord. Försök igen.",
"verificationCodePrompt": "Verifieringskod:",
@@ -290,7 +290,6 @@
"connecting": "กำลังเชื่อมต่อ...",
"connectedSuccessfully": "เชื่อมต่อสำเร็จแล้ว",
"totpVerificationFailed": "การตรวจสอบ TOTP ล้มเหลว",
"warpgateVerificationFailed": "การตรวจสอบสิทธิ์วาร์ปไม่สำเร็จ",
"authenticationFailed": "การตรวจสอบสิทธิ์ล้มเหลว",
"incorrectPassphrase": "รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง",
"verificationCodePrompt": "รหัสยืนยัน:",
@@ -290,7 +290,6 @@
"connecting": "Bağlanıyor...",
"connectedSuccessfully": "Bağlantı başarıyla kuruldu",
"totpVerificationFailed": "TOTP doğrulaması başarısız oldu",
"warpgateVerificationFailed": "Warpgate kimlik doğrulaması başarısız oldu",
"authenticationFailed": "Kimlik doğrulama başarısız oldu",
"incorrectPassphrase": "Yanlış parola. Lütfen tekrar deneyin.",
"verificationCodePrompt": "Doğrulama kodu:",
@@ -290,7 +290,6 @@
"connecting": "Підключення...",
"connectedSuccessfully": "Підключено успішно",
"totpVerificationFailed": "Не вдалося перевірити TOTP",
"warpgateVerificationFailed": "Автентифікація Warpgate не вдалася",
"authenticationFailed": "Помилка автентифікації",
"incorrectPassphrase": "Неправильна парольна фраза. Спробуйте ще раз.",
"verificationCodePrompt": "Код підтвердження:",
@@ -290,7 +290,6 @@
"connecting": "Đang kết nối...",
"connectedSuccessfully": "Đã kết nối thành công",
"totpVerificationFailed": "Xác minh TOTP thất bại",
"warpgateVerificationFailed": "Xác thực Warpgate thất bại",
"authenticationFailed": "Xác thực thất bại",
"incorrectPassphrase": "Cụm mật khẩu không đúng. Vui lòng thử lại.",
"verificationCodePrompt": "Mã xác minh:",
@@ -290,7 +290,6 @@
"connecting": "连接中...",
"connectedSuccessfully": "连接成功",
"totpVerificationFailed": "TOTP 验证失败",
"warpgateVerificationFailed": "Warpgate 认证失败",
"authenticationFailed": "认证失败",
"incorrectPassphrase": "密码错误。请重试。",
"verificationCodePrompt": "验证码:",
@@ -290,7 +290,6 @@
"connecting": "連線中...",
"connectedSuccessfully": "已成功連線",
"totpVerificationFailed": "TOTP 驗證失敗",
"warpgateVerificationFailed": "Warpgate 驗證失敗",
"authenticationFailed": "驗證失敗",
"incorrectPassphrase": "密碼片語錯誤。請再試一次。",
"verificationCodePrompt": "驗證碼:",
+28 -31
View File
@@ -200,7 +200,7 @@ export async function activate(ctx: PluginContext) {
);
}
// Pending TOTP/Warpgate parking sessions expire on their own; sweep stale
// Pending TOTP and browser sign-in parking sessions expire on their own; sweep stale
// ones so a session left mid-handshake doesn't leak an open ssh2 Client.
const pendingSweep = setInterval(() => {
const now = Date.now();
@@ -386,7 +386,7 @@ export async function activate(ctx: PluginContext) {
if (
req.path === "/connect" ||
req.path === "/connect-totp" ||
req.path === "/connect-warpgate"
req.path === "/connect-browser-sign-in"
) {
return next();
}
@@ -463,7 +463,7 @@ export async function activate(ctx: PluginContext) {
* type: array
* responses:
* 200:
* description: SSH connection established, or requires TOTP/Warpgate authentication.
* description: SSH connection established, or requires TOTP or a browser sign-in.
* 400:
* description: Missing required parameters or invalid SSH key format.
* 401:
@@ -983,7 +983,7 @@ export async function activate(ctx: PluginContext) {
finish: (responses: string[]) => void,
prompts: Array<{ prompt: string; echo: boolean }>,
promptIndex: number,
isWarpgate = false,
isBrowserSignIn = false,
) => {
pendingTOTPSessions[sessionId] = {
client,
@@ -1000,7 +1000,7 @@ export async function activate(ctx: PluginContext) {
totpPromptIndex: promptIndex,
resolvedPassword: resolvedCredentials.password,
totpAttempts: 0,
...(isWarpgate ? { isWarpgate: true } : {}),
...(isBrowserSignIn ? { isBrowserSignIn: true } : {}),
};
};
@@ -1025,23 +1025,24 @@ export async function activate(ctx: PluginContext) {
return;
}
if (decision.kind === "warpgate") {
if (decision.kind === "browser") {
if (responseSent) return;
responseSent = true;
connectionLogs.push(
createConnectionLog(
"info",
"sftp_auth",
"Warpgate authentication required",
`${decision.label} sign-in required`,
{ url: decision.url },
),
);
parkForAnswer(finish, prompts, -1, true);
res.json({
requires_warpgate: true,
requires_browser_sign_in: true,
sessionId,
label: decision.label,
url: decision.url,
securityKey: decision.securityKey,
code: decision.code,
connectionLogs,
});
return;
@@ -1347,10 +1348,10 @@ export async function activate(ctx: PluginContext) {
/**
* @openapi
* /plugin-api/file-manager/connect-warpgate:
* /plugin-api/file-manager/connect-browser-sign-in:
* post:
* summary: Complete Warpgate authentication
* description: Submits empty response to complete Warpgate authentication after user completes browser auth.
* summary: Continue after a browser sign-in
* description: Answers the parked keyboard-interactive round once the user finished signing in in the browser (an SSH gateway's approval, for example).
* tags:
* - File Manager
* requestBody:
@@ -1366,15 +1367,15 @@ export async function activate(ctx: PluginContext) {
* type: string
* responses:
* 200:
* description: Warpgate authentication completed successfully.
* description: Signed in, SSH connection established.
* 401:
* description: Authentication failed or unauthorized.
* 404:
* description: Warpgate session expired.
* description: Sign-in session expired.
* 408:
* description: Warpgate session timeout.
* description: Sign-in session timeout.
*/
app.post("/connect-warpgate", async (req, res) => {
app.post("/connect-browser-sign-in", async (req, res) => {
const { sessionId } = req.body;
const userId = ctx.currentActor();
@@ -1389,12 +1390,12 @@ export async function activate(ctx: PluginContext) {
if (!session) {
return res
.status(404)
.json({ error: "Warpgate session expired. Please reconnect." });
.json({ error: "Sign-in session expired. Please reconnect." });
}
if (!session.isWarpgate) {
if (!session.isBrowserSignIn) {
return res
.status(400)
.json({ error: "Session is not a Warpgate session" });
.json({ error: "Session is not waiting for a browser sign-in" });
}
if (Date.now() - session.createdAt > 300000) {
delete pendingTOTPSessions[sessionId];
@@ -1405,7 +1406,7 @@ export async function activate(ctx: PluginContext) {
}
return res
.status(408)
.json({ error: "Warpgate session timeout. Please reconnect." });
.json({ error: "Sign-in session timeout. Please reconnect." });
}
let responseSent = false;
@@ -1413,7 +1414,7 @@ export async function activate(ctx: PluginContext) {
if (!responseSent) {
responseSent = true;
delete pendingTOTPSessions[sessionId];
res.status(408).json({ error: "Warpgate verification timeout" });
res.status(408).json({ error: "Sign-in verification timeout" });
}
}, 60000);
@@ -1440,7 +1441,7 @@ export async function activate(ctx: PluginContext) {
res.json({
status: "success",
message: "Warpgate verified, SSH connection established",
message: "Signed in, SSH connection established",
});
if (session.hostId && session.userId) {
@@ -1460,9 +1461,7 @@ export async function activate(ctx: PluginContext) {
responseSent = true;
clearTimeout(responseTimeout);
delete pendingTOTPSessions[sessionId];
res
.status(401)
.json({ status: "error", message: "Warpgate authentication failed" });
res.status(401).json({ status: "error", message: "Sign-in failed" });
});
session.finish([""]);
@@ -1567,12 +1566,10 @@ export async function activate(ctx: PluginContext) {
}
const session = sshSessions[sessionId];
if (!session || !session.isConnected) {
return res
.status(400)
.json({
error: "SSH session not found or not connected",
connected: false,
});
return res.status(400).json({
error: "SSH session not found or not connected",
connected: false,
});
}
if (!userId || !verifySessionOwnership(session, userId)) {
return res.status(403).json({ error: "Session access denied" });
+1 -1
View File
@@ -57,7 +57,7 @@ export interface PendingTOTPSession {
totpPromptIndex?: number;
resolvedPassword?: string;
totpAttempts: number;
isWarpgate?: boolean;
isBrowserSignIn?: boolean;
}
export function execWithSudo(
+1 -1
View File
@@ -38,7 +38,7 @@ export function connectSsh(
/**
* Lower-level primitives for the interactive /connect route, which drives its
* own ssh2 Client through the TOTP/Warpgate parking flow rather than letting
* own ssh2 Client through the TOTP and browser sign-in parking flow rather than letting
* ctx.ssh.connect finish the handshake itself.
*/
export function prepareSsh(
@@ -59,7 +59,7 @@ import {
moveSSHItem,
connectSSH,
verifySSHTOTP,
verifySSHWarpgate,
verifySSHBrowserSignIn,
getSSHStatus,
keepSSHAlive,
identifySSHSymlink,
@@ -199,12 +199,13 @@ function FileManagerContent({
const [totpRequired, setTotpRequired] = useState(false);
const [totpSessionId, setTotpSessionId] = useState<string | null>(null);
const [totpPrompt, setTotpPrompt] = useState<string>("");
const [warpgateRequired, setWarpgateRequired] = useState(false);
const [warpgateSessionId, setWarpgateSessionId] = useState<string | null>(
null,
);
const [warpgateUrl, setWarpgateUrl] = useState<string>("");
const [warpgateSecurityKey, setWarpgateSecurityKey] = useState<string>("");
const [browserSignInRequired, setBrowserSignInRequired] = useState(false);
const [browserSignInSessionId, setBrowserSignInSessionId] = useState<
string | null
>(null);
const [browserSignInUrl, setBrowserSignInUrl] = useState<string>("");
const [browserSignInCode, setBrowserSignInCode] = useState<string>("");
const [browserSignInLabel, setBrowserSignInLabel] = useState<string>("");
const [showAuthDialog, setShowAuthDialog] = useState(false);
const [authDialogReason, setAuthDialogReason] = useState<
"no_keyboard" | "auth_failed" | "timeout"
@@ -421,7 +422,7 @@ function FileManagerContent({
!!currentHost &&
!sshSessionId &&
!totpRequired &&
!warpgateRequired &&
!browserSignInRequired &&
!showAuthDialog &&
!showPassphraseDialog,
autoStart: false,
@@ -627,11 +628,12 @@ function FileManagerContent({
socks5ProxyChain: currentHost.socks5ProxyChain,
});
if (result?.requires_warpgate) {
setWarpgateRequired(true);
setWarpgateSessionId(sessionId);
setWarpgateUrl(result.url || "");
setWarpgateSecurityKey(result.securityKey || "N/A");
if (result?.requires_browser_sign_in) {
setBrowserSignInRequired(true);
setBrowserSignInSessionId(sessionId);
setBrowserSignInUrl(result.url || "");
setBrowserSignInCode(result.code || "N/A");
setBrowserSignInLabel(result.label || "");
setIsLoading(false);
return;
}
@@ -698,11 +700,14 @@ function FileManagerContent({
setIsLoading(false);
return;
}
if (sshError.requires_warpgate) {
setWarpgateRequired(true);
setWarpgateSessionId(sshError.sessionId || currentHost.id.toString());
setWarpgateUrl(sshError.url || "");
setWarpgateSecurityKey(sshError.securityKey || "N/A");
if (sshError.requires_browser_sign_in) {
setBrowserSignInRequired(true);
setBrowserSignInSessionId(
sshError.sessionId || currentHost.id.toString(),
);
setBrowserSignInUrl(sshError.url || "");
setBrowserSignInCode(sshError.code || "N/A");
setBrowserSignInLabel(sshError.label || "");
setIsLoading(false);
return;
}
@@ -2660,23 +2665,26 @@ function FileManagerContent({
if (onClose) onClose();
}
async function handleWarpgateContinue() {
if (!warpgateSessionId) return;
async function handleBrowserSignInContinue() {
if (!browserSignInSessionId) return;
try {
setIsLoading(true);
const result = await verifySSHWarpgate(warpgateSessionId);
const result = await verifySSHBrowserSignIn(browserSignInSessionId);
if (result?.status === "success") {
setWarpgateRequired(false);
setWarpgateUrl("");
setWarpgateSecurityKey("");
setSshSessionId(warpgateSessionId);
setWarpgateSessionId(null);
setBrowserSignInRequired(false);
setBrowserSignInUrl("");
setBrowserSignInCode("");
setSshSessionId(browserSignInSessionId);
setBrowserSignInSessionId(null);
connectRetryRef.current.markConnected();
try {
const response = await listSSHFiles(warpgateSessionId, currentPath);
const response = await listSSHFiles(
browserSignInSessionId,
currentPath,
);
const files = Array.isArray(response)
? response
: response?.files || [];
@@ -2691,24 +2699,24 @@ function FileManagerContent({
}
}
} catch (error: unknown) {
console.error("Warpgate verification failed:", error);
toast.error(t("fileManager.warpgateVerificationFailed"));
console.error("Browser sign-in failed:", error);
toast.error(t("fileManager.browserSignInFailed"));
} finally {
setIsLoading(false);
}
}
function handleWarpgateCancel() {
setWarpgateRequired(false);
setWarpgateUrl("");
setWarpgateSecurityKey("");
setWarpgateSessionId(null);
function handleBrowserSignInCancel() {
setBrowserSignInRequired(false);
setBrowserSignInUrl("");
setBrowserSignInCode("");
setBrowserSignInSessionId(null);
if (onClose) onClose();
}
function handleWarpgateOpenUrl() {
if (warpgateUrl) {
window.open(warpgateUrl, "_blank", "noopener,noreferrer");
function handleBrowserSignInOpenUrl() {
if (browserSignInUrl) {
window.open(browserSignInUrl, "_blank", "noopener,noreferrer");
}
}
@@ -2745,11 +2753,12 @@ function FileManagerContent({
socks5ProxyChain: currentHost.socks5ProxyChain,
});
if (result?.requires_warpgate) {
setWarpgateRequired(true);
setWarpgateSessionId(sessionId);
setWarpgateUrl(result.url || "");
setWarpgateSecurityKey(result.securityKey || "N/A");
if (result?.requires_browser_sign_in) {
setBrowserSignInRequired(true);
setBrowserSignInSessionId(sessionId);
setBrowserSignInUrl(result.url || "");
setBrowserSignInCode(result.code || "N/A");
setBrowserSignInLabel(result.label || "");
setIsLoading(false);
return;
}
@@ -3626,12 +3635,13 @@ function FileManagerContent({
totpPrompt={totpPrompt}
handleTotpSubmit={handleTotpSubmit}
handleTotpCancel={handleTotpCancel}
warpgateRequired={warpgateRequired}
warpgateUrl={warpgateUrl}
warpgateSecurityKey={warpgateSecurityKey}
handleWarpgateContinue={handleWarpgateContinue}
handleWarpgateCancel={handleWarpgateCancel}
handleWarpgateOpenUrl={handleWarpgateOpenUrl}
browserSignInRequired={browserSignInRequired}
browserSignInUrl={browserSignInUrl}
browserSignInCode={browserSignInCode}
browserSignInLabel={browserSignInLabel}
handleBrowserSignInContinue={handleBrowserSignInContinue}
handleBrowserSignInCancel={handleBrowserSignInCancel}
handleBrowserSignInOpenUrl={handleBrowserSignInOpenUrl}
currentHost={currentHost}
showAuthDialog={showAuthDialog}
authDialogReason={authDialogReason}
@@ -2,7 +2,7 @@ import type { FileItem, SSHHost } from "@/types/index";
import {
TOTPDialog,
SSHAuthDialog,
WarpgateDialog,
BrowserSignInDialog,
} from "@termix/plugin-sdk/ui";
import { PermissionsDialog } from "./components/PermissionsDialog.tsx";
import { CompressDialog } from "./components/CompressDialog.tsx";
@@ -17,12 +17,13 @@ type FileManagerDialogsProps = {
totpPrompt: string;
handleTotpSubmit: (code: string) => void | Promise<void>;
handleTotpCancel: () => void;
warpgateRequired: boolean;
warpgateUrl: string;
warpgateSecurityKey: string;
handleWarpgateContinue: () => void | Promise<void>;
handleWarpgateCancel: () => void;
handleWarpgateOpenUrl: () => void;
browserSignInRequired: boolean;
browserSignInUrl: string;
browserSignInCode: string;
browserSignInLabel: string;
handleBrowserSignInContinue: () => void | Promise<void>;
handleBrowserSignInCancel: () => void;
handleBrowserSignInOpenUrl: () => void;
currentHost: SSHHost | null;
showAuthDialog: boolean;
authDialogReason: "no_keyboard" | "auth_failed" | "timeout";
@@ -49,12 +50,13 @@ export function FileManagerDialogs({
totpPrompt,
handleTotpSubmit,
handleTotpCancel,
warpgateRequired,
warpgateUrl,
warpgateSecurityKey,
handleWarpgateContinue,
handleWarpgateCancel,
handleWarpgateOpenUrl,
browserSignInRequired,
browserSignInUrl,
browserSignInCode,
browserSignInLabel,
handleBrowserSignInContinue,
handleBrowserSignInCancel,
handleBrowserSignInOpenUrl,
currentHost,
showAuthDialog,
authDialogReason,
@@ -85,13 +87,14 @@ export function FileManagerDialogs({
backgroundColor="var(--bg-canvas)"
/>
<WarpgateDialog
isOpen={warpgateRequired}
url={warpgateUrl}
securityKey={warpgateSecurityKey}
onContinue={handleWarpgateContinue}
onCancel={handleWarpgateCancel}
onOpenUrl={handleWarpgateOpenUrl}
<BrowserSignInDialog
isOpen={browserSignInRequired}
label={browserSignInLabel}
url={browserSignInUrl}
code={browserSignInCode}
onContinue={handleBrowserSignInContinue}
onCancel={handleBrowserSignInCancel}
onOpenUrl={handleBrowserSignInOpenUrl}
backgroundColor="var(--bg-canvas)"
/>
@@ -30,11 +30,12 @@ type ConnectErrorResponse = {
message?: string;
connectionLogs?: ApiConnectionLog[];
requires_totp?: boolean;
requires_warpgate?: boolean;
requires_browser_sign_in?: boolean;
sessionId?: string;
prompt?: string;
url?: string;
securityKey?: string;
code?: string;
label?: string;
status?: string;
reason?: string;
};
@@ -48,11 +49,12 @@ export interface SSHConnectResult {
status?: string;
reason?: "timeout" | "no_keyboard" | "auth_failed";
requires_totp?: boolean;
requires_warpgate?: boolean;
requires_browser_sign_in?: boolean;
sessionId?: string;
prompt?: string;
url?: string;
securityKey?: string;
code?: string;
label?: string;
}
function triggerBlobDownload(blob: Blob, fileName: string): void {
@@ -125,12 +127,13 @@ export async function connectSSH(
prompt: data.prompt,
});
}
if (data.requires_warpgate) {
if (data.requires_browser_sign_in) {
Object.assign(errorWithLogs, {
requires_warpgate: true,
requires_browser_sign_in: true,
sessionId: data.sessionId,
url: data.url,
securityKey: data.securityKey,
code: data.code,
label: data.label,
});
}
if (data.status === "auth_required") {
@@ -176,17 +179,17 @@ export async function verifySSHTOTP(
}
}
export async function verifySSHWarpgate(
export async function verifySSHBrowserSignIn(
sessionId: string,
): Promise<Record<string, unknown>> {
try {
const response = await getFileManagerApiForSession(sessionId).post(
"/ssh/connect-warpgate",
"/ssh/connect-browser-sign-in",
{ sessionId },
);
return response.data;
} catch (error) {
handleApiError(error, "verify SSH Warpgate");
handleApiError(error, "verify SSH browser sign-in");
}
}
@@ -1020,7 +1023,7 @@ export async function ensureSSHSessionForHost(
if (
result?.requires_totp ||
result?.requires_warpgate ||
result?.requires_browser_sign_in ||
result?.status === "auth_required"
) {
return { state: "auth_required", sessionId };
@@ -16,11 +16,12 @@ export type ConnectionLogPayload = Omit<LogEntry, "id" | "timestamp">;
export type SSHConnectionError = Error & {
connectionLogs?: ConnectionLogPayload[];
requires_totp?: boolean;
requires_warpgate?: boolean;
requires_browser_sign_in?: boolean;
sessionId?: string;
prompt?: string;
url?: string;
securityKey?: string;
code?: string;
label?: string;
status?: string;
reason?: "no_keyboard" | "auth_failed" | "timeout";
};