mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-24 20:27:51 +00:00
client/web: limit authorization checks to API calls
This completes the migration to setting up authentication state in the client first before fetching any node data or rendering the client view. Notable changes: - `authorizeRequest` is now only enforced on `/api/*` calls (with the exception of /api/auth, which is handled early because it's needed to initially setup auth, particularly for synology) - re-separate the App and WebClient components to ensure that auth is completed before moving on - refactor platform auth (synology and QNAP) to fit into this new structure. Synology no longer returns redirect for auth, but returns authResponse instructing the client to fetch a SynoToken Updates tailscale/corp#14335 Signed-off-by: Will Norris <will@tailscale.com>
This commit is contained in:
@@ -3,17 +3,36 @@ import React from "react"
|
||||
import LegacyClientView from "src/components/views/legacy-client-view"
|
||||
import LoginClientView from "src/components/views/login-client-view"
|
||||
import ReadonlyClientView from "src/components/views/readonly-client-view"
|
||||
import useAuth from "src/hooks/auth"
|
||||
import useAuth, { AuthResponse } from "src/hooks/auth"
|
||||
import useNodeData from "src/hooks/node-data"
|
||||
import ManagementClientView from "./views/management-client-view"
|
||||
|
||||
export default function App() {
|
||||
const { data, refreshData, updateNode } = useNodeData()
|
||||
const { data: auth, loading: loadingAuth, waitOnAuth } = useAuth()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center min-w-sm max-w-lg mx-auto py-14">
|
||||
{!data || loadingAuth ? (
|
||||
{loadingAuth ? (
|
||||
<div className="text-center py-14">Loading...</div> // TODO(sonia): add a loading view
|
||||
) : (
|
||||
<WebClient auth={auth} waitOnAuth={waitOnAuth} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WebClient({
|
||||
auth,
|
||||
waitOnAuth,
|
||||
}: {
|
||||
auth?: AuthResponse
|
||||
waitOnAuth: () => Promise<void>
|
||||
}) {
|
||||
const { data, refreshData, updateNode } = useNodeData()
|
||||
|
||||
return (
|
||||
<>
|
||||
{!data ? (
|
||||
<div className="text-center py-14">Loading...</div> // TODO(sonia): add a loading view
|
||||
) : data?.Status === "NeedsLogin" || data?.Status === "NoState" ? (
|
||||
// Client not on a tailnet, render login.
|
||||
@@ -35,8 +54,8 @@ export default function App() {
|
||||
updateNode={updateNode}
|
||||
/>
|
||||
)}
|
||||
{data && !loadingAuth && <Footer licensesURL={data.LicensesURL} />}
|
||||
</div>
|
||||
{data && <Footer licensesURL={data.LicensesURL} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -16,7 +16,7 @@ export type AuthResponse = {
|
||||
// for the web client.
|
||||
export default function useAuth() {
|
||||
const [data, setData] = useState<AuthResponse>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
|
||||
const loadAuth = useCallback((wait?: boolean) => {
|
||||
const url = wait ? "/auth?wait=true" : "/auth"
|
||||
@@ -24,16 +24,19 @@ export default function useAuth() {
|
||||
return apiFetch(url, "GET")
|
||||
.then((r) => r.json())
|
||||
.then((d) => {
|
||||
if ((d as AuthResponse).authNeeded == AuthType.synology) {
|
||||
fetch("/webman/login.cgi")
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
setSynoToken(data.SynoToken)
|
||||
})
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
setData(d)
|
||||
switch ((d as AuthResponse).authNeeded) {
|
||||
case AuthType.synology:
|
||||
fetch("/webman/login.cgi")
|
||||
.then((r) => r.json())
|
||||
.then((a) => {
|
||||
setSynoToken(a.SynoToken)
|
||||
setLoading(false)
|
||||
})
|
||||
break
|
||||
default:
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false)
|
||||
|
Reference in New Issue
Block a user