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
@@ -78,7 +78,7 @@
"containerMustBeRunning": "Container must be running to access console",
"verificationCodePrompt": "Enter verification code",
"totpVerificationFailed": "TOTP verification failed. Please try again.",
"warpgateVerificationFailed": "Warpgate authentication failed. Please try again.",
"browserSignInFailed": "Sign-in failed. Please try again.",
"connectedTo": "Connected to {{containerName}}",
"disconnected": "Disconnected",
"consoleError": "Console error",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Die houer moet loop om toegang tot die konsole te verkry",
"verificationCodePrompt": "Voer verifikasiekode in",
"totpVerificationFailed": "TOTP-verifikasie het misluk. Probeer asseblief weer.",
"warpgateVerificationFailed": "Warpgate-verifikasie het misluk. Probeer asseblief weer.",
"connectedTo": "Gekoppel aan {{containerName}}",
"disconnected": "Ontkoppel",
"consoleError": "Konsolefout",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "يجب أن تكون الحاوية قيد التشغيل للوصول إلى وحدة التحكم",
"verificationCodePrompt": "أدخل رمز التحقق",
"totpVerificationFailed": "فشل التحقق من TOTP. يرجى المحاولة مرة أخرى.",
"warpgateVerificationFailed": "فشلت مصادقة Warpgate. يرجى المحاولة مرة أخرى.",
"connectedTo": "متصل بـ {{containerName}}",
"disconnected": "تم قطع الاتصال",
"consoleError": "خطأ في وحدة التحكم",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Контейнерът трябва да е работещ, за да има достъп до конзолата",
"verificationCodePrompt": "Въведете код за потвърждение",
"totpVerificationFailed": "Проверката на TOTP не бе успешна. Моля, опитайте отново.",
"warpgateVerificationFailed": "Удостоверяването на Warpgate не бе успешно. Моля, опитайте отново.",
"connectedTo": "Свързано с {{containerName}}",
"disconnected": "Изключен",
"consoleError": "Грешка в конзолата",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "কনসোল অ্যাক্সেস করতে হলে কন্টেইনারটি অবশ্যই চালু থাকতে হবে।",
"verificationCodePrompt": "যাচাইকরণ কোড প্রবেশ করান",
"totpVerificationFailed": "TOTP যাচাইকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
"warpgateVerificationFailed": "ওয়ার্পগেট প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আবার চেষ্টা করুন।",
"connectedTo": "{{containerName}} এর সাথে সংযুক্ত",
"disconnected": "বিচ্ছিন্ন",
"consoleError": "কনসোল ত্রুটি",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "El contenidor ha d'estar en execució per accedir a la consola.",
"verificationCodePrompt": "Introduïu el codi de verificació",
"totpVerificationFailed": "La verificació TOTP ha fallat. Torna-ho a provar.",
"warpgateVerificationFailed": "L'autenticació de Warpgate ha fallat. Torna-ho a intentar.",
"connectedTo": "Connectat a {{containerName}}",
"disconnected": "Desconnectat",
"consoleError": "Error de consola",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Pro přístup ke konzoli musí být kontejner spuštěný.",
"verificationCodePrompt": "Zadejte ověřovací kód",
"totpVerificationFailed": "Ověření TOTP se nezdařilo. Zkuste to prosím znovu.",
"warpgateVerificationFailed": "Ověření Warpgate se nezdařilo. Zkuste to prosím znovu.",
"connectedTo": "Připojeno k {{containerName}}",
"disconnected": "Odpojeno",
"consoleError": "Chyba konzole",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Containeren skal køre for at få adgang til konsollen",
"verificationCodePrompt": "Indtast bekræftelseskode",
"totpVerificationFailed": "TOTP-bekræftelse mislykkedes. Prøv igen.",
"warpgateVerificationFailed": "Warpgate-godkendelse mislykkedes. Prøv igen.",
"connectedTo": "Forbundet til {{containerName}}",
"disconnected": "Afbrudt",
"consoleError": "Konsolfejl",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Der Container muss ausgeführt werden, um auf die Konsole zuzugreifen",
"verificationCodePrompt": "Bestätigungscode eingeben",
"totpVerificationFailed": "TOTP-Überprüfung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"warpgateVerificationFailed": "Warpgate-Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"connectedTo": "Verbunden mit {{containerName}}",
"disconnected": "Getrennt",
"consoleError": "Konsolenfehler",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Το κοντέινερ πρέπει να εκτελείται για πρόσβαση στην κονσόλα",
"verificationCodePrompt": "Εισαγάγετε τον κωδικό επαλήθευσης",
"totpVerificationFailed": "Η επαλήθευση TOTP απέτυχε. Δοκιμάστε ξανά.",
"warpgateVerificationFailed": "Ο έλεγχος ταυτότητας Warpgate απέτυχε. Δοκιμάστε ξανά.",
"connectedTo": "Συνδεδεμένο με {{containerName}}",
"disconnected": "Ασύνδετος",
"consoleError": "Σφάλμα κονσόλας",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "El contenedor debe estar en ejecución para acceder a la consola",
"verificationCodePrompt": "Introduzca el código de verificación",
"totpVerificationFailed": "La verificación TOTP falló. Inténtelo de nuevo.",
"warpgateVerificationFailed": "La autenticación Warpgate falló. Inténtelo de nuevo.",
"connectedTo": "Conectado a {{containerName}}",
"disconnected": "Desconectado",
"consoleError": "Error de consola",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Kontin on oltava käynnissä, jotta konsoliin pääsee käsiksi.",
"verificationCodePrompt": "Syötä vahvistuskoodi",
"totpVerificationFailed": "TOTP-vahvistus epäonnistui. Yritä uudelleen.",
"warpgateVerificationFailed": "Warpgate-todennus epäonnistui. Yritä uudelleen.",
"connectedTo": "Yhdistetty kohteeseen {{containerName}}",
"disconnected": "Yhteys katkennut",
"consoleError": "Konsolivirhe",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Le conteneur doit être en cours d'exécution pour accéder à la console",
"verificationCodePrompt": "Saisissez le code de vérification",
"totpVerificationFailed": "La vérification TOTP a échoué. Veuillez réessayer.",
"warpgateVerificationFailed": "L'authentification Warpgate a échoué. Veuillez réessayer.",
"connectedTo": "Connecté à {{containerName}}",
"disconnected": "Déconnecté",
"consoleError": "Erreur de la console",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "יש לפעול על מנת לגשת למסוף.",
"verificationCodePrompt": "הזן קוד אימות",
"totpVerificationFailed": "אימות TOTP נכשל. אנא נסה שוב.",
"warpgateVerificationFailed": "אימות Warpgate נכשל. אנא נסה שוב.",
"connectedTo": "מחובר אל {{containerName}}",
"disconnected": "מְנוּתָק",
"consoleError": "שגיאת קונסולה",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "कंसोल तक पहुंचने के लिए कंटेनर चालू होना चाहिए",
"verificationCodePrompt": "सत्यापन कोड दर्ज करें",
"totpVerificationFailed": "TOTP सत्यापन विफल। कृपया पुनः प्रयास करें।",
"warpgateVerificationFailed": "Warpgate प्रमाणीकरण विफल। कृपया पुनः प्रयास करें।",
"connectedTo": "{{containerName}} से जुड़ा",
"disconnected": "डिस्कनेक्टेड",
"consoleError": "कंसोल त्रुटि",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "A konténernek futnia kell a konzol eléréséhez",
"verificationCodePrompt": "Írja be az ellenőrző kódot",
"totpVerificationFailed": "A TOTP ellenőrzése sikertelen. Próbáld újra.",
"warpgateVerificationFailed": "A Warpgate hitelesítése sikertelen. Próbáld újra.",
"connectedTo": "Csatlakoztatva ehhez: {{containerName}}",
"disconnected": "Szétkapcsolt",
"consoleError": "Konzolhiba",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Kontainer harus berjalan agar dapat mengakses konsol.",
"verificationCodePrompt": "Masukkan kode verifikasi",
"totpVerificationFailed": "Verifikasi TOTP gagal. Silakan coba lagi.",
"warpgateVerificationFailed": "Autentikasi Warpgate gagal. Silakan coba lagi.",
"connectedTo": "Terhubung ke {{containerName}}",
"disconnected": "Terputus",
"consoleError": "Kesalahan konsol",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Il container deve essere in esecuzione per accedere alla console",
"verificationCodePrompt": "Inserisci il codice di verifica",
"totpVerificationFailed": "Verifica TOTP fallita. Riprova.",
"warpgateVerificationFailed": "Autenticazione Warpgate fallita. Riprova.",
"connectedTo": "Connesso a {{containerName}}",
"disconnected": "Disconnesso",
"consoleError": "Errore console",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "コンソールにアクセスするにはコンテナが実行中である必要があります",
"verificationCodePrompt": "認証コードを入力してください",
"totpVerificationFailed": "TOTP認証に失敗しました。もう一度お試しください。",
"warpgateVerificationFailed": "Warpgate認証に失敗しました。もう一度お試しください。",
"connectedTo": "{{containerName}}に接続しました",
"disconnected": "切断されました",
"consoleError": "コンソールエラー",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "컨테이너가 실행 중이어야 콘솔에 접근할 수 있습니다",
"verificationCodePrompt": "인증 코드를 입력하세요",
"totpVerificationFailed": "TOTP 인증에 실패했습니다. 다시 시도하세요.",
"warpgateVerificationFailed": "Warpgate 인증에 실패했습니다. 다시 시도하세요.",
"connectedTo": "{{containerName}}에 연결됨",
"disconnected": "연결 끊김",
"consoleError": "콘솔 오류",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "De container moet actief zijn om toegang te krijgen tot de console.",
"verificationCodePrompt": "Voer de verificatiecode in.",
"totpVerificationFailed": "TOTP-verificatie mislukt. Probeer het opnieuw.",
"warpgateVerificationFailed": "Authenticatie via Warpgate is mislukt. Probeer het opnieuw.",
"connectedTo": "Verbonden met {{containerName}}",
"disconnected": "Verbinding verbroken",
"consoleError": "Consolefout",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Containeren må kjøre for å få tilgang til konsollen",
"verificationCodePrompt": "Skriv inn bekreftelseskoden",
"totpVerificationFailed": "TOTP-verifisering mislyktes. Prøv på nytt.",
"warpgateVerificationFailed": "Warpgate-autentisering mislyktes. Prøv på nytt.",
"connectedTo": "Koblet til {{containerName}}",
"disconnected": "Frakoblet",
"consoleError": "Konsollfeil",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Aby uzyskać dostęp do konsoli, kontener musi być uruchomiony",
"verificationCodePrompt": "Wprowadź kod weryfikacyjny",
"totpVerificationFailed": "Weryfikacja TOTP nie powiodła się. Spróbuj ponownie.",
"warpgateVerificationFailed": "Uwierzytelnianie Warpgate nie powiodło się. Spróbuj ponownie.",
"connectedTo": "Połączono z {{containerName}}",
"disconnected": "Rozłączono",
"consoleError": "Błąd konsoli",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "O contêiner precisa estar em execução para acessar o console.",
"verificationCodePrompt": "Insira o código de verificação",
"totpVerificationFailed": "A verificação TOTP falhou. Tente novamente.",
"warpgateVerificationFailed": "A autenticação no portal de guerra falhou. Por favor, tente novamente.",
"connectedTo": "Conectado a {{containerName}}",
"disconnected": "Desconectado",
"consoleError": "Erro no console",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "O contentor tem de estar em execução para aceder à consola",
"verificationCodePrompt": "Introduza o código de verificação",
"totpVerificationFailed": "A verificação TOTP falhou. Por favor, tente novamente.",
"warpgateVerificationFailed": "A autenticação Warpgate falhou. Por favor, tente novamente.",
"connectedTo": "Ligado a {{containerName}}",
"disconnected": "Desligado",
"consoleError": "Erro da consola",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Containerul trebuie să ruleze pentru a accesa consola.",
"verificationCodePrompt": "Introduceți codul de verificare",
"totpVerificationFailed": "Verificarea TOTP a eșuat. Vă rugăm să încercați din nou.",
"warpgateVerificationFailed": "Autentificarea Warpgate a eșuat. Vă rugăm să încercați din nou.",
"connectedTo": "Conectat la {{containerName}}",
"disconnected": "Deconectat",
"consoleError": "Eroare consolă",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Для доступа к консоли контейнер должен быть запущен",
"verificationCodePrompt": "Введите код подтверждения",
"totpVerificationFailed": "Ошибка проверки TOTP. Попробуйте еще раз.",
"warpgateVerificationFailed": "Ошибка аутентификации Warpgate. Попробуйте еще раз.",
"connectedTo": "Подключено к {{containerName}}",
"disconnected": "Отключено",
"consoleError": "Ошибка консоли",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Контејнер мора бити покренут да би се приступило конзоли",
"verificationCodePrompt": "Унесите верификациони код",
"totpVerificationFailed": "Верификација TOTP-а није успела. Молимо покушајте поново.",
"warpgateVerificationFailed": "Аутентификација преко Ворпгејта није успела. Молимо покушајте поново.",
"connectedTo": "Повезано са {{containerName}}",
"disconnected": "Искључено",
"consoleError": "Грешка конзоле",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Behållaren måste köras för att komma åt konsolen",
"verificationCodePrompt": "Ange verifieringskod",
"totpVerificationFailed": "TOTP-verifieringen misslyckades. Försök igen.",
"warpgateVerificationFailed": "Warpgate-autentiseringen misslyckades. Försök igen.",
"connectedTo": "Ansluten till {{containerName}}",
"disconnected": "Osammanhängande",
"consoleError": "Konsolfel",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "ต้องเปิดใช้งานคอนเทนเนอร์ก่อนจึงจะสามารถเข้าถึงคอนโซลได้",
"verificationCodePrompt": "ป้อนรหัสยืนยัน",
"totpVerificationFailed": "การตรวจสอบ TOTP ล้มเหลว โปรดลองอีกครั้ง",
"warpgateVerificationFailed": "การตรวจสอบสิทธิ์ผ่านวาร์ปไม่สำเร็จ โปรดลองอีกครั้ง",
"connectedTo": "เชื่อมต่อกับ {{containerName}}",
"disconnected": "ตัดการเชื่อมต่อ",
"consoleError": "ข้อผิดพลาดของคอนโซล",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Konsola erişmek için konteyner çalışıyor olmalıdır",
"verificationCodePrompt": "Doğrulama kodunu girin",
"totpVerificationFailed": "TOTP doğrulaması başarısız oldu. Lütfen tekrar deneyin.",
"warpgateVerificationFailed": "Warpgate kimlik doğrulaması başarısız oldu. Lütfen tekrar deneyin.",
"connectedTo": "{{containerName}} ile bağlantı kuruldu",
"disconnected": "Bağlantı kesildi",
"consoleError": "Konsol hatası",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Для доступу до консолі контейнер має бути запущений",
"verificationCodePrompt": "Введіть код підтвердження",
"totpVerificationFailed": "Не вдалося перевірити TOTP. Спробуйте ще раз.",
"warpgateVerificationFailed": "Помилка автентифікації Warpgate. Будь ласка, спробуйте ще раз.",
"connectedTo": "Підключено до {{containerName}}",
"disconnected": "Відключено",
"consoleError": "Помилка консолі",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "Container phải đang chạy để truy cập bảng điều khiển",
"verificationCodePrompt": "Nhập mã xác minh",
"totpVerificationFailed": "Xác minh TOTP thất bại. Vui lòng thử lại.",
"warpgateVerificationFailed": "Xác thực Warpgate thất bại. Vui lòng thử lại.",
"connectedTo": "Đã kết nối đến {{containerName}}",
"disconnected": "Đã ngắt kết nối",
"consoleError": "Lỗi bảng điều khiển",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "访问控制台需要容器处于运行状态",
"verificationCodePrompt": "输入验证码",
"totpVerificationFailed": "TOTP 验证失败,请重试。",
"warpgateVerificationFailed": "Warpgate 认证失败,请重试。",
"connectedTo": "已连接到 {{containerName}}",
"disconnected": "已断开连接",
"consoleError": "控制台错误",
@@ -73,7 +73,6 @@
"containerMustBeRunning": "容器必須在執行中才能存取主控台",
"verificationCodePrompt": "請輸入驗證碼",
"totpVerificationFailed": "TOTP 驗證失敗,請再試一次。",
"warpgateVerificationFailed": "Warpgate 驗證失敗,請再試一次。",
"connectedTo": "已連線至 {{containerName}}",
"disconnected": "已中斷連線",
"consoleError": "主控台錯誤",
+15 -14
View File
@@ -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",
},
};
+7 -7
View File
@@ -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);
+1 -1
View File
@@ -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;
+23 -16
View File
@@ -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
+6 -5
View File
@@ -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) =>
+12 -9
View File
@@ -44,7 +44,7 @@ describe("docker routes", () => {
const calls: Array<[string, string]> = [
["POST", "/ssh/connect"],
["POST", "/ssh/connect-totp"],
["POST", "/ssh/connect-warpgate"],
["POST", "/ssh/connect-browser-sign-in"],
["POST", "/ssh/disconnect"],
["POST", "/ssh/keepalive"],
["GET", "/ssh/status?sessionId=s1"],
@@ -204,29 +204,32 @@ describe("docker routes", () => {
expect((await server.request("GET", "/containers/s1")).status).toBe(200);
});
it("continues a Warpgate sign-in and refuses a code for it", async () => {
it("continues a browser sign-in and refuses a code for it", async () => {
server = await startServer();
promptingConnect(server, async (ask) => {
await ask({
kind: "warpgate",
url: "https://warpgate.example/login",
securityKey: "KEY",
kind: "browser",
id: "gateway",
label: "Gateway",
url: "https://gateway.example/login",
code: "KEY",
instructions: "",
});
});
const first = await connect(server);
expect(first.body).toMatchObject({
requires_warpgate: true,
url: "https://warpgate.example/login",
securityKey: "KEY",
requires_browser_sign_in: true,
label: "Gateway",
url: "https://gateway.example/login",
code: "KEY",
});
const wrongKind = await server.request("POST", "/ssh/connect-totp", {
body: { sessionId: "s1", totpCode: "1" },
});
expect(wrongKind.status).toBe(400);
const done = await server.request("POST", "/ssh/connect-warpgate", {
const done = await server.request("POST", "/ssh/connect-browser-sign-in", {
body: { sessionId: "s1" },
});
expect(done.body.success).toBe(true);
+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";
};
+34
View File
@@ -0,0 +1,34 @@
# OPKSSH
Adds the `opkssh` SSH auth type: Termix signs you in with your identity
provider in the browser, gets a short-lived SSH certificate from
[OpenPubkey SSH](https://github.com/openpubkey/opkssh), and caches it for 24
hours.
## Config
`<DATA_DIR>/plugins/opkssh/config.yml`, OPKSSH's own config format. A
template is written there the first time someone signs in without one. An
install upgraded from 2.8 has its old `<DATA_DIR>/.opk/config.yml` copied
here.
`redirect_uris` in that file are OPKSSH's local listener on the Termix
server and must be localhost (or left out). The public callback is passed
to OPKSSH as `--remote-redirect-uri`; register it with your identity
provider. The plugin's admin settings page shows it:
`<base URL>/plugin-api/opkssh/callback`, or `<base URL>/host/opkssh-callback`
while "Use the old redirect URI" is on.
## Binary
The plugin runs `opkssh` pinned to one release and its SHA-256. It looks, in
order, at:
1. `OPKSSH_BUNDLED_DIR/<asset>` (default `<cwd>/opkssh-bundled`). The Docker
image bakes the binary into `/app/opkssh-bundled`, so offline installs
never download it.
2. `<DATA_DIR>/plugins/opkssh/bin/<asset>`, a copy downloaded earlier.
3. A download from the GitHub release.
Any copy whose checksum does not match is ignored. `OPKSSH_VERSION` picks
another release; `OPKSSH_SHA256` must then give its checksum.
+29
View File
@@ -0,0 +1,29 @@
{
"editor": {
"label": "OPKSSH",
"description": "Sign in to this host using your identity provider instead of a password or key. Requires OPKSSH set up on the server.",
"docs": "View docs"
},
"dialog": {
"title": "OPKSSH Authentication Required",
"description": "Complete authentication in your browser to continue. This session will remain valid for 24 hours.",
"openBrowser": "Open Browser to Authenticate",
"waiting": "Waiting for authentication in browser...",
"authenticating": "Processing authentication...",
"signInWith": "Sign in with {{provider}}"
},
"errors": {
"authFailed": "Authentication failed. Please check your credentials and try again.",
"timeout": "Authentication timed out. Please try again."
},
"settings": {
"redirectUri": {
"label": "Redirect URI",
"description": "Register this URL as an allowed redirect URI with every identity provider in your OPKSSH config."
},
"legacyCallback": {
"label": "Use the old redirect URI",
"description": "Keep sending /host/opkssh-callback, the redirect URI used before 2.9. Turn this off once your identity providers allow the new one."
}
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Meld aan by hierdie gasheer met jou identiteitsverskaffer in plaas van 'n wagwoord of sleutel. Vereis dat OPKSSH op die bediener opgestel is.",
"docs": "Bekyk dokumente"
},
"dialog": {
"title": "OPKSSH-verifikasie vereis",
"description": "Voltooi verifikasie in jou blaaier om voort te gaan. Hierdie sessie sal vir 24 uur geldig bly.",
"openBrowser": "Maak blaaier oop om te verifieer",
"waiting": "Wag vir verifikasie in blaaier...",
"authenticating": "Verwerk verifikasie...",
"signInWith": "Teken aan met {{provider}}"
},
"errors": {
"authFailed": "Verifikasie het misluk. Kontroleer asseblief jou geloofsbriewe en probeer weer.",
"timeout": "Verifikasie het verstryk. Probeer asseblief weer."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "تسجيل الدخول إلى هذا المضيف باستخدام مزود الهوية بدلاً من كلمة المرور أو المفتاح. يتطلب إعداد OPKSSH على الخادم.",
"docs": "عرض المستندات"
},
"dialog": {
"title": "مطلوب مصادقة OPKSSH",
"description": "أكمل المصادقة في المتصفح للمتابعة. تبقى هذه الجلسة صالحة لمدة 24 ساعة.",
"openBrowser": "افتح المتصفح للمصادقة",
"waiting": "في انتظار المصادقة في المتصفح...",
"authenticating": "جارٍ معالجة المصادقة...",
"signInWith": "تسجيل الدخول باستخدام {{provider}}"
},
"errors": {
"authFailed": "فشلت المصادقة. يرجى التحقق من بيانات الاعتماد والمحاولة مرة أخرى.",
"timeout": "انتهت مهلة المصادقة. يرجى المحاولة مرة أخرى."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "ОПКСШ",
"description": "Влезте в този хост, използвайки вашия доставчик на самоличност вместо парола или ключ. Изисква се OPKSSH, настроен на сървъра.",
"docs": "Преглед на документи"
},
"dialog": {
"title": "Изисква се удостоверяване на OPKSSH",
"description": "За да продължите, завършете удостоверяването в браузъра си. Тази сесия ще остане валидна 24 часа.",
"openBrowser": "Отворете браузъра за удостоверяване",
"waiting": "Чака се удостоверяване в браузъра...",
"authenticating": "Обработва се удостоверяване...",
"signInWith": "Влезте с {{provider}}"
},
"errors": {
"authFailed": "Удостоверяването не бе успешно. Моля, проверете идентификационните си данни и опитайте отново.",
"timeout": "Времето за изчакване на удостоверяването изтече. Моля, опитайте отново."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "পাসওয়ার্ড বা কী-এর পরিবর্তে আপনার আইডেন্টিটি প্রোভাইডার ব্যবহার করে এই হোস্টে সাইন ইন করুন। এর জন্য সার্ভারে OPKSSH সেট আপ করা আবশ্যক।",
"docs": "নথি দেখুন"
},
"dialog": {
"title": "OPKSSH প্রমাণীকরণ আবশ্যক",
"description": "চালিয়ে যাওয়ার জন্য আপনার ব্রাউজারে প্রমাণীকরণ সম্পন্ন করুন। এই সেশনটি ২৪ ঘণ্টার জন্য বৈধ থাকবে।",
"openBrowser": "প্রমাণীকরণের জন্য ব্রাউজার খুলুন",
"waiting": "ব্রাউজারে প্রমাণীকরণের জন্য অপেক্ষা করা হচ্ছে...",
"authenticating": "প্রমাণীকরণ প্রক্রিয়া চলছে...",
"signInWith": "{{provider}} দিয়ে সাইন ইন করুন"
},
"errors": {
"authFailed": "প্রমাণীকরণ ব্যর্থ হয়েছে। অনুগ্রহ করে আপনার পরিচয়পত্র যাচাই করে আবার চেষ্টা করুন।",
"timeout": "প্রমাণীকরণের সময়সীমা শেষ হয়ে গেছে। অনুগ্রহ করে আবার চেষ্টা করুন।"
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Inicieu la sessió en aquest amfitrió amb el vostre proveïdor d'identitat en comptes d'una contrasenya o clau. Requereix que OPKSSH estigui configurat al servidor.",
"docs": "Veure documents"
},
"dialog": {
"title": "Cal autenticació OPKSSH",
"description": "Completeu l'autenticació al navegador per continuar. Aquesta sessió serà vàlida durant 24 hores.",
"openBrowser": "Obre el navegador per autenticar-te",
"waiting": "Esperant l'autenticació al navegador...",
"authenticating": "Processant l'autenticació...",
"signInWith": "Inicia la sessió amb {{provider}}"
},
"errors": {
"authFailed": "L'autenticació ha fallat. Si us plau, comproveu les vostres credencials i torneu-ho a intentar.",
"timeout": "S'ha esgotat el temps d'autenticació. Torna-ho a provar."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Přihlaste se k tomuto hostiteli pomocí svého poskytovatele identity namísto hesla nebo klíče. Vyžaduje nastavení OPKSSH na serveru.",
"docs": "Zobrazit dokumenty"
},
"dialog": {
"title": "Vyžadováno ověřování OPKSSH",
"description": "Pro pokračování dokončete ověření ve vašem prohlížeči. Tato relace bude platná 24 hodin.",
"openBrowser": "Otevřít prohlížeč pro ověření",
"waiting": "Čekání na ověření v prohlížeči...",
"authenticating": "Zpracování ověřování...",
"signInWith": "Přihlásit se pomocí {{provider}}"
},
"errors": {
"authFailed": "Ověření se nezdařilo. Zkontrolujte prosím své přihlašovací údaje a zkuste to znovu.",
"timeout": "Časový limit ověření vypršel. Zkuste to prosím znovu."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Log ind på denne vært med din identitetsudbyder i stedet for en adgangskode eller nøgle. Kræver OPKSSH-opsætning på serveren.",
"docs": "Se dokumenter"
},
"dialog": {
"title": "OPKSSH-godkendelse påkrævet",
"description": "Fuldfør godkendelsen i din browser for at fortsætte. Denne session forbliver gyldig i 24 timer.",
"openBrowser": "Åbn browseren for at godkende",
"waiting": "Venter på godkendelse i browseren...",
"authenticating": "Behandler godkendelse...",
"signInWith": "Log ind med {{provider}}"
},
"errors": {
"authFailed": "Godkendelse mislykkedes. Tjek dine loginoplysninger, og prøv igen.",
"timeout": "Godkendelsen udløb. Prøv igen."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Melden Sie sich mit Ihrem Identitätsanbieter an diesem Host an, anstatt mit einem Passwort oder Schlüssel. Erfordert, dass OPKSSH auf dem Server eingerichtet ist.",
"docs": "Dokumentation anzeigen"
},
"dialog": {
"title": "OPKSSH-Authentifizierung erforderlich",
"description": "Schließen Sie die Authentifizierung in Ihrem Browser ab, um fortzufahren. Diese Sitzung bleibt 24 Stunden lang gültig.",
"openBrowser": "Browser zum Authentifizieren öffnen",
"waiting": "Warten auf Authentifizierung im Browser...",
"authenticating": "Authentifizierung wird verarbeitet...",
"signInWith": "Anmelden mit {{provider}}"
},
"errors": {
"authFailed": "Authentifizierung fehlgeschlagen. Bitte überprüfen Sie Ihre Anmeldeinformationen und versuchen Sie es erneut.",
"timeout": "Authentifizierung abgelaufen. Bitte versuchen Sie es erneut."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Συνδεθείτε σε αυτόν τον κεντρικό υπολογιστή χρησιμοποιώντας την υπηρεσία παροχής ταυτότητας αντί για κωδικό πρόσβασης ή κλειδί. Απαιτείται η ρύθμιση του OPKSSH στον διακομιστή.",
"docs": "Προβολή εγγράφων"
},
"dialog": {
"title": "Απαιτείται έλεγχος ταυτότητας OPKSSH",
"description": "Ολοκληρώστε τον έλεγχο ταυτότητας στο πρόγραμμα περιήγησής σας για να συνεχίσετε. Αυτή η περίοδος σύνδεσης θα παραμείνει έγκυρη για 24 ώρες.",
"openBrowser": "Άνοιγμα προγράμματος περιήγησης για έλεγχο ταυτότητας",
"waiting": "Αναμονή για έλεγχο ταυτότητας στο πρόγραμμα περιήγησης...",
"authenticating": "Επεξεργασία ελέγχου ταυτότητας...",
"signInWith": "Συνδεθείτε με {{provider}}"
},
"errors": {
"authFailed": "Ο έλεγχος ταυτότητας απέτυχε. Ελέγξτε τα διαπιστευτήριά σας και προσπαθήστε ξανά.",
"timeout": "Το χρονικό όριο ελέγχου ταυτότητας έληξε. Δοκιμάστε ξανά."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Inicie sesión en este host usando su proveedor de identidad en lugar de una contraseña o clave. Requiere OPKSSH configurado en el servidor.",
"docs": "Ver documentación"
},
"dialog": {
"title": "Autenticación OPKSSH requerida",
"description": "Completa la autenticación en tu navegador para continuar. Esta sesión seguirá siendo válida durante 24 horas.",
"openBrowser": "Abrir navegador para autenticarse",
"waiting": "Esperando autenticación en el navegador...",
"authenticating": "Procesando autenticación...",
"signInWith": "Iniciar sesión con {{provider}}"
},
"errors": {
"authFailed": "La autenticación falló. Revisa tus credenciales e inténtalo de nuevo.",
"timeout": "La autenticación expiró. Inténtalo de nuevo."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Kirjaudu tähän isäntään käyttämällä identiteetintarjoajaasi salasanan tai avaimen sijaan. Edellyttää OPKSSH:n määrittämistä palvelimelle.",
"docs": "Näytä dokumentit"
},
"dialog": {
"title": "OPKSSH-todennus vaaditaan",
"description": "Suorita todennus selaimessasi jatkaaksesi. Tämä istunto on voimassa 24 tuntia.",
"openBrowser": "Avaa selain todennusta varten",
"waiting": "Odotetaan selaimen todennusta...",
"authenticating": "Todennusta käsitellään...",
"signInWith": "Kirjaudu sisään tunnuksella {{provider}}"
},
"errors": {
"authFailed": "Todennus epäonnistui. Tarkista tunnistetietosi ja yritä uudelleen.",
"timeout": "Todennus aikakatkaistiin. Yritä uudelleen."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Connectez-vous à cet hôte en utilisant votre fournisseur d'identité au lieu d'un mot de passe ou d'une clé. Nécessite OPKSSH configuré sur le serveur.",
"docs": "Voir la documentation"
},
"dialog": {
"title": "Authentification OPKSSH requise",
"description": "Terminez l'authentification dans votre navigateur pour continuer. Cette session restera valide pendant 24 heures.",
"openBrowser": "Ouvrir le navigateur pour s'authentifier",
"waiting": "En attente de l'authentification dans le navigateur...",
"authenticating": "Traitement de l'authentification...",
"signInWith": "Se connecter avec {{provider}}"
},
"errors": {
"authFailed": "Échec de l'authentification. Veuillez vérifier vos identifiants et réessayer.",
"timeout": "L'authentification a expiré. Veuillez réessayer."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "אופקש",
"description": "היכנס למארח זה באמצעות ספק הזהות שלך במקום סיסמה או מפתח. נדרשת הגדרת OPKSSH בשרת.",
"docs": "הצג מסמכים"
},
"dialog": {
"title": "נדרש אימות OPKSSH",
"description": "השלם את האימות בדפדפן שלך כדי להמשיך. סשן זה יישאר תקף למשך 24 שעות.",
"openBrowser": "פתח את הדפדפן כדי לאמת",
"waiting": "ממתין לאימות בדפדפן...",
"authenticating": "מעבד אימות...",
"signInWith": "התחבר באמצעות {{provider}}"
},
"errors": {
"authFailed": "האימות נכשל. אנא בדוק את פרטי הגישה שלך ונסה שוב.",
"timeout": "הזמן שהוקצב לאימות הסתיים. אנא נסה שוב."
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "इस होस्ट में पासवर्ड या कुंजी के बजाय अपने पहचान प्रदाता का उपयोग करके साइन इन करें। सर्वर पर OPKSSH सेटअप आवश्यक है।",
"docs": "दस्तावेज़ देखें"
},
"dialog": {
"title": "OPKSSH प्रमाणीकरण आवश्यक है",
"description": "जारी रखने के लिए अपने ब्राउज़र में प्रमाणीकरण पूरा करें। यह सत्र 24 घंटे के लिए मान्य रहेगा।",
"openBrowser": "प्रमाणीकरण के लिए ब्राउज़र खोलें",
"waiting": "ब्राउज़र में प्रमाणीकरण की प्रतीक्षा हो रही है...",
"authenticating": "प्रमाणीकरण प्रक्रिया में है...",
"signInWith": "{{provider}} से साइन इन करें"
},
"errors": {
"authFailed": "प्रमाणीकरण विफल। कृपया अपने क्रेडेंशियल जाँचें और पुनः प्रयास करें।",
"timeout": "प्रमाणीकरण का समय समाप्त हो गया। कृपया पुनः प्रयास करें।"
}
}
@@ -0,0 +1,19 @@
{
"editor": {
"label": "OPKSSH",
"description": "Jelentkezzen be erre a gazdagépre az identitásszolgáltatójával jelszó vagy kulcs helyett. OPKSSH beállítását igényli a szerveren.",
"docs": "Dokumentumok megtekintése"
},
"dialog": {
"title": "OPKSSH hitelesítés szükséges",
"description": "A folytatáshoz végezze el a hitelesítést a böngészőjében. Ez a munkamenet 24 órán át érvényes marad.",
"openBrowser": "Böngésző megnyitása a hitelesítéshez",
"waiting": "Várakozás a böngésző hitelesítésére...",
"authenticating": "Hitelesítés feldolgozása...",
"signInWith": "Bejelentkezés {{provider}} felhasználónévvel"
},
"errors": {
"authFailed": "Sikertelen hitelesítés. Kérjük, ellenőrizze a hitelesítő adatait, és próbálja újra.",
"timeout": "A hitelesítés időtúllépést okozott. Kérjük, próbálja újra."
}
}
Loaded 100 of 336 files, more files were not shown because too many files have changed in this diff. Show more