From 7de387b987d1f703feeb3b7f23a6c45420829394 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sun, 2 Nov 2025 01:19:29 -0600 Subject: [PATCH] fix: Electron security issues and TOTP/None auth issues --- docker/nginx-https.conf | 10 +- docker/nginx.conf | 9 + electron/main.cjs | 5 +- src/backend/ssh/file-manager.ts | 155 ++++---- src/backend/ssh/terminal.ts | 94 +---- src/ui/desktop/DesktopApp.tsx | 1 - .../desktop/apps/file-manager/FileManager.tsx | 1 + .../authentication/ElectronLoginForm.tsx | 367 ++++++++++-------- src/ui/desktop/navigation/SSHAuthDialog.tsx | 7 +- 9 files changed, 320 insertions(+), 329 deletions(-) diff --git a/docker/nginx-https.conf b/docker/nginx-https.conf index 53caa38b..984e7973 100644 --- a/docker/nginx-https.conf +++ b/docker/nginx-https.conf @@ -34,7 +34,6 @@ http { ssl_certificate_key ${SSL_KEY_PATH}; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - add_header X-Frame-Options DENY always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection "1; mode=block" always; @@ -49,6 +48,15 @@ http { log_not_found off; } + location ~ ^/users/sessions(/.*)?$ { + proxy_pass http://127.0.0.1:30001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location ~ ^/users(/.*)?$ { proxy_pass http://127.0.0.1:30001; proxy_http_version 1.1; diff --git a/docker/nginx.conf b/docker/nginx.conf index eb1d2817..384e64d9 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -45,6 +45,15 @@ http { log_not_found off; } + location ~ ^/users/sessions(/.*)?$ { + proxy_pass http://127.0.0.1:30001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + location ~ ^/users(/.*)?$ { proxy_pass http://127.0.0.1:30001; proxy_http_version 1.1; diff --git a/electron/main.cjs b/electron/main.cjs index 58c0f5b9..9716c1e5 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -62,10 +62,11 @@ function createWindow() { webPreferences: { nodeIntegration: false, contextIsolation: true, - webSecurity: true, + webSecurity: false, preload: path.join(__dirname, "preload.js"), partition: "persist:termix", - allowRunningInsecureContent: false, + allowRunningInsecureContent: true, + webviewTag: true, }, show: false, }); diff --git a/src/backend/ssh/file-manager.ts b/src/backend/ssh/file-manager.ts index b1e0cdba..7a543ab1 100644 --- a/src/backend/ssh/file-manager.ts +++ b/src/backend/ssh/file-manager.ts @@ -57,10 +57,6 @@ app.use( "http://127.0.0.1:3000", ]; - if (allowedOrigins.includes(origin)) { - return callback(null, true); - } - if (origin.startsWith("https://")) { return callback(null, true); } @@ -69,6 +65,10 @@ app.use( return callback(null, true); } + if (allowedOrigins.includes(origin)) { + return callback(null, true); + } + callback(new Error("Not allowed by CORS")); }, credentials: true, @@ -172,6 +172,7 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { keyPassword, authType, credentialId, + userProvidedPassword, } = req.body; const userId = (req as AuthenticatedRequest).userId; @@ -264,44 +265,31 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { keepaliveCountMax: 3, algorithms: { kex: [ - "curve25519-sha256", - "curve25519-sha256@libssh.org", - "ecdh-sha2-nistp521", - "ecdh-sha2-nistp384", - "ecdh-sha2-nistp256", - "diffie-hellman-group-exchange-sha256", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1", - "diffie-hellman-group-exchange-sha1", "diffie-hellman-group1-sha1", - ], - serverHostKey: [ - "ssh-ed25519", - "ecdsa-sha2-nistp521", - "ecdsa-sha2-nistp384", - "ecdsa-sha2-nistp256", - "rsa-sha2-512", - "rsa-sha2-256", - "ssh-rsa", - "ssh-dss", + "diffie-hellman-group-exchange-sha256", + "diffie-hellman-group-exchange-sha1", + "ecdh-sha2-nistp256", + "ecdh-sha2-nistp384", + "ecdh-sha2-nistp521", ], cipher: [ - "chacha20-poly1305@openssh.com", - "aes256-gcm@openssh.com", - "aes128-gcm@openssh.com", - "aes256-ctr", - "aes192-ctr", "aes128-ctr", - "aes256-cbc", - "aes192-cbc", + "aes192-ctr", + "aes256-ctr", + "aes128-gcm@openssh.com", + "aes256-gcm@openssh.com", "aes128-cbc", + "aes192-cbc", + "aes256-cbc", "3des-cbc", ], hmac: [ - "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256-etm@openssh.com", - "hmac-sha2-512", + "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", + "hmac-sha2-512", "hmac-sha1", "hmac-md5", ], @@ -309,8 +297,6 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { }, }; - let authMethodNotAvailable = false; - if ( resolvedCredentials.authType === "key" && resolvedCredentials.sshKey && @@ -348,33 +334,11 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { .status(400) .json({ error: "Password required for password authentication" }); } - config.password = resolvedCredentials.password; + + if (userProvidedPassword) { + config.password = resolvedCredentials.password; + } } else if (resolvedCredentials.authType === "none") { - config.authHandler = ( - methodsLeft: string[] | null, - partialSuccess: boolean, - callback: (nextMethod: string | false) => void, - ) => { - if (methodsLeft && methodsLeft.length > 0) { - if (methodsLeft.includes("keyboard-interactive")) { - callback("keyboard-interactive"); - } else { - authMethodNotAvailable = true; - fileLogger.error( - "Server does not support keyboard-interactive auth", - { - operation: "ssh_auth_handler_no_keyboard", - hostId, - sessionId, - methodsAvailable: methodsLeft, - }, - ); - callback(false); - } - } else { - callback(false); - } - }; } else { fileLogger.warn( "No valid authentication method provided for file manager", @@ -451,36 +415,26 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { client.on("error", (err) => { if (responseSent) return; responseSent = true; + fileLogger.error("SSH connection failed for file manager", { + operation: "file_connect", + sessionId, + hostId, + ip, + port, + username, + error: err.message, + }); - if (authMethodNotAvailable && resolvedCredentials.authType === "none") { - res.status(200).json({ - status: "auth_required", - message: - "The server does not support keyboard-interactive authentication. Please provide credentials.", - reason: "no_keyboard", - }); - } else if ( + if ( resolvedCredentials.authType === "none" && - (err.message.includes("All configured authentication methods failed") || - err.message.includes("No supported authentication methods available") || - err.message.includes("authentication methods failed")) + (err.message.includes("authentication") || + err.message.includes("All configured authentication methods failed")) ) { - res.status(200).json({ + res.json({ status: "auth_required", - message: - "The server does not support keyboard-interactive authentication. Please provide credentials.", reason: "no_keyboard", }); } else { - fileLogger.error("SSH connection failed for file manager", { - operation: "file_connect", - sessionId, - hostId, - ip, - port, - username, - error: err.message, - }); res.status(500).json({ status: "error", message: err.message }); } }); @@ -564,13 +518,43 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { /password/i.test(p.prompt), ); + if ( + resolvedCredentials.authType === "none" && + passwordPromptIndex !== -1 + ) { + if (responseSent) return; + responseSent = true; + + client.end(); + + res.json({ + status: "auth_required", + reason: "no_keyboard", + }); + return; + } + if (!hasStoredPassword && passwordPromptIndex !== -1) { if (responseSent) { + const responses = prompts.map((p) => { + if (/password/i.test(p.prompt) && resolvedCredentials.password) { + return resolvedCredentials.password; + } + return ""; + }); + finish(responses); return; } responseSent = true; if (pendingTOTPSessions[sessionId]) { + const responses = prompts.map((p) => { + if (/password/i.test(p.prompt) && resolvedCredentials.password) { + return resolvedCredentials.password; + } + return ""; + }); + finish(responses); return; } @@ -2446,6 +2430,15 @@ app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => { : code; const cleanOutput = output.replace(/EXIT_CODE:\d+$/, "").trim(); + fileLogger.info("File execution completed", { + operation: "execute_file", + sessionId, + filePath, + exitCode: actualExitCode, + outputLength: cleanOutput.length, + errorLength: errorOutput.length, + }); + res.json({ success: true, exitCode: actualExitCode, diff --git a/src/backend/ssh/terminal.ts b/src/backend/ssh/terminal.ts index 3b986b53..12af8974 100644 --- a/src/backend/ssh/terminal.ts +++ b/src/backend/ssh/terminal.ts @@ -64,47 +64,21 @@ const wss = new WebSocketServer({ const token = url.query.token as string; if (!token) { - sshLogger.warn("WebSocket connection rejected: missing token", { - operation: "websocket_auth_reject", - reason: "missing_token", - ip: info.req.socket.remoteAddress, - }); return false; } const payload = await authManager.verifyJWTToken(token); if (!payload) { - sshLogger.warn("WebSocket connection rejected: invalid token", { - operation: "websocket_auth_reject", - reason: "invalid_token", - ip: info.req.socket.remoteAddress, - }); return false; } if (payload.pendingTOTP) { - sshLogger.warn( - "WebSocket connection rejected: TOTP verification pending", - { - operation: "websocket_auth_reject", - reason: "totp_pending", - userId: payload.userId, - ip: info.req.socket.remoteAddress, - }, - ); return false; } const existingConnections = userConnections.get(payload.userId); if (existingConnections && existingConnections.size >= 3) { - sshLogger.warn("WebSocket connection rejected: too many connections", { - operation: "websocket_auth_reject", - reason: "connection_limit", - userId: payload.userId, - currentConnections: existingConnections.size, - ip: info.req.socket.remoteAddress, - }); return false; } @@ -127,28 +101,12 @@ wss.on("connection", async (ws: WebSocket, req) => { const token = url.query.token as string; if (!token) { - sshLogger.warn( - "WebSocket connection rejected: missing token in connection", - { - operation: "websocket_connection_reject", - reason: "missing_token", - ip: req.socket.remoteAddress, - }, - ); ws.close(1008, "Authentication required"); return; } const payload = await authManager.verifyJWTToken(token); if (!payload) { - sshLogger.warn( - "WebSocket connection rejected: invalid token in connection", - { - operation: "websocket_connection_reject", - reason: "invalid_token", - ip: req.socket.remoteAddress, - }, - ); ws.close(1008, "Authentication required"); return; } @@ -169,11 +127,6 @@ wss.on("connection", async (ws: WebSocket, req) => { const dataKey = userCrypto.getUserDataKey(userId); if (!dataKey) { - sshLogger.warn("WebSocket connection rejected: data locked", { - operation: "websocket_data_locked", - userId, - ip: req.socket.remoteAddress, - }); ws.send( JSON.stringify({ type: "error", @@ -213,11 +166,6 @@ wss.on("connection", async (ws: WebSocket, req) => { ws.on("message", (msg: RawData) => { const currentDataKey = userCrypto.getUserDataKey(userId); if (!currentDataKey) { - sshLogger.warn("WebSocket message rejected: data access expired", { - operation: "websocket_message_rejected", - userId, - reason: "data_access_expired", - }); ws.send( JSON.stringify({ type: "error", @@ -371,6 +319,7 @@ wss.on("connection", async (ws: WebSocket, req) => { if (credentialsData.password) { credentialsData.hostConfig.password = credentialsData.password; credentialsData.hostConfig.authType = "password"; + (credentialsData.hostConfig as any).userProvidedPassword = true; } else if (credentialsData.sshKey) { credentialsData.hostConfig.key = credentialsData.sshKey; credentialsData.hostConfig.keyPassword = credentialsData.keyPassword; @@ -776,13 +725,6 @@ wss.on("connection", async (ws: WebSocket, req) => { sshConn.on("close", () => { clearTimeout(connectionTimeout); - sshLogger.warn("SSH connection closed by server", { - operation: "ssh_close", - hostId: id, - ip, - port, - hadStream: !!sshStream, - }); cleanupSSH(connectionTimeout); }); @@ -795,15 +737,6 @@ wss.on("connection", async (ws: WebSocket, req) => { prompts: Array<{ prompt: string; echo: boolean }>, finish: (responses: string[]) => void, ) => { - if (resolvedCredentials.authType === "none") { - ws.send( - JSON.stringify({ - type: "keyboard_interactive_available", - message: "Keyboard-interactive authentication is available", - }), - ); - } - const promptTexts = prompts.map((p) => p.prompt); const totpPromptIndex = prompts.findIndex((p) => /verification code|verification_code|token|otp|2fa|authenticator|google.*auth/i.test( @@ -854,7 +787,7 @@ wss.on("connection", async (ws: WebSocket, req) => { ); if (!hasStoredPassword && passwordPromptIndex !== -1) { - if (keyboardInteractiveResponded && totpPromptSent) { + if (keyboardInteractiveResponded) { return; } keyboardInteractiveResponded = true; @@ -898,7 +831,7 @@ wss.on("connection", async (ws: WebSocket, req) => { host: ip, port, username, - tryKeyboard: resolvedCredentials.authType === "none", + tryKeyboard: true, keepaliveInterval: 30000, keepaliveCountMax: 3, readyTimeout: 60000, @@ -964,22 +897,6 @@ wss.on("connection", async (ws: WebSocket, req) => { }; if (resolvedCredentials.authType === "none") { - connectConfig.authHandler = ( - methodsLeft: string[] | null, - partialSuccess: boolean, - callback: (nextMethod: string | false) => void, - ) => { - if (methodsLeft && methodsLeft.length > 0) { - if (methodsLeft.includes("keyboard-interactive")) { - callback("keyboard-interactive"); - } else { - authMethodNotAvailable = true; - callback(false); - } - } else { - callback(false); - } - }; } else if (resolvedCredentials.authType === "password") { if (!resolvedCredentials.password) { sshLogger.error( @@ -994,7 +911,10 @@ wss.on("connection", async (ws: WebSocket, req) => { ); return; } - connectConfig.password = resolvedCredentials.password; + + if ((hostConfig as any).userProvidedPassword) { + connectConfig.password = resolvedCredentials.password; + } } else if ( resolvedCredentials.authType === "key" && resolvedCredentials.key diff --git a/src/ui/desktop/DesktopApp.tsx b/src/ui/desktop/DesktopApp.tsx index 4d8c2b16..5ad4c465 100644 --- a/src/ui/desktop/DesktopApp.tsx +++ b/src/ui/desktop/DesktopApp.tsx @@ -29,7 +29,6 @@ function AppContent() { setAuthLoading(true); getUserInfo() .then((meRes) => { - // Check if response is actually HTML (Vite dev server page) if (typeof meRes === "string" || !meRes.username) { setIsAuthenticated(false); setIsAdmin(false); diff --git a/src/ui/desktop/apps/file-manager/FileManager.tsx b/src/ui/desktop/apps/file-manager/FileManager.tsx index 37cfefa0..f1d0c0ec 100644 --- a/src/ui/desktop/apps/file-manager/FileManager.tsx +++ b/src/ui/desktop/apps/file-manager/FileManager.tsx @@ -1344,6 +1344,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) { authType: credentials.password ? "password" : "key", credentialId: currentHost.credentialId, userId: currentHost.userId, + userProvidedPassword: true, }); if (result?.requires_totp) { diff --git a/src/ui/desktop/authentication/ElectronLoginForm.tsx b/src/ui/desktop/authentication/ElectronLoginForm.tsx index e026a7e6..ae6f7349 100644 --- a/src/ui/desktop/authentication/ElectronLoginForm.tsx +++ b/src/ui/desktop/authentication/ElectronLoginForm.tsx @@ -5,6 +5,22 @@ import { useTranslation } from "react-i18next"; import { AlertCircle, Loader2, ArrowLeft, RefreshCw } from "lucide-react"; import { getCookie, getUserInfo } from "@/ui/main-axios.ts"; +declare global { + namespace JSX { + interface IntrinsicElements { + webview: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + > & { + src?: string; + partition?: string; + allowpopups?: string; + ref?: React.Ref; + }; + } + } +} + interface ElectronLoginFormProps { serverUrl: string; onAuthSuccess: () => void; @@ -20,10 +36,12 @@ export function ElectronLoginForm({ const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isAuthenticating, setIsAuthenticating] = useState(false); - const iframeRef = useRef(null); + const webviewRef = useRef(null); const hasAuthenticatedRef = useRef(false); const [currentUrl, setCurrentUrl] = useState(serverUrl); const hasLoadedOnce = useRef(false); + const urlCheckInterval = useRef(null); + const loadTimeout = useRef(null); useEffect(() => { const handleMessage = async (event: MessageEvent) => { @@ -57,7 +75,17 @@ export function ElectronLoginForm({ await getUserInfo(); } catch (verifyErr) { localStorage.removeItem("jwt"); - throw new Error("Invalid or expired authentication token"); + const errorMsg = + verifyErr instanceof Error + ? verifyErr.message + : "Failed to verify authentication"; + console.error("Authentication verification failed:", verifyErr); + throw new Error( + errorMsg.includes("registration") || + errorMsg.includes("allowed") + ? "Authentication failed. Please check your server connection and try again." + : errorMsg, + ); } await new Promise((resolve) => setTimeout(resolve, 500)); @@ -85,159 +113,186 @@ export function ElectronLoginForm({ }, [serverUrl, isAuthenticating, onAuthSuccess, t]); useEffect(() => { - const iframe = iframeRef.current; - if (!iframe) return; + const checkWebviewUrl = () => { + const webview = webviewRef.current; + if (!webview) return; + + try { + const webviewUrl = webview.getURL(); + if (webviewUrl && webviewUrl !== currentUrl) { + setCurrentUrl(webviewUrl); + } + } catch (e) {} + }; + + urlCheckInterval.current = setInterval(checkWebviewUrl, 500); + + return () => { + if (urlCheckInterval.current) { + clearInterval(urlCheckInterval.current); + urlCheckInterval.current = null; + } + }; + }, [currentUrl]); + + useEffect(() => { + const webview = webviewRef.current; + if (!webview) return; + + loadTimeout.current = setTimeout(() => { + if (!hasLoadedOnce.current && loading) { + setLoading(false); + setError( + "Unable to connect to server. Please check the server URL and try again.", + ); + } + }, 15000); const handleLoad = () => { + if (loadTimeout.current) { + clearTimeout(loadTimeout.current); + loadTimeout.current = null; + } + setLoading(false); hasLoadedOnce.current = true; setError(null); try { - if (iframe.contentWindow) { - setCurrentUrl(iframe.contentWindow.location.href); - } + const webviewUrl = webview.getURL(); + setCurrentUrl(webviewUrl || serverUrl); } catch (e) { setCurrentUrl(serverUrl); } - try { - const injectedScript = ` - (function() { - window.IS_ELECTRON = true; - if (typeof window.electronAPI === 'undefined') { - window.electronAPI = { isElectron: true }; + const injectedScript = ` + (function() { + window.IS_ELECTRON = true; + if (typeof window.electronAPI === 'undefined') { + window.electronAPI = { isElectron: true }; + } + + let hasNotified = false; + + function postJWTToParent(token, source) { + if (hasNotified) { + return; } + hasNotified = true; - let hasNotified = false; - - function postJWTToParent(token, source) { - if (hasNotified) { - return; - } - hasNotified = true; - - try { - window.parent.postMessage({ - type: 'AUTH_SUCCESS', - token: token, - source: source, - platform: 'desktop', - timestamp: Date.now() - }, '*'); - } catch (e) { - } - } - - function clearAuthData() { - try { - localStorage.removeItem('jwt'); - sessionStorage.removeItem('jwt'); - - const cookies = document.cookie.split(';'); - for (let i = 0; i < cookies.length; i++) { - const cookie = cookies[i]; - const eqPos = cookie.indexOf('='); - const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim(); - if (name === 'jwt') { - document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/'; - document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=' + window.location.hostname; - } - } - } catch (error) { - } - } - - window.addEventListener('message', function(event) { - try { - if (event.data && typeof event.data === 'object') { - if (event.data.type === 'CLEAR_AUTH_DATA') { - clearAuthData(); - } - } - } catch (error) { - } - }); - - function checkAuth() { - try { - const localToken = localStorage.getItem('jwt'); - if (localToken && localToken.length > 20) { - postJWTToParent(localToken, 'localStorage'); - return true; - } - - const sessionToken = sessionStorage.getItem('jwt'); - if (sessionToken && sessionToken.length > 20) { - postJWTToParent(sessionToken, 'sessionStorage'); - return true; - } - - const cookies = document.cookie; - if (cookies && cookies.length > 0) { - const cookieArray = cookies.split('; '); - const tokenCookie = cookieArray.find(row => row.startsWith('jwt=')); - - if (tokenCookie) { - const token = tokenCookie.split('=')[1]; - if (token && token.length > 20) { - postJWTToParent(token, 'cookie'); - return true; - } - } - } - } catch (error) { - } - return false; - } - - const originalSetItem = localStorage.setItem; - localStorage.setItem = function(key, value) { - originalSetItem.apply(this, arguments); - if (key === 'jwt' && value && value.length > 20 && !hasNotified) { - setTimeout(() => checkAuth(), 100); - } - }; - - const originalSessionSetItem = sessionStorage.setItem; - sessionStorage.setItem = function(key, value) { - originalSessionSetItem.apply(this, arguments); - if (key === 'jwt' && value && value.length > 20 && !hasNotified) { - setTimeout(() => checkAuth(), 100); - } - }; - - const intervalId = setInterval(() => { - if (hasNotified) { - clearInterval(intervalId); - return; - } - if (checkAuth()) { - clearInterval(intervalId); - } - }, 500); - - setTimeout(() => { - clearInterval(intervalId); - }, 300000); - - setTimeout(() => checkAuth(), 500); - })(); - `; - - try { - if (iframe.contentWindow) { try { - iframe.contentWindow.eval(injectedScript); - } catch (evalError) { - iframe.contentWindow.postMessage( - { type: "INJECT_SCRIPT", script: injectedScript }, - "*", - ); + window.parent.postMessage({ + type: 'AUTH_SUCCESS', + token: token, + source: source, + platform: 'desktop', + timestamp: Date.now() + }, '*'); + } catch (e) { } } - } catch (err) {} - } catch (err) {} + + function clearAuthData() { + try { + localStorage.removeItem('jwt'); + sessionStorage.removeItem('jwt'); + + const cookies = document.cookie.split(';'); + for (let i = 0; i < cookies.length; i++) { + const cookie = cookies[i]; + const eqPos = cookie.indexOf('='); + const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim(); + if (name === 'jwt') { + document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/'; + document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=' + window.location.hostname; + } + } + } catch (error) { + } + } + + window.addEventListener('message', function(event) { + try { + if (event.data && typeof event.data === 'object') { + if (event.data.type === 'CLEAR_AUTH_DATA') { + clearAuthData(); + } + } + } catch (error) { + } + }); + + function checkAuth() { + try { + const localToken = localStorage.getItem('jwt'); + if (localToken && localToken.length > 20) { + postJWTToParent(localToken, 'localStorage'); + return true; + } + + const sessionToken = sessionStorage.getItem('jwt'); + if (sessionToken && sessionToken.length > 20) { + postJWTToParent(sessionToken, 'sessionStorage'); + return true; + } + + const cookies = document.cookie; + if (cookies && cookies.length > 0) { + const cookieArray = cookies.split('; '); + const tokenCookie = cookieArray.find(row => row.startsWith('jwt=')); + + if (tokenCookie) { + const token = tokenCookie.split('=')[1]; + if (token && token.length > 20) { + postJWTToParent(token, 'cookie'); + return true; + } + } + } + } catch (error) { + } + return false; + } + + const originalSetItem = localStorage.setItem; + localStorage.setItem = function(key, value) { + originalSetItem.apply(this, arguments); + if (key === 'jwt' && value && value.length > 20 && !hasNotified) { + setTimeout(() => checkAuth(), 100); + } + }; + + const originalSessionSetItem = sessionStorage.setItem; + sessionStorage.setItem = function(key, value) { + originalSessionSetItem.apply(this, arguments); + if (key === 'jwt' && value && value.length > 20 && !hasNotified) { + setTimeout(() => checkAuth(), 100); + } + }; + + const intervalId = setInterval(() => { + if (hasNotified) { + clearInterval(intervalId); + return; + } + if (checkAuth()) { + clearInterval(intervalId); + } + }, 500); + + setTimeout(() => { + clearInterval(intervalId); + }, 300000); + + setTimeout(() => checkAuth(), 500); + })(); + `; + + try { + webview.executeJavaScript(injectedScript); + } catch (err) { + console.error("Failed to inject authentication script:", err); + } }; const handleError = () => { @@ -247,18 +302,27 @@ export function ElectronLoginForm({ } }; - iframe.addEventListener("load", handleLoad); - iframe.addEventListener("error", handleError); + webview.addEventListener("did-finish-load", handleLoad); + webview.addEventListener("did-fail-load", handleError); return () => { - iframe.removeEventListener("load", handleLoad); - iframe.removeEventListener("error", handleError); + webview.removeEventListener("did-finish-load", handleLoad); + webview.removeEventListener("did-fail-load", handleError); + if (loadTimeout.current) { + clearTimeout(loadTimeout.current); + loadTimeout.current = null; + } }; - }, [t]); + }, [t, loading, serverUrl]); const handleRefresh = () => { - if (iframeRef.current) { - iframeRef.current.src = serverUrl; + if (webviewRef.current) { + if (loadTimeout.current) { + clearTimeout(loadTimeout.current); + loadTimeout.current = null; + } + + webviewRef.current.src = serverUrl; setLoading(true); setError(null); } @@ -336,14 +400,13 @@ export function ElectronLoginForm({ )}
-