From 2cb994a29712b874668d0c783d35d2562eacf852 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 30 Oct 2023 11:21:41 -0500 Subject: [PATCH 1/4] fix: prevent infinite redirect oauth auth flow --- coderd/externalauth.go | 5 +++-- .../pages/ExternalAuthPage/ExternalAuthPage.tsx | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/coderd/externalauth.go b/coderd/externalauth.go index 774a5f860397d..b1b7acc8bc449 100644 --- a/coderd/externalauth.go +++ b/coderd/externalauth.go @@ -268,8 +268,9 @@ func (api *API) externalAuthCallback(externalAuthConfig *externalauth.Config) ht redirect := state.Redirect if redirect == "" { - // This is a nicely rendered screen on the frontend - redirect = fmt.Sprintf("/external-auth/%s", externalAuthConfig.ID) + // This is a nicely rendered screen on the frontend. Passing the query param lets the + // FE know not to enter the authentication loop again, and instead display an error. + redirect = fmt.Sprintf("/external-auth/%s?redirected=true", externalAuthConfig.ID) } http.Redirect(rw, r, redirect, http.StatusTemporaryRedirect) } diff --git a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx index d42004b758914..218b7dfa1cf30 100644 --- a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx +++ b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx @@ -72,6 +72,20 @@ const ExternalAuthPage: FC = () => { !getExternalAuthProviderQuery.data.authenticated && !getExternalAuthProviderQuery.data.device ) { + if (window.location.search.includes("redirected=true")) { + // The auth flow redirected the user here. If we redirect back to the + // callback, that resets the flow and we'll end up in an infinite loop. + // So instead, show an error, as the user expects to be authenticated at + // this point. + // TODO: Unsure what to do about the device auth flow, should we also + // show an error there? + return ( + <> + Failed to validate the user's oauth access token. Verify the + external auth validate url is configured correctly. + + ); + } window.location.href = `/external-auth/${provider}/callback`; return null; } From 5bc70e093a6d04867c51ba49bb023b6d5061b55e Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 30 Oct 2023 11:38:19 -0500 Subject: [PATCH 2/4] Update error text --- .../ExternalAuthPage/ExternalAuthPage.tsx | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx index 218b7dfa1cf30..cf2348d2516db 100644 --- a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx +++ b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx @@ -10,6 +10,9 @@ import { useParams } from "react-router-dom"; import ExternalAuthPageView from "./ExternalAuthPageView"; import { ApiErrorResponse } from "api/errors"; import { isAxiosError } from "axios"; +import { Box, Button } from "@mui/material"; +import { SignInLayout } from "components/SignInLayout/SignInLayout"; +import { Welcome } from "components/Welcome/Welcome"; const ExternalAuthPage: FC = () => { const { provider } = useParams(); @@ -80,10 +83,24 @@ const ExternalAuthPage: FC = () => { // TODO: Unsure what to do about the device auth flow, should we also // show an error there? return ( - <> - Failed to validate the user's oauth access token. Verify the - external auth validate url is configured correctly. - + + + + Attempted to validate the user's oauth access token from the + authentication flow. This situation may occur as a result of an + external authentication provider misconfiguration. Verify the + external authentication validation URL is accurately configured. + +
+ +
); } window.location.href = `/external-auth/${provider}/callback`; From 8ea497aea31593e1af08610fa26fa29a50156427 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 30 Oct 2023 11:58:21 -0500 Subject: [PATCH 3/4] Linting --- site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx index cf2348d2516db..25ac63eff3bf8 100644 --- a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx +++ b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx @@ -10,7 +10,8 @@ import { useParams } from "react-router-dom"; import ExternalAuthPageView from "./ExternalAuthPageView"; import { ApiErrorResponse } from "api/errors"; import { isAxiosError } from "axios"; -import { Box, Button } from "@mui/material"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; import { SignInLayout } from "components/SignInLayout/SignInLayout"; import { Welcome } from "components/Welcome/Welcome"; From 891384810ce8a27de27325ff93a8466f05662213 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 30 Oct 2023 13:11:04 -0500 Subject: [PATCH 4/4] useSearchParams hook --- site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx index 25ac63eff3bf8..8cc955b596d4c 100644 --- a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx +++ b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx @@ -6,7 +6,7 @@ import { } from "api/api"; import { usePermissions } from "hooks"; import { type FC } from "react"; -import { useParams } from "react-router-dom"; +import { useParams, useSearchParams } from "react-router-dom"; import ExternalAuthPageView from "./ExternalAuthPageView"; import { ApiErrorResponse } from "api/errors"; import { isAxiosError } from "axios"; @@ -20,6 +20,7 @@ const ExternalAuthPage: FC = () => { if (!provider) { throw new Error("provider must exist"); } + const [searchParams] = useSearchParams(); const permissions = usePermissions(); const queryClient = useQueryClient(); const getExternalAuthProviderQuery = useQuery({ @@ -76,7 +77,8 @@ const ExternalAuthPage: FC = () => { !getExternalAuthProviderQuery.data.authenticated && !getExternalAuthProviderQuery.data.device ) { - if (window.location.search.includes("redirected=true")) { + const redirectedParam = searchParams?.get("redirected"); + if (redirectedParam && redirectedParam.toLowerCase() === "true") { // The auth flow redirected the user here. If we redirect back to the // callback, that resets the flow and we'll end up in an infinite loop. // So instead, show an error, as the user expects to be authenticated at