feat: better error handling for oidc api key

This also ships with a better error page
This commit is contained in:
Aarnav Tale
2025-11-28 17:54:33 -05:00
parent c0c4ecf631
commit d3d7c7cc0e
16 changed files with 476 additions and 256 deletions
+39 -17
View File
@@ -31,7 +31,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
const ssoOnly = context.config.oidc?.disable_api_key_login;
if (urlState !== 'logout' && ssoOnly) {
// This shouldn't be possible, but still a safe sanity check
if (!context.oidc) {
if (!context.oidc || typeof context.oidc === 'string') {
throw data(
'`oidc.disable_api_key_login` was set without a valid OIDC configuration',
{
@@ -44,8 +44,10 @@ export async function loader({ request, context }: Route.LoaderArgs) {
}
return {
isOidcEnabled: context.oidc !== undefined,
isCookieSecureEnabled: context.config.server.cookie_secure,
isOidcEnabled: context.oidc !== undefined,
oidcErrorMessage:
typeof context.oidc === 'string' ? context.oidc : undefined,
urlState,
};
}
@@ -53,7 +55,8 @@ export async function loader({ request, context }: Route.LoaderArgs) {
export const action = loginAction;
export default function Page({ loaderData, actionData }: Route.ComponentProps) {
const { isOidcEnabled, isCookieSecureEnabled, urlState } = loaderData;
const { isOidcEnabled, isCookieSecureEnabled, oidcErrorMessage, urlState } =
loaderData;
const [showCookieWarning, setShowCookieWarning] = useState(false);
const [params] = useSearchParams();
const { pause } = useLiveData();
@@ -92,25 +95,40 @@ export default function Page({ loaderData, actionData }: Route.ComponentProps) {
return (
<div className="flex w-screen h-screen items-center justify-center">
<div>
{showCookieWarning ? (
{showCookieWarning || oidcErrorMessage ? (
<Card className="max-w-md m-4 sm:m-0 mb-4 sm:mb-4 border border-red-500">
<div className="flex items-center justify-between gap-4">
<Card.Title className="text-red-500">
Configuration Issue
Configuration Issue(s)
</Card.Title>
<AlertCircle className="w-6 h-6 mb-2 text-red-500" />
</div>
<Card.Text className="text-sm text-red-600 dark:text-red-400">
Headplane is configured to use secure cookies, but this site is
being served over an insecure connection and login will not work
correctly.{' '}
<Link
name="Headplane Common Issues"
to="https://headplane.net/configuration/common-issues#issue-logging-in-does-not-do-anything"
>
Learn more.
</Link>
</Card.Text>
<div className="flex flex-col gap-2">
{showCookieWarning ? (
<Card.Text className="text-sm text-red-600 dark:text-red-400">
Headplane is configured to use secure cookies, but this site
is being served over an insecure connection and login will not
work correctly.{' '}
<Link
name="Headplane Common Issues"
to="https://headplane.net/configuration/common-issues#issue-logging-in-does-not-do-anything"
>
Learn more.
</Link>
</Card.Text>
) : undefined}
{oidcErrorMessage ? (
<Card.Text className="text-sm text-red-600 dark:text-red-400">
{oidcErrorMessage}{' '}
<Link
name="Headplane OIDC Issues"
to="https://headplane.net/configuration/sso#help"
>
Learn more.
</Link>
</Card.Text>
) : undefined}
</div>
</Card>
) : undefined}
<Card className="max-w-md m-4 sm:m-0">
@@ -141,7 +159,11 @@ export default function Page({ loaderData, actionData }: Route.ComponentProps) {
</Form>
{isOidcEnabled ? (
<RemixLink to="/oidc/start">
<Button className="w-full mt-2" variant="light">
<Button
className="w-full mt-2"
isDisabled={oidcErrorMessage !== undefined}
variant="light"
>
Single Sign-On
</Button>
</RemixLink>
+2 -2
View File
@@ -20,7 +20,7 @@ export async function loader({
request,
context,
}: LoaderFunctionArgs<LoadContext>) {
if (!context.oidc) {
if (!context.oidc || typeof context.oidc === 'string') {
throw new Error('OIDC is not enabled');
}
@@ -92,7 +92,7 @@ export async function loader({
// keys because they are currently non-deletable in the headscale
// database. Look at this in the future once we have a solution
// or we have permissioned API keys.
api_key: context.config.oidc?.headscale_api_key!,
api_key: context.config.oidc!.headscale_api_key,
user,
}),
},
+5 -1
View File
@@ -11,7 +11,11 @@ export async function loader({
return redirect('/machines');
} catch {}
if (!context.oidc || !context.config.oidc) {
if (
!context.oidc ||
typeof context.oidc === 'string' ||
!context.config.oidc
) {
throw new Error('OIDC is not enabled');
}
+5 -1
View File
@@ -10,7 +10,11 @@ import { LoadContext } from '~/server';
export async function loader({ context }: LoaderFunctionArgs<LoadContext>) {
return {
config: context.hs.writable(),
oidc: context.oidc,
oidc: context.oidc
? typeof context.oidc === 'string'
? undefined
: context.oidc
: undefined,
};
}