mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(site): improve minor queries and visuals on external auth (#11066)
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import * as API from "api/api";
|
||||
import { ExternalAuth } from "api/typesGenerated";
|
||||
import { QueryClient, UseMutationOptions } from "react-query";
|
||||
|
||||
// Returns all configured external auths for a given user.
|
||||
export const externalAuths = () => {
|
||||
return {
|
||||
queryKey: ["external-auth"],
|
||||
queryFn: () => API.getUserExternalAuthProviders(),
|
||||
};
|
||||
};
|
||||
|
||||
export const externalAuthProvider = (providerId: string) => {
|
||||
return {
|
||||
queryKey: ["external-auth", providerId],
|
||||
queryFn: () => API.getExternalAuthProvider(providerId),
|
||||
};
|
||||
};
|
||||
|
||||
export const externalAuthDevice = (providerId: string) => {
|
||||
return {
|
||||
queryFn: () => API.getExternalAuthDevice(providerId),
|
||||
queryKey: ["external-auth", providerId, "device"],
|
||||
};
|
||||
};
|
||||
|
||||
export const exchangeExternalAuthDevice = (
|
||||
providerId: string,
|
||||
deviceCode: string,
|
||||
queryClient: QueryClient,
|
||||
) => {
|
||||
return {
|
||||
queryFn: () =>
|
||||
API.exchangeExternalAuthDevice(providerId, {
|
||||
device_code: deviceCode,
|
||||
}),
|
||||
queryKey: ["external-auth", providerId, "device", deviceCode],
|
||||
onSuccess: async () => {
|
||||
// Force a refresh of the Git auth status.
|
||||
await queryClient.invalidateQueries(["external-auth", providerId]);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const validateExternalAuth = (
|
||||
queryClient: QueryClient,
|
||||
): UseMutationOptions<ExternalAuth, unknown, string> => {
|
||||
return {
|
||||
mutationFn: API.getExternalAuthProvider,
|
||||
onSuccess: (data, providerId) => {
|
||||
queryClient.setQueryData(["external-auth", providerId], data);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const unlinkExternalAuths = (queryClient: QueryClient) => {
|
||||
return {
|
||||
mutationFn: API.unlinkExternalAuthProvider,
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries(["external-auth"]);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import * as API from "api/api";
|
||||
import { QueryClient } from "react-query";
|
||||
|
||||
const getUserExternalAuthsKey = () => ["list", "external-auth"];
|
||||
|
||||
// listUserExternalAuths returns all configured external auths for a given user.
|
||||
export const listUserExternalAuths = () => {
|
||||
return {
|
||||
queryKey: getUserExternalAuthsKey(),
|
||||
queryFn: () => API.getUserExternalAuthProviders(),
|
||||
};
|
||||
};
|
||||
|
||||
const getUserExternalAuthKey = (providerID: string) => [
|
||||
providerID,
|
||||
"get",
|
||||
"external-auth",
|
||||
];
|
||||
|
||||
export const userExternalAuth = (providerID: string) => {
|
||||
return {
|
||||
queryKey: getUserExternalAuthKey(providerID),
|
||||
queryFn: () => API.getExternalAuthProvider(providerID),
|
||||
};
|
||||
};
|
||||
|
||||
export const validateExternalAuth = (_: QueryClient) => {
|
||||
return {
|
||||
mutationFn: API.getExternalAuthProvider,
|
||||
};
|
||||
};
|
||||
|
||||
export const unlinkExternalAuths = (queryClient: QueryClient) => {
|
||||
return {
|
||||
mutationFn: API.unlinkExternalAuthProvider,
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries(["external-auth"]);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,4 @@
|
||||
import { useQuery, useQueryClient } from "react-query";
|
||||
import {
|
||||
exchangeExternalAuthDevice,
|
||||
getExternalAuthDevice,
|
||||
getExternalAuthProvider,
|
||||
} from "api/api";
|
||||
import { usePermissions } from "hooks";
|
||||
import { type FC } from "react";
|
||||
import { useParams, useSearchParams } from "react-router-dom";
|
||||
@@ -13,56 +8,44 @@ import { isAxiosError } from "axios";
|
||||
import Button from "@mui/material/Button";
|
||||
import { SignInLayout } from "components/SignInLayout/SignInLayout";
|
||||
import { Welcome } from "components/Welcome/Welcome";
|
||||
import {
|
||||
externalAuthDevice,
|
||||
externalAuthProvider,
|
||||
exchangeExternalAuthDevice,
|
||||
} from "api/queries/externalAuth";
|
||||
|
||||
const ExternalAuthPage: FC = () => {
|
||||
const { provider } = useParams();
|
||||
if (!provider) {
|
||||
throw new Error("provider must exist");
|
||||
}
|
||||
const { provider } = useParams() as { provider: string };
|
||||
const [searchParams] = useSearchParams();
|
||||
const permissions = usePermissions();
|
||||
const queryClient = useQueryClient();
|
||||
const getExternalAuthProviderQuery = useQuery({
|
||||
queryKey: ["externalauth", provider],
|
||||
queryFn: () => getExternalAuthProvider(provider),
|
||||
const externalAuthProviderOpts = externalAuthProvider(provider);
|
||||
const externalAuthProviderQuery = useQuery({
|
||||
...externalAuthProviderOpts,
|
||||
refetchOnWindowFocus: true,
|
||||
});
|
||||
|
||||
const getExternalAuthDeviceQuery = useQuery({
|
||||
const externalAuthDeviceQuery = useQuery({
|
||||
...externalAuthDevice(provider),
|
||||
enabled:
|
||||
Boolean(!getExternalAuthProviderQuery.data?.authenticated) &&
|
||||
Boolean(getExternalAuthProviderQuery.data?.device),
|
||||
queryFn: () => getExternalAuthDevice(provider),
|
||||
queryKey: ["externalauth", provider, "device"],
|
||||
Boolean(!externalAuthProviderQuery.data?.authenticated) &&
|
||||
Boolean(externalAuthProviderQuery.data?.device),
|
||||
refetchOnMount: false,
|
||||
});
|
||||
const exchangeExternalAuthDeviceQuery = useQuery({
|
||||
queryFn: () =>
|
||||
exchangeExternalAuthDevice(provider, {
|
||||
device_code: getExternalAuthDeviceQuery.data?.device_code || "",
|
||||
}),
|
||||
queryKey: [
|
||||
"externalauth",
|
||||
...exchangeExternalAuthDevice(
|
||||
provider,
|
||||
getExternalAuthDeviceQuery.data?.device_code,
|
||||
],
|
||||
enabled: Boolean(getExternalAuthDeviceQuery.data),
|
||||
onSuccess: () => {
|
||||
// Force a refresh of the Git auth status.
|
||||
queryClient.invalidateQueries(["externalauth", provider]).catch((ex) => {
|
||||
console.error("invalidate queries", ex);
|
||||
});
|
||||
},
|
||||
externalAuthDeviceQuery.data?.device_code ?? "",
|
||||
queryClient,
|
||||
),
|
||||
enabled: Boolean(externalAuthDeviceQuery.data),
|
||||
retry: true,
|
||||
retryDelay: (getExternalAuthDeviceQuery.data?.interval || 5) * 1000,
|
||||
retryDelay: (externalAuthDeviceQuery.data?.interval || 5) * 1000,
|
||||
refetchOnWindowFocus: (query) =>
|
||||
query.state.status === "success" ? false : "always",
|
||||
});
|
||||
|
||||
if (
|
||||
getExternalAuthProviderQuery.isLoading ||
|
||||
!getExternalAuthProviderQuery.data
|
||||
) {
|
||||
if (externalAuthProviderQuery.isLoading || !externalAuthProviderQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -73,8 +56,8 @@ const ExternalAuthPage: FC = () => {
|
||||
}
|
||||
|
||||
if (
|
||||
!getExternalAuthProviderQuery.data.authenticated &&
|
||||
!getExternalAuthProviderQuery.data.device
|
||||
!externalAuthProviderQuery.data.authenticated &&
|
||||
!externalAuthProviderQuery.data.device
|
||||
) {
|
||||
const redirectedParam = searchParams?.get("redirected");
|
||||
if (redirectedParam && redirectedParam.toLowerCase() === "true") {
|
||||
@@ -111,16 +94,16 @@ const ExternalAuthPage: FC = () => {
|
||||
|
||||
return (
|
||||
<ExternalAuthPageView
|
||||
externalAuth={getExternalAuthProviderQuery.data}
|
||||
externalAuth={externalAuthProviderQuery.data}
|
||||
onReauthenticate={() => {
|
||||
queryClient.setQueryData(["externalauth", provider], {
|
||||
...getExternalAuthProviderQuery.data,
|
||||
queryClient.setQueryData(externalAuthProviderOpts.queryKey, {
|
||||
...externalAuthProviderQuery.data,
|
||||
authenticated: false,
|
||||
});
|
||||
}}
|
||||
viewExternalAuthConfig={permissions.viewExternalAuthConfig}
|
||||
deviceExchangeError={deviceExchangeError}
|
||||
externalAuthDevice={getExternalAuthDeviceQuery.data}
|
||||
externalAuthDevice={externalAuthDeviceQuery.data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { FC, useState } from "react";
|
||||
import { UserExternalAuthSettingsPageView } from "./UserExternalAuthSettingsPageView";
|
||||
import {
|
||||
listUserExternalAuths,
|
||||
externalAuths,
|
||||
unlinkExternalAuths,
|
||||
validateExternalAuth,
|
||||
} from "api/queries/externalauth";
|
||||
} from "api/queries/externalAuth";
|
||||
import { Section } from "components/SettingsLayout/Section";
|
||||
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
@@ -17,30 +17,17 @@ const UserExternalAuthSettingsPage: FC = () => {
|
||||
// need to be refetched
|
||||
const [unlinked, setUnlinked] = useState(0);
|
||||
|
||||
const {
|
||||
data: externalAuths,
|
||||
error,
|
||||
isLoading,
|
||||
refetch,
|
||||
} = useQuery(listUserExternalAuths());
|
||||
|
||||
const externalAuthsQuery = useQuery(externalAuths());
|
||||
const [appToUnlink, setAppToUnlink] = useState<string>();
|
||||
const mutateParams = unlinkExternalAuths(queryClient);
|
||||
const unlinkAppMutation = useMutation({
|
||||
...mutateParams,
|
||||
onSuccess: async () => {
|
||||
await mutateParams.onSuccess();
|
||||
},
|
||||
});
|
||||
|
||||
const unlinkAppMutation = useMutation(unlinkExternalAuths(queryClient));
|
||||
const validateAppMutation = useMutation(validateExternalAuth(queryClient));
|
||||
|
||||
return (
|
||||
<Section title="External Authentication">
|
||||
<Section title="External Authentication" layout="fluid">
|
||||
<UserExternalAuthSettingsPageView
|
||||
isLoading={isLoading}
|
||||
getAuthsError={error}
|
||||
auths={externalAuths}
|
||||
isLoading={externalAuthsQuery.isLoading}
|
||||
getAuthsError={externalAuthsQuery.error}
|
||||
auths={externalAuthsQuery.data}
|
||||
unlinked={unlinked}
|
||||
onUnlinkExternalAuth={(providerID: string) => {
|
||||
setAppToUnlink(providerID);
|
||||
@@ -81,7 +68,7 @@ const UserExternalAuthSettingsPage: FC = () => {
|
||||
// setAppToUnlink closes the modal
|
||||
setAppToUnlink(undefined);
|
||||
// refetch repopulates the external auth data
|
||||
await refetch();
|
||||
await externalAuthsQuery.refetch();
|
||||
// this tells our child components to refetch their data
|
||||
// as at least 1 provider was unlinked.
|
||||
setUnlinked(unlinked + 1);
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
import { ExternalAuthPollingState } from "pages/CreateWorkspacePage/CreateWorkspacePage";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { userExternalAuth } from "api/queries/externalauth";
|
||||
import { externalAuthProvider } from "api/queries/externalAuth";
|
||||
import { FullScreenLoader } from "components/Loader/FullScreenLoader";
|
||||
|
||||
export type UserExternalAuthSettingsPageViewProps = {
|
||||
@@ -61,7 +61,7 @@ export const UserExternalAuthSettingsPageView = ({
|
||||
<TableRow>
|
||||
<TableCell>Application</TableCell>
|
||||
<TableCell>Link</TableCell>
|
||||
<TableCell></TableCell>
|
||||
<TableCell width="1%"></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -150,7 +150,7 @@ const ExternalAuthRow = ({
|
||||
message={authenticated ? "Authenticated" : "Click to Login"}
|
||||
externalAuthPollingState={externalAuthPollingState}
|
||||
startPollingExternalAuth={startPollingExternalAuth}
|
||||
></ExternalAuth>
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{(link || externalAuth?.authenticated) && (
|
||||
@@ -199,7 +199,7 @@ const useExternalAuth = (providerID: string, unlinked: number) => {
|
||||
}, []);
|
||||
|
||||
const { data: externalAuth, refetch } = useQuery({
|
||||
...userExternalAuth(providerID),
|
||||
...externalAuthProvider(providerID),
|
||||
refetchInterval: externalAuthPollingState === "polling" ? 1000 : false,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user