mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 13:05:46 +00:00
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 <sonia@tailscale.com>
This commit is contained in:
parent
33147c4591
commit
4f409012c5
@ -1,5 +1,5 @@
|
|||||||
import cx from "classnames"
|
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 { AuthResponse, AuthType } from "src/hooks/auth"
|
||||||
import { NodeData } from "src/hooks/node-data"
|
import { NodeData } from "src/hooks/node-data"
|
||||||
import { ReactComponent as ChevronDown } from "src/icons/chevron-down.svg"
|
import { ReactComponent as ChevronDown } from "src/icons/chevron-down.svg"
|
||||||
@ -81,6 +81,49 @@ function LoginPopoverContent({
|
|||||||
auth: AuthResponse
|
auth: AuthResponse
|
||||||
newSession: () => Promise<void>
|
newSession: () => Promise<void>
|
||||||
}) {
|
}) {
|
||||||
|
/**
|
||||||
|
* 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<boolean>(false)
|
||||||
|
const [isRunningCheck, setIsRunningCheck] = useState<boolean>(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(() => {
|
const handleSignInClick = useCallback(() => {
|
||||||
if (auth.viewerIdentity) {
|
if (auth.viewerIdentity) {
|
||||||
newSession()
|
newSession()
|
||||||
@ -93,7 +136,7 @@ function LoginPopoverContent({
|
|||||||
}, [node.IP, auth.viewerIdentity, newSession])
|
}, [node.IP, auth.viewerIdentity, newSession])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div onMouseEnter={!canConnectOverTS ? checkTSConnection : undefined}>
|
||||||
<div className="text-black text-sm font-medium leading-tight">
|
<div className="text-black text-sm font-medium leading-tight">
|
||||||
{!auth.canManageNode ? "Viewing" : "Managing"}
|
{!auth.canManageNode ? "Viewing" : "Managing"}
|
||||||
{auth.viewerIdentity && ` as ${auth.viewerIdentity.loginName}`}
|
{auth.viewerIdentity && ` as ${auth.viewerIdentity.loginName}`}
|
||||||
@ -117,9 +160,15 @@ function LoginPopoverContent({
|
|||||||
<button
|
<button
|
||||||
className={cx(
|
className={cx(
|
||||||
"w-full px-3 py-2 bg-indigo-500 rounded shadow text-center text-white text-sm font-medium mt-2",
|
"w-full px-3 py-2 bg-indigo-500 rounded shadow text-center text-white text-sm font-medium mt-2",
|
||||||
{ "mb-2": auth.viewerIdentity }
|
{
|
||||||
|
"mb-2": auth.viewerIdentity,
|
||||||
|
"cursor-not-allowed": !canConnectOverTS,
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
onClick={handleSignInClick}
|
onClick={handleSignInClick}
|
||||||
|
// TODO: add some helper info when disabled
|
||||||
|
// due to needing to connect to TS
|
||||||
|
disabled={!canConnectOverTS}
|
||||||
>
|
>
|
||||||
{auth.viewerIdentity ? "Sign in to confirm identity" : "Sign in"}
|
{auth.viewerIdentity ? "Sign in to confirm identity" : "Sign in"}
|
||||||
</button>
|
</button>
|
||||||
@ -144,6 +193,6 @@ function LoginPopoverContent({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user