Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/commands/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { CommandFlags } from "../core/command_dispatch.js";
import { saveConfig } from "../core/config.js";
import { VERSION } from "../version.js";
import { renderHealthReport } from "../core/health.js";
import { isCredentialSafeUrl } from "../core/transport.js";
import { ApiClient, isCredentialSafeUrl } from "../core/transport.js";
import { DEVICE_ENROLL_PATH } from "../core/device_runtime/contract.js";
import { deviceRuntimeEnabled } from "../core/device_runtime/enablement.js";
import { loadEnrollment, saveEnrollment, type EnrollmentRecord } from "../core/device_runtime/identity.js";
Expand Down Expand Up @@ -200,7 +200,11 @@ async function enroll(ctx: AppContext, flags: CommandFlags): Promise<number> {
}
let resp: EnrollResponse;
try {
resp = await ctx.api.postJson<EnrollResponse>(DEVICE_ENROLL_PATH, {
// The saved device bearer must point to the same Cloud that minted it.
// An explicit --base-url changes both the enrollment request and record.
const api = resolved.url.replace(/\/+$/, "") === ctx.cfg.baseUrl.replace(/\/+$/, "")
? ctx.api : new ApiClient(resolved.url, ctx.tokens);
resp = await api.postJson<EnrollResponse>(DEVICE_ENROLL_PATH, {
client_label: hostname(),
allowed_projects: [],
});
Expand Down
34 changes: 28 additions & 6 deletions test/device_command.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { createServer } from "node:http";
import { hostname, tmpdir } from "node:os";
import { join } from "node:path";
import { cmdDevice, DEVICE_EXIT, deviceHealthState, resolveEnrollBaseUrl } from "../src/commands/device.js";
Expand Down Expand Up @@ -180,17 +181,38 @@ test("resolveEnrollBaseUrl REFUSES a URL that would put the device bearer in cle

test("device enroll --base-url persists the override into the enrollment record", async () => {
await withConfigDir(async () => {
let path: string | undefined;
let authorization: string | undefined;
const server = createServer(async (request, response) => {
path = request.url;
authorization = request.headers.authorization;
for await (const _chunk of request) { /* Drain the request. */ }
response.writeHead(201, { "content-type": "application/json" });
response.end(JSON.stringify({
device_id: "dev-flag", device_token: "t", command_key_hex: "ab".repeat(32),
}));
});
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
assert.ok(address && typeof address !== "string");
const override = `http://127.0.0.1:${address.port}/cloud`;
const ctx = fakeCtx({
cfg: { ...DEFAULT_CONFIG, baseUrl: "https://config.example.test" },
api: {
postJson: async () => ({ device_id: "dev-flag", device_token: "t", command_key_hex: "ab".repeat(32) }),
postJson: async () => { throw new Error("enrollment used configured Cloud instead of override"); },
} as unknown as AppContext["api"],
});
const { code } = await capture(() =>
cmdDevice(ctx, ["enroll"], flagsWith({ "base-url": "https://laptop-cloud.example.test/cloud" })),
);
assert.equal(code, DEVICE_EXIT.ok);
assert.equal(loadEnrollment()?.base_url, "https://laptop-cloud.example.test/cloud");
try {
const { code } = await capture(() =>
cmdDevice(ctx, ["enroll"], flagsWith({ "base-url": override })),
);
assert.equal(code, DEVICE_EXIT.ok);
assert.equal(loadEnrollment()?.base_url, override);
assert.equal(path, "/cloud/device/v1/enroll");
assert.equal(authorization, "Bearer sess-token");
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
});

Expand Down
Loading