delete /api/vpn
it had no caller. verified three ways before removing: nothing in the mobile monorepo reaches it (enrollVpn's only call site is behind `if (embedded)`, and the one app rendering VpnScreen never passes embedded), nothing in the officer web app references it, and the live database holds no vpn grants. the companion was checked separately by its own author — zero references there either. and it will not come back. offscale is permanently standalone: the thing that gets you to the platform cannot itself need the platform, or a broken tailnet locks you out of both. gone: api/vpn/router.ts, its mount, and the `vpn` capability. the registry keeps a comment where the capability was, because its removal has a cost worth recording — headscale is admin-only, so no member-grantable headscale surface remains, and reintroducing one is a deliberate act rather than an oversight. kept: the sidecar's enroll.ts. its bare POST /_officer/enroll handler is now unreachable, but the file is also the dispatcher for /enroll/invites, which is live and fundamental. the header comment now says so, so nobody deletes it looking for dead code. also records the third component in the doc. two of the three have an "enroll" surface and only one is ours: /api/v1/enroll/* belongs to the companion, is where the phone actually goes, and must not be collapsed into /api/offscale/*. capabilities tests: 17 pass / 8 fail both before and after, stash-verified — the 8 are pre-existing, in totality and path-to-capability, which is precisely the machinery dynamic mounting will rework. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
import { createRouter } from '../../create-router';
|
||||
import { getHeadscaleServerUrl } from '../headscale/router';
|
||||
|
||||
// Enrollment for OffTail, the in-app Tailscale. Authenticate the owner, forward to officer-headscale, and
|
||||
// hold no Headscale knowledge whatsoever — no URL, no admin key, no user name.
|
||||
//
|
||||
// This file used to mint the pre-auth key itself, from HEADSCALE_URL / HEADSCALE_API_KEY / HEADSCALE_USER
|
||||
// read out of the host env. Three globals describe exactly one server; Officer keeps a registry of many in
|
||||
// `headscale_servers`, one active at a time, so the env could contradict the server the owner had selected.
|
||||
// The two credential vars were later removed and the failure was silent — `if (!base || !apiKey)` returned
|
||||
// 503 before the rest of the route ever ran, so enrollment had simply stopped working and said nothing.
|
||||
// The logic now lives in the sidecar that owns the registry (src/servers/sidecar/headscale/enroll.ts).
|
||||
//
|
||||
// It stays mounted at /api/vpn rather than moving under /api/headscale because the path is a contract:
|
||||
// enrollVpn() in the mobile core POSTs exactly /api/vpn/enroll. createSidecarProxy strips its own prefix
|
||||
// and cannot express that rewrite, so this one forward is spelled out by hand.
|
||||
|
||||
export const vpnRouter = createRouter();
|
||||
|
||||
// POST /api/vpn/enroll → { controlUrl, authKey }
|
||||
//
|
||||
// The response shape is the other half of the contract: enrollVpn() in @officer/core destructures exactly
|
||||
// those two fields, so changing them means changing the mobile app too.
|
||||
vpnRouter.post('/enroll', async (ctx) => {
|
||||
const baseUrl = getHeadscaleServerUrl();
|
||||
if (!baseUrl) return ctx.json({ error: 'headscale sidecar not available' }, 503);
|
||||
|
||||
// Forwarded verbatim: an optional {userId} picks the owning Headscale user when the server has several.
|
||||
const body = await ctx.req.arrayBuffer();
|
||||
|
||||
let upstream: Response;
|
||||
try {
|
||||
upstream = await fetch(`${baseUrl}/_officer/enroll`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': ctx.req.header('content-type') ?? 'application/json',
|
||||
// The authenticated owner. The sidecar binds loopback only, so its presence is the trust signal.
|
||||
'X-Officer-User': String(ctx.get('user').id),
|
||||
},
|
||||
body: body.byteLength ? body : undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[vpn] headscale sidecar unreachable:', err);
|
||||
return ctx.json({ error: 'headscale sidecar unreachable' }, 502);
|
||||
}
|
||||
|
||||
return new Response(upstream.body, { status: upstream.status, headers: new Headers(upstream.headers) });
|
||||
});
|
||||
Reference in New Issue
Block a user