headscale: device invites — admin surface for offscale enrollment

This commit is contained in:
2026-08-05 18:10:44 +00:00
parent 55abaa4042
commit 4cee0335c2
9 changed files with 673 additions and 8 deletions
+6
View File
@@ -4,6 +4,7 @@ import { getActiveHeadscaleCredentials } from 'officerdb';
import { badRequest, methodNotAllowed, readJson } from './routes';
import { createClient, type HeadscaleClient } from './client';
import { arrayField, toUser } from './normalize';
import { handleInvitesRoute } from './invites';
// Device enrolment — POST /_officer/enroll. The mobile app's one-tap join: it turns an authenticated
// Officer session into a short-lived, single-use pre-auth key, so nobody pastes a key by hand.
@@ -64,6 +65,11 @@ async function resolveOwner(client: HeadscaleClient, ctx: OfficerContext): Promi
}
export async function handleEnrollRoute(ctx: OfficerContext, segments: string[]): Promise<Response | null> {
// `/enroll/invites…` is the admin invite surface — a different flow entirely (see invites.ts): the device
// is not here and there is no Officer session on it. Same prefix because it is the same feature to the
// person using it, and because the spec names it that way.
if (segments[0] === 'invites') return handleInvitesRoute(ctx, segments.slice(1));
if (segments.length > 0) return null;
if (ctx.req.method !== 'POST') return methodNotAllowed();