From 948cfad58cbdd799209d78b72a554d2b343c015d Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 4 Jul 2026 00:07:53 -0400 Subject: [PATCH] fix: handle registration keys on headscale 0.29+ Closes HP-555. --- CHANGELOG.md | 1 + app/routes/machines/dialogs/new.tsx | 17 ++++-- app/routes/machines/machine-actions.ts | 12 ++++- app/server/headscale/api/capabilities.ts | 9 ++++ app/server/headscale/api/resources/nodes.ts | 9 +++- app/utils/register-key.ts | 46 +++++++++++++++++ tests/integration/api/nodes.test.ts | 54 +++++++++++++++++-- tests/integration/setup/env.ts | 1 + tests/integration/setup/start-tailscale.ts | 8 +-- tests/unit/headscale/server-version.test.ts | 7 +++ tests/unit/utils/register-key.test.ts | 57 +++++++++++++++++++++ 11 files changed, 206 insertions(+), 15 deletions(-) create mode 100644 app/utils/register-key.ts create mode 100644 tests/unit/utils/register-key.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 86c4837..978ad14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Added automatic role assignment for new OIDC users via `oidc.default_role` and IdP-provided role claims via `oidc.role_claim` (closes [#352](https://github.com/tale/headplane/issues/352)). - Fixed the DNS page crashing when Headscale has no Split DNS nameservers configured (closes [#570](https://github.com/tale/headplane/issues/570)). - User lists now show Headscale display names while preserving usernames as secondary text (closes [#571](https://github.com/tale/headplane/issues/571)). +- Fixed the Register Machine Key dialog so it accepts registration URLs and full `hskey-authreq-...` registration keys (closes [#579](https://github.com/tale/headplane/issues/579)). --- diff --git a/app/routes/machines/dialogs/new.tsx b/app/routes/machines/dialogs/new.tsx index 523b723..41f4dc2 100644 --- a/app/routes/machines/dialogs/new.tsx +++ b/app/routes/machines/dialogs/new.tsx @@ -12,10 +12,11 @@ import Text from "~/components/text"; import Title from "~/components/title"; import { useForm } from "~/hooks/use-form"; import type { User } from "~/types"; +import { normalizeRegistrationKey } from "~/utils/register-key"; import { getUserDisplayName } from "~/utils/user"; const registerSchema = type({ - register_key: "string == 24", + register_key: "string > 0", user: "string > 0", }); @@ -28,7 +29,16 @@ export interface NewMachineProps { export default function NewMachine(data: NewMachineProps) { const [pushDialog, setPushDialog] = useState(false); - const form = useForm({ schema: registerSchema }); + const form = useForm({ + schema: registerSchema, + validate: (values) => + normalizeRegistrationKey(String(values.register_key ?? "")) + ? undefined + : { + register_key: + "Paste the registration URL or full hskey-authreq-... key from tailscale up.", + }, + }); const navigate = useNavigate(); return ( @@ -43,7 +53,8 @@ export default function NewMachine(data: NewMachineProps) { {...form.field("register_key")} required label="Machine Key" - placeholder="AbCd..." + placeholder="hskey-authreq-XXXXXXXXXXXXXXXXXXXXXXXX" + description="Paste the registration URL or full key shown by tailscale up." />