From 4f409012c5fa7a93b67b5e82fb399be420405691 Mon Sep 17 00:00:00 2001 From: Sonia Appasamy Date: Wed, 15 Nov 2023 19:10:26 -0500 Subject: [PATCH] client/web: when readonly, add check for TS connection When the viewing user is accessing a webclient not over Tailscale, they must connect over Tailscale before being able to log into the full management client, which is served over TS. This change adds a check that the user is able to access the node's tailscale IP. If not able to, the signin button is disabled. We'll also be adding Copy here to help explain to the user that they must connect to Tailscale before proceeding. Updates #10261 Signed-off-by: Sonia Appasamy --- client/web/src/components/login-toggle.tsx | 57 ++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/client/web/src/components/login-toggle.tsx b/client/web/src/components/login-toggle.tsx index 360a0065ccc9b..d96f71ca31060 100644 --- a/client/web/src/components/login-toggle.tsx +++ b/client/web/src/components/login-toggle.tsx @@ -1,5 +1,5 @@ import cx from "classnames" -import React, { useCallback, useState } from "react" +import React, { useCallback, useEffect, useState } from "react" import { AuthResponse, AuthType } from "src/hooks/auth" import { NodeData } from "src/hooks/node-data" import { ReactComponent as ChevronDown } from "src/icons/chevron-down.svg" @@ -81,6 +81,49 @@ function LoginPopoverContent({ auth: AuthResponse newSession: () => Promise }) { + /** + * canConnectOverTS indicates whether the current viewer + * is able to hit the node's web client that's being served + * at http://${node.IP}:5252. If false, this means that the + * viewer must connect to the correct tailnet before being + * able to sign in. + */ + const [canConnectOverTS, setCanConnectOverTS] = useState(false) + const [isRunningCheck, setIsRunningCheck] = useState(false) + + const checkTSConnection = useCallback(() => { + if (auth.viewerIdentity) { + setCanConnectOverTS(true) // already connected over ts + return + } + // Otherwise, test connection to the ts IP. + if (isRunningCheck) { + return // already checking + } + setIsRunningCheck(true) + fetch(`http://${node.IP}:5252/ok`, { mode: "no-cors" }) + .then(() => { + setIsRunningCheck(false) + setCanConnectOverTS(true) + }) + .catch(() => setIsRunningCheck(false)) + }, [ + auth.viewerIdentity, + isRunningCheck, + setCanConnectOverTS, + setIsRunningCheck, + ]) + + /** + * Checking connection for first time on page load. + * + * While not connected, we check again whenever the mouse + * enters the popover component, to pick up on the user + * leaving to turn on Tailscale then returning to the view. + * See `onMouseEnter` on the div below. + */ + useEffect(() => checkTSConnection(), []) + const handleSignInClick = useCallback(() => { if (auth.viewerIdentity) { newSession() @@ -93,7 +136,7 @@ function LoginPopoverContent({ }, [node.IP, auth.viewerIdentity, newSession]) return ( - <> +
{!auth.canManageNode ? "Viewing" : "Managing"} {auth.viewerIdentity && ` as ${auth.viewerIdentity.loginName}`} @@ -117,9 +160,15 @@ function LoginPopoverContent({ @@ -144,6 +193,6 @@ function LoginPopoverContent({
)} - +
) }