diff --git a/app/routes/auth/login.tsx b/app/routes/auth/login.tsx index 6862da0..02803f4 100644 --- a/app/routes/auth/login.tsx +++ b/app/routes/auth/login.tsx @@ -1,7 +1,9 @@ +import { useEffect } from 'react'; import { type ActionFunctionArgs, type LoaderFunctionArgs, redirect, + useSearchParams, } from 'react-router'; import { Form, useActionData, useLoaderData } from 'react-router'; import Button from '~/components/Button'; @@ -15,6 +17,9 @@ export async function loader({ request, context, }: LoaderFunctionArgs) { + const qp = new URL(request.url).searchParams; + const state = qp.get('s'); + try { const session = await context.sessions.auth(request); if (session.has('api_key')) { @@ -24,12 +29,18 @@ export async function loader({ const disableApiKeyLogin = context.config.oidc?.disable_api_key_login; if (context.oidc && disableApiKeyLogin) { - return redirect('/oidc/start'); + // Prevents automatic redirect loop if OIDC is enabled and API key login is disabled + // Since logging out would just log back in based on the redirects + + if (state !== 'logout') { + return redirect('/oidc/start'); + } } return { oidc: context.oidc, disableApiKeyLogin, + state, }; } @@ -92,14 +103,47 @@ export async function action({ } export default function Page() { - const data = useLoaderData(); + const { state, disableApiKeyLogin, oidc } = useLoaderData(); const actionData = useActionData(); + const [params] = useSearchParams(); + + useEffect(() => { + // State is a one time thing, we need to remove it after it has + // been consumed to prevent logic loops. + if (state !== null) { + const searchParams = new URLSearchParams(params); + searchParams.delete('s'); + + // Replacing because it's not a navigation, just a cleanup of the URL + // We can't use the useSearchParams method since it revalidates + // which will trigger a full reload + const newUrl = searchParams.toString() + ? `{${window.location.pathname}?${searchParams.toString()}` + : window.location.pathname; + + window.history.replaceState(null, '', newUrl); + } + }, [state, params]); + + if (state === 'logout') { + return ( +
+ + You have been logged out + + You can now close this window. If you would like to log in again, + please refresh the page. + + +
+ ); + } return (
Welcome to Headplane - {!data.disableApiKeyLogin ? ( + {!disableApiKeyLogin ? (
Enter an API key to authenticate with Headplane. You can generate @@ -124,19 +168,12 @@ export default function Page() { ) : undefined} - {data.oidc ? ( + {oidc ? (
- {data.disableApiKeyLogin ? ( - - Sign in with your authentication provider to continue. Your - administrator has disabled API key login. - - ) : undefined} -