From 0a51182eed78c5b76ea3557ff90f08926a5a651e Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 30 May 2026 18:49:11 -0400 Subject: [PATCH] fix(pre-auth-keys): pass Headscale numeric user id when expiring on 0.27.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-0.28 ExpirePreAuthKey RPC takes a uint64 `user` field plus the key string. The API layer reads that uint64 from `key.user?.id`, but the action was wrapping the form's user_id as `{ name: user }` — so .id was always undefined and the wire request sent an empty string, which Headscale rejects with "proto: invalid value for uint64 field user". 0.28+ is unaffected because the new expire endpoint only reads `key.id` (the stable preauthkey id). Pass `{ id: user }` so the numeric Headscale user id reaches the wire. Amp-Thread-ID: https://ampcode.com/threads/T-019e7ae4-6862-760c-a3e7-239350eab71d Co-authored-by: Amp --- CHANGELOG.md | 1 + app/routes/settings/auth-keys/actions.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 647b662..a752a64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Fixed focus rings on inputs and buttons inside dialogs being clipped by the scrollable content container. - Fixed tooltips on the last row of the machines table being clipped by the viewport; tooltips now anchor above the trigger with collision padding (closes [#508](https://github.com/tale/headplane/issues/508)). - Fixed the "Register Machine Key" dialog passing the Headscale numeric user id instead of the username. Headscale's `RegisterNodeRequest.user` proto field is a `string` that is looked up via `GetUserByName` (no numeric fallback), so registration was failing whenever the selected owner's display name differed from their numeric id (closes [#532](https://github.com/tale/headplane/issues/532)). +- Fixed pre-auth key expiration on Headscale 0.27.x. The pre-0.28 expire endpoint takes a `uint64 user` field which the API layer reads from `key.user?.id`, but the caller was wrapping the id as `{ name: user }`, causing the request to send an empty user field. Headplane now correctly passes the numeric Headscale user id. - Corrected the Docker healthcheck example in the docs to use the required `CMD` prefix so reverse proxies don't see the container as unhealthy (closes [#535](https://github.com/tale/headplane/issues/535)). --- diff --git a/app/routes/settings/auth-keys/actions.ts b/app/routes/settings/auth-keys/actions.ts index 85f5d8a..24b7d97 100644 --- a/app/routes/settings/auth-keys/actions.ts +++ b/app/routes/settings/auth-keys/actions.ts @@ -112,10 +112,14 @@ export async function authKeysAction({ request, context }: Route.ActionArgs) { } await checkSelfServiceOwnership(user); + // `user` here is the Headscale numeric user id (form field is wired + // from User.id). Pre-0.28 expire posts a uint64 `user` field, which + // the API layer reads from `key.user?.id`. Headscale 0.28+ only + // looks at `key.id` (the stable preauthkey id). await api.preAuthKeys.expire({ id: keyId, key, - user: { name: user }, + user: { id: user }, } as unknown as PreAuthKey); return data("Pre-auth key expired"); }