import { useMemo } from 'react'; import { type ActionFunctionArgs, type LoaderFunctionArgs, redirect, } from 'react-router'; import { Form, useActionData, useLoaderData } from 'react-router'; import Button from '~/components/Button'; import Card from '~/components/Card'; import Code from '~/components/Code'; import Input from '~/components/Input'; import type { Key } from '~/types'; import { loadContext } from '~/utils/config/headplane'; import { pull } from '~/utils/headscale'; import { commitSession, getSession } from '~/utils/sessions.server'; export async function loader({ request }: LoaderFunctionArgs) { const session = await getSession(request.headers.get('Cookie')); if (session.has('hsApiKey')) { return redirect('/machines', { headers: { 'Set-Cookie': await commitSession(session), }, }); } const context = await loadContext(); // Only set if OIDC is properly enabled anyways if (context.oidc?.disableKeyLogin) { return redirect('/oidc/start'); } return { oidc: context.oidc?.issuer, apiKey: !context.oidc?.disableKeyLogin, }; } export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const oidcStart = formData.get('oidc-start'); const session = await getSession(request.headers.get('Cookie')); if (oidcStart) { const context = await loadContext(); if (!context.oidc) { throw new Error('An invalid OIDC configuration was provided'); } return redirect('/oidc/start'); } const apiKey = String(formData.get('api-key')); // Test the API key try { const apiKeys = await pull<{ apiKeys: Key[] }>('v1/apikey', apiKey); const key = apiKeys.apiKeys.find((k) => apiKey.startsWith(k.prefix)); if (!key) { throw new Error('Invalid API key'); } const expiry = new Date(key.expiration); const expiresIn = expiry.getTime() - Date.now(); const expiresDays = Math.round(expiresIn / 1000 / 60 / 60 / 24); session.set('hsApiKey', apiKey); session.set('user', { subject: 'unknown-non-oauth', name: key.prefix, email: `${expiresDays.toString()} days`, }); return redirect('/machines', { headers: { 'Set-Cookie': await commitSession(session, { maxAge: expiresIn, }), }, }); } catch { return { error: 'Invalid API key', }; } } export default function Page() { const data = useLoaderData(); const actionData = useActionData(); const showOr = useMemo(() => data.oidc && data.apiKey, [data]); return (
Welcome to Headplane {data.apiKey ? (
Enter an API key to authenticate with Headplane. You can generate one by running headscale apikeys create in your terminal. {actionData?.error ? (

{actionData.error}

) : undefined}
) : undefined} {showOr ? (

or
) : undefined} {data.oidc ? (
) : undefined}
); }