1168 lines
48 KiB
JavaScript
1168 lines
48 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
import test from "node:test";
|
|
import {
|
|
RELAY_AUTHORIZATION_MAX_TTL_SECONDS,
|
|
RELAY_SIGNING_KEY_FOR_SNAPSHOT_SQL,
|
|
classifySnapshotPlacement,
|
|
canonicalJson,
|
|
signRelayAuthorizationSnapshot,
|
|
verifyRelayAuthorizationSnapshot,
|
|
} from "../db/relay-authorization.ts";
|
|
import {
|
|
ACTIVATE_PHONE_PRINCIPAL_SQL,
|
|
ACTIVATE_PHONE_ROUTE_SQL,
|
|
ADVANCE_AUTHORIZATION_AFTER_CLAIM_SQL,
|
|
ADVANCE_AUTHORIZATION_AFTER_PHONE_ACTIVATION_SQL,
|
|
ADVANCE_AUTHORIZATION_AFTER_PHONE_REVOKE_SQL,
|
|
ADVANCE_AUTHORIZATION_AFTER_REVOKE_SQL,
|
|
AUTHORIZE_PHONE_ROUTE_SQL,
|
|
CLAIM_PHONE_HANDOFF_ACTIVATION_SQL,
|
|
CONSUME_PHONE_HANDOFF_SQL,
|
|
DELETE_SUPERSEDED_PENDING_PHONE_PRINCIPALS_SQL,
|
|
DELETE_SUPERSEDED_PENDING_PHONE_ROUTES_SQL,
|
|
FINALIZE_PHONE_HANDOFF_ACTIVATION_SQL,
|
|
PHONE_FOR_NODE_REVOCATION_SQL,
|
|
REVOKE_PHONE_PRINCIPAL_SQL,
|
|
REVOKE_PHONE_ROUTES_SQL,
|
|
} from "../db/relay-control-sql.ts";
|
|
import {
|
|
AUTHENTICATE_RELAY_NODE_IDENTITY_SQL,
|
|
createTrustedRelayMtlsAssertion,
|
|
verifyTrustedRelayMtlsIdentity,
|
|
} from "../db/relay-node-identity.ts";
|
|
import {
|
|
decryptRegistrationReplay,
|
|
encryptRegistrationReplay,
|
|
} from "../db/registration-replay.ts";
|
|
import { REQUIRED_PUBLIC_BETA_P0_KEYS } from "../db/launch-gates.ts";
|
|
import { CONSUME_CLAIM_RATE_SQL, RESERVE_PAIRING_SQL } from "../db/pairing.ts";
|
|
import { isWritableRelayPlacement, resolveRelayPlacementRoute } from "../db/relay-routing.ts";
|
|
import { authorityAfterMigrationFailure } from "../db/relay-migration-state.ts";
|
|
import {
|
|
COMPLETED_RELAY_PURGE_PROOF_SQL,
|
|
relayPurgeCompletionAuditId,
|
|
relayPurgeRecordCanReturn,
|
|
} from "../db/relay-purge-proof.ts";
|
|
|
|
function migrationStatements(source) {
|
|
return source
|
|
.split("--> statement-breakpoint")
|
|
.map((statement) => statement.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
test("resolves stable ingress to exactly one active local or remote Relay", () => {
|
|
assert.match(AUTHORIZE_PHONE_ROUTE_SQL, /placements\.state IN \('active', 'draining'\)/);
|
|
assert.equal(isWritableRelayPlacement("active", "active"), true);
|
|
assert.equal(isWritableRelayPlacement("draining", "active"), true);
|
|
assert.equal(isWritableRelayPlacement("copying", "active"), false);
|
|
assert.equal(isWritableRelayPlacement("draining", "offline"), false);
|
|
const placement = {
|
|
tenant_id: `tenant_${"a".repeat(32)}`,
|
|
tenant_status: "active",
|
|
home_region: "cn-east",
|
|
relay_node_id: "node_east_1",
|
|
generation: 4,
|
|
placement_state: "active",
|
|
authorization_revision: 9,
|
|
internal_endpoint_ref: "relay-east-1",
|
|
relay_node_status: "active",
|
|
};
|
|
assert.deepEqual(resolveRelayPlacementRoute(placement, "node_east_1"), {
|
|
relay_node_id: "node_east_1",
|
|
placement_generation: 4,
|
|
home_region: "cn-east",
|
|
local: true,
|
|
});
|
|
assert.deepEqual(resolveRelayPlacementRoute(placement, "node_west_1"), {
|
|
relay_node_id: "node_east_1",
|
|
placement_generation: 4,
|
|
home_region: "cn-east",
|
|
local: false,
|
|
endpoint_ref: "relay-east-1",
|
|
});
|
|
assert.deepEqual(
|
|
resolveRelayPlacementRoute({ ...placement, placement_state: "draining" }, "node_west_1"),
|
|
{
|
|
relay_node_id: "node_east_1",
|
|
placement_generation: 4,
|
|
home_region: "cn-east",
|
|
local: false,
|
|
endpoint_ref: "relay-east-1",
|
|
},
|
|
);
|
|
assert.throws(
|
|
() => resolveRelayPlacementRoute({ ...placement, placement_state: "switching" }, "node_west_1"),
|
|
/租户 Relay 正在准备/,
|
|
);
|
|
assert.throws(
|
|
() => resolveRelayPlacementRoute({ ...placement, relay_node_status: "offline" }, "node_west_1"),
|
|
/目标 Relay 节点当前不可用/,
|
|
);
|
|
assert.throws(
|
|
() => resolveRelayPlacementRoute({ ...placement, internal_endpoint_ref: "https:\/\/evil.example" }, "node_west_1"),
|
|
/目标 Relay 内部路由不可用/,
|
|
);
|
|
});
|
|
|
|
test("rolls back only before switch and never makes a stale source authoritative afterward", () => {
|
|
const fence = {
|
|
state: "copying",
|
|
source_node_id: "node_source",
|
|
target_node_id: "node_target",
|
|
source_generation: 4,
|
|
target_generation: 5,
|
|
};
|
|
assert.deepEqual(authorityAfterMigrationFailure(fence), {
|
|
nodeId: "node_source",
|
|
generation: 4,
|
|
});
|
|
assert.deepEqual(authorityAfterMigrationFailure({ ...fence, state: "draining" }), {
|
|
nodeId: "node_target",
|
|
generation: 5,
|
|
});
|
|
});
|
|
|
|
test("canonicalizes and signs a bounded Ed25519 authorization snapshot", async () => {
|
|
assert.equal(
|
|
canonicalJson({ z: 1, a: [true, { y: null, x: "猫" }] }),
|
|
'{"a":[true,{"x":"猫","y":null}],"z":1}',
|
|
);
|
|
const keys = await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
|
|
const issuedAt = "2026-08-12T12:00:00.000Z";
|
|
const payload = {
|
|
snapshot_version: 1,
|
|
tenant_id: `tenant_${"a".repeat(32)}`,
|
|
tenant_status: "active",
|
|
home_region: "cn-east",
|
|
relay_node_id: "node_cn-east-1",
|
|
placement_generation: 3,
|
|
authorization_revision: 7,
|
|
devices: [
|
|
{
|
|
device_id: `host_${"b".repeat(32)}`,
|
|
name: "Workstation",
|
|
os: "windows",
|
|
ed25519_public: "e".repeat(43),
|
|
x25519_public: "x".repeat(43),
|
|
credential_hash: "c".repeat(64),
|
|
identity_fingerprint: "d".repeat(64),
|
|
},
|
|
],
|
|
issued_at: issuedAt,
|
|
expires_at: new Date(
|
|
Date.parse(issuedAt) + RELAY_AUTHORIZATION_MAX_TTL_SECONDS * 1_000,
|
|
).toISOString(),
|
|
};
|
|
const snapshot = await signRelayAuthorizationSnapshot({
|
|
kid: "relay-signing-2026-08",
|
|
privateKey: keys.privateKey,
|
|
payload,
|
|
});
|
|
assert.equal(
|
|
await verifyRelayAuthorizationSnapshot({
|
|
snapshot,
|
|
publicKey: keys.publicKey,
|
|
nowMs: Date.parse(issuedAt) + 60_000,
|
|
}),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
await verifyRelayAuthorizationSnapshot({
|
|
snapshot: {
|
|
...snapshot,
|
|
payload: { ...snapshot.payload, authorization_revision: 8 },
|
|
},
|
|
publicKey: keys.publicKey,
|
|
nowMs: Date.parse(issuedAt) + 60_000,
|
|
}),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
await verifyRelayAuthorizationSnapshot({
|
|
snapshot: {
|
|
...snapshot,
|
|
payload: {
|
|
...snapshot.payload,
|
|
devices: [{ ...snapshot.payload.devices[0], x25519_public: "y".repeat(43) }],
|
|
},
|
|
},
|
|
publicKey: keys.publicKey,
|
|
nowMs: Date.parse(issuedAt) + 60_000,
|
|
}),
|
|
false,
|
|
);
|
|
await assert.rejects(
|
|
signRelayAuthorizationSnapshot({
|
|
kid: "relay-signing-2026-08",
|
|
privateKey: keys.privateKey,
|
|
payload: {
|
|
...payload,
|
|
devices: [{ ...payload.devices[0], ed25519_public: "invalid" }],
|
|
},
|
|
}),
|
|
/invalid_snapshot_device_public_key/,
|
|
);
|
|
assert.equal(
|
|
await verifyRelayAuthorizationSnapshot({
|
|
snapshot: {
|
|
...snapshot,
|
|
payload: {
|
|
...snapshot.payload,
|
|
devices: [{ ...snapshot.payload.devices[0], name: "Other" }],
|
|
},
|
|
},
|
|
publicKey: keys.publicKey,
|
|
nowMs: Date.parse(issuedAt) + 60_000,
|
|
}),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
await verifyRelayAuthorizationSnapshot({
|
|
snapshot,
|
|
publicKey: keys.publicKey,
|
|
nowMs: Date.parse(payload.expires_at),
|
|
}),
|
|
false,
|
|
);
|
|
await assert.rejects(
|
|
signRelayAuthorizationSnapshot({
|
|
kid: "relay-signing-2026-08",
|
|
privateKey: keys.privateKey,
|
|
payload: {
|
|
...payload,
|
|
expires_at: new Date(
|
|
Date.parse(issuedAt) + (RELAY_AUTHORIZATION_MAX_TTL_SECONDS + 1) * 1_000,
|
|
).toISOString(),
|
|
},
|
|
}),
|
|
/snapshot_ttl_exceeds_maximum/,
|
|
);
|
|
});
|
|
|
|
test("selects only a signing key valid through the full snapshot lifetime", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE relay_signing_keys (
|
|
kid TEXT PRIMARY KEY, public_key_jwk TEXT, private_key_ref TEXT,
|
|
status TEXT, not_before TEXT, not_after TEXT
|
|
);
|
|
INSERT INTO relay_signing_keys VALUES
|
|
('old-too-short', '{}', 'env:key#old', 'active',
|
|
'2026-08-12T11:00:00.000Z', '2026-08-12T12:04:59.999Z'),
|
|
('overlap-old', '{}', 'env:key#overlap', 'active',
|
|
'2026-08-12T11:30:00.000Z', '2026-08-12T12:05:00.000Z'),
|
|
('new', '{}', 'env:key#new', 'active',
|
|
'2026-08-12T11:59:00.000Z', '2026-08-13T00:00:00.000Z');
|
|
`);
|
|
const select = db.prepare(RELAY_SIGNING_KEY_FOR_SNAPSHOT_SQL);
|
|
assert.equal(select.get(
|
|
"2026-08-12T12:00:00.000Z",
|
|
"2026-08-12T12:05:00.000Z",
|
|
).kid, "new");
|
|
db.prepare("UPDATE relay_signing_keys SET status = 'retired' WHERE kid = 'new'").run();
|
|
assert.equal(select.get(
|
|
"2026-08-12T12:00:00.000Z",
|
|
"2026-08-12T12:05:00.000Z",
|
|
).kid, "overlap-old");
|
|
db.prepare("UPDATE relay_signing_keys SET status = 'retired' WHERE kid = 'overlap-old'").run();
|
|
assert.equal(select.get(
|
|
"2026-08-12T12:00:00.000Z",
|
|
"2026-08-12T12:05:00.000Z",
|
|
), undefined);
|
|
db.close();
|
|
});
|
|
|
|
test("fails full snapshot refresh for wrong node, stale generation, suspended tenant, or provisioning", () => {
|
|
const placement = {
|
|
relay_node_id: "node_a",
|
|
generation: 3,
|
|
tenant_status: "active",
|
|
placement_state: "active",
|
|
};
|
|
assert.equal(classifySnapshotPlacement({ placement, nodeId: "node_a", expectedGeneration: 3 }), "ready");
|
|
assert.equal(classifySnapshotPlacement({
|
|
placement: { ...placement, placement_state: "draining" },
|
|
nodeId: "node_a",
|
|
expectedGeneration: 3,
|
|
}), "ready");
|
|
assert.equal(classifySnapshotPlacement({ placement, nodeId: "node_b", expectedGeneration: 3 }), "wrong_node");
|
|
assert.equal(classifySnapshotPlacement({ placement, nodeId: "node_a", expectedGeneration: 2 }), "stale_generation");
|
|
assert.equal(classifySnapshotPlacement({
|
|
placement: { ...placement, tenant_status: "suspended" },
|
|
nodeId: "node_a",
|
|
expectedGeneration: 3,
|
|
}), "suspended");
|
|
assert.equal(classifySnapshotPlacement({
|
|
placement: { ...placement, placement_state: "copying" },
|
|
nodeId: "node_a",
|
|
expectedGeneration: 3,
|
|
}), "provisioning");
|
|
});
|
|
|
|
test("installs shared-relay tables, backfills placement and stores no plaintext private keys", async () => {
|
|
const migration = await readFile(
|
|
new URL("../drizzle/0012_shared_relay_control_plane.sql", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE accounts (id TEXT PRIMARY KEY);
|
|
CREATE TABLE tenant_instances (
|
|
id TEXT PRIMARY KEY, account_id TEXT NOT NULL,
|
|
credential_revision INTEGER NOT NULL DEFAULT 0, tombstoned_at TEXT
|
|
);
|
|
INSERT INTO accounts VALUES ('acct_a');
|
|
INSERT INTO tenant_instances VALUES
|
|
('tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'acct_a', 4, NULL);
|
|
`);
|
|
for (const statement of migrationStatements(migration)) db.exec(statement);
|
|
assert.deepEqual(
|
|
{ ...db.prepare(
|
|
`SELECT home_region_id, generation, state
|
|
FROM tenant_placements WHERE tenant_id = ?`,
|
|
).get("tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") },
|
|
{ home_region_id: "region_default", generation: 1, state: "provisioning" },
|
|
);
|
|
assert.deepEqual(
|
|
{ ...db.prepare(
|
|
`SELECT revision, status
|
|
FROM tenant_authorization_state WHERE tenant_id = ?`,
|
|
).get("tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") },
|
|
{ revision: 4, status: "active" },
|
|
);
|
|
const signingColumns = db.prepare("PRAGMA table_info(relay_signing_keys)").all();
|
|
assert.equal(signingColumns.some((column) => column.name === "private_key"), false);
|
|
assert.equal(signingColumns.some((column) => column.name === "private_key_ref"), true);
|
|
const nodeIdentityColumns = db.prepare("PRAGMA table_info(relay_node_credentials)").all();
|
|
assert.equal(nodeIdentityColumns.some((column) => column.name === "token_hash"), false);
|
|
const allNewColumns = [
|
|
"relay_regions",
|
|
"relay_nodes",
|
|
"tenant_placements",
|
|
"tenant_authorization_state",
|
|
"relay_signing_keys",
|
|
"relay_node_credentials",
|
|
"phone_handoff_tickets",
|
|
"phone_route_handles",
|
|
"relay_phone_principals",
|
|
].flatMap((table) => db.prepare(`PRAGMA table_info(${table})`).all());
|
|
assert.equal(
|
|
allNewColumns.some((column) => /prompt|session|attachment|private_key$/u.test(column.name)),
|
|
false,
|
|
);
|
|
db.close();
|
|
});
|
|
|
|
test("fences one Relay migration through quiesce, copy, switch, drain, and active", async () => {
|
|
const migration = await readFile(
|
|
new URL("../drizzle/0013_relay_migration_fencing.sql", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
PRAGMA foreign_keys = ON;
|
|
CREATE TABLE tenant_instances (id TEXT PRIMARY KEY);
|
|
CREATE TABLE relay_nodes (id TEXT PRIMARY KEY);
|
|
CREATE TABLE tenant_placements (
|
|
tenant_id TEXT PRIMARY KEY, relay_node_id TEXT, generation INTEGER,
|
|
state TEXT, last_error_code TEXT, updated_at TEXT
|
|
);
|
|
INSERT INTO tenant_instances VALUES ('tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
|
|
INSERT INTO relay_nodes VALUES ('node_source'), ('node_target');
|
|
INSERT INTO tenant_placements VALUES
|
|
('tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'node_source', 4, 'active', NULL,
|
|
'2026-08-12T12:00:00.000Z');
|
|
`);
|
|
for (const statement of migrationStatements(migration)) db.exec(statement);
|
|
const insert = db.prepare(`
|
|
INSERT INTO relay_migrations
|
|
(id, tenant_id, source_node_id, target_node_id, source_generation,
|
|
target_generation, state, requested_by, reason, started_at, updated_at)
|
|
VALUES (?, 'tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'node_source', 'node_target',
|
|
4, 5, 'quiescing', 'admin', 'capacity move', ?, ?)
|
|
`);
|
|
const now = "2026-08-12T12:00:01.000Z";
|
|
insert.run(`migration_${"a".repeat(32)}`, now, now);
|
|
assert.throws(
|
|
() => insert.run(`migration_${"b".repeat(32)}`, now, now),
|
|
/UNIQUE constraint failed/,
|
|
);
|
|
assert.throws(
|
|
() => db.prepare(`
|
|
INSERT INTO relay_migrations
|
|
(id, tenant_id, source_node_id, target_node_id, source_generation,
|
|
target_generation, state, requested_by, reason, started_at, updated_at)
|
|
VALUES (?, 'tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'node_source', 'node_target',
|
|
4, 9, 'failed', 'admin', 'invalid fence', ?, ?)
|
|
`).run(`migration_${"c".repeat(32)}`, now, now),
|
|
/CHECK constraint failed/,
|
|
);
|
|
const placement = () => ({ ...db.prepare(
|
|
"SELECT relay_node_id, generation, state FROM tenant_placements",
|
|
).get() });
|
|
db.prepare("UPDATE tenant_placements SET state = 'quiescing'").run();
|
|
assert.deepEqual(placement(), { relay_node_id: "node_source", generation: 4, state: "quiescing" });
|
|
db.prepare("UPDATE tenant_placements SET state = 'copying'").run();
|
|
db.prepare("UPDATE relay_migrations SET state = 'copying'").run();
|
|
assert.notEqual(placement().state, "active");
|
|
db.prepare("UPDATE tenant_placements SET state = 'switching'").run();
|
|
db.prepare("UPDATE relay_migrations SET state = 'switching'").run();
|
|
assert.notEqual(placement().state, "active");
|
|
db.prepare("UPDATE tenant_placements SET relay_node_id = 'node_target', generation = 5, state = 'draining'").run();
|
|
db.prepare("UPDATE relay_migrations SET state = 'draining'").run();
|
|
assert.deepEqual(placement(), { relay_node_id: "node_target", generation: 5, state: "draining" });
|
|
db.prepare("UPDATE tenant_placements SET state = 'active'").run();
|
|
db.prepare("UPDATE relay_migrations SET state = 'completed'").run();
|
|
assert.deepEqual(placement(), { relay_node_id: "node_target", generation: 5, state: "active" });
|
|
db.close();
|
|
});
|
|
|
|
test("fences one permanent Relay purge per tenant and covers live credentials and backups", async () => {
|
|
const [migration, control, relayPrimitive] = await Promise.all([
|
|
readFile(new URL("../drizzle/0014_relay_tenant_purge.sql", import.meta.url), "utf8"),
|
|
readFile(new URL("../db/relay-purges.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../relay/internal/tenantpurge/purge.go", import.meta.url), "utf8"),
|
|
]);
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
PRAGMA foreign_keys = ON;
|
|
CREATE TABLE account_deletion_requests (id TEXT PRIMARY KEY);
|
|
CREATE TABLE tenant_instances (id TEXT PRIMARY KEY);
|
|
CREATE TABLE relay_nodes (id TEXT PRIMARY KEY);
|
|
INSERT INTO account_deletion_requests VALUES ('deletion_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
|
|
INSERT INTO tenant_instances VALUES ('tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
|
|
INSERT INTO relay_nodes VALUES ('node_a');
|
|
`);
|
|
for (const statement of migrationStatements(migration)) db.exec(statement);
|
|
const insert = db.prepare(`
|
|
INSERT INTO relay_purge_jobs
|
|
(id, deletion_request_id, tenant_id, relay_node_id, placement_generation,
|
|
state, requested_by, reason, started_at, updated_at)
|
|
VALUES (?, 'deletion_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
'tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'node_a', ?,
|
|
'quiescing', 'admin', 'confirmed deletion', ?, ?)
|
|
`);
|
|
const now = "2026-08-12T12:00:00.000Z";
|
|
insert.run(`purge_${"a".repeat(32)}`, 4, now, now);
|
|
assert.throws(
|
|
() => insert.run(`purge_${"b".repeat(32)}`, 4, now, now),
|
|
/UNIQUE constraint failed/,
|
|
);
|
|
assert.throws(
|
|
() => db.prepare(`
|
|
INSERT INTO relay_purge_jobs
|
|
(id, deletion_request_id, tenant_id, relay_node_id, placement_generation,
|
|
state, requested_by, reason, started_at, updated_at)
|
|
VALUES (?, 'deletion_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
'tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'node_a', 0,
|
|
'quiescing', 'admin', 'invalid', ?, ?)
|
|
`).run(`purge_${"c".repeat(32)}`, now, now),
|
|
/CHECK constraint failed|UNIQUE constraint failed/,
|
|
);
|
|
assert.match(control, /DELETE FROM device_registration_replays/);
|
|
assert.match(control, /DELETE FROM device_credentials/);
|
|
assert.match(control, /DELETE FROM phone_route_handles/);
|
|
assert.match(control, /DELETE FROM relay_phone_principals/);
|
|
assert.match(control, /DELETE FROM phone_handoff_tickets/);
|
|
assert.match(control, /generation = CASE WHEN state = 'deleting' THEN generation \+ 1/);
|
|
assert.match(control, /SET lifecycle = 'deleting', desired_state = 'deleted'/);
|
|
assert.match(control, /status = 'deleting'/);
|
|
const completionAudit = control.lastIndexOf("INSERT OR IGNORE INTO audit_events");
|
|
const completionFence = control.lastIndexOf("UPDATE relay_purge_jobs\n SET state = 'completed'");
|
|
assert.ok(completionAudit > control.indexOf("SET status = 'deleted'"));
|
|
assert.ok(completionFence > completionAudit);
|
|
assert.match(control.slice(completionAudit, completionFence), /NOT EXISTS \(SELECT 1 FROM phone_route_handles/);
|
|
assert.match(control.slice(completionAudit, completionFence), /generation = \?7 \+ 1 AND state = 'deleted'/);
|
|
assert.match(control.slice(completionFence), /json_extract\(after_json, '\$\.evidence_sha256'\) = \?1/);
|
|
assert.doesNotMatch(control.slice(completionFence), /db\.prepare\(/);
|
|
assert.match(control, /return validatePurgeRecordForReturn\(record\)/);
|
|
assert.match(control, /existing\.state !== "failed"\) return validatePurgeRecordForReturn\(existing\)/);
|
|
assert.match(control, /if \(raced\) return validatePurgeRecordForReturn\(raced\)/);
|
|
assert.match(control, /const current = await reloadPurge\(purgeId\)/);
|
|
assert.match(control, /current\.state === "completed"\) return validatePurgeRecordForReturn\(current\)/);
|
|
assert.doesNotMatch(control, /state === "completed"[^\n]*return (?:record|existing|raced|current)/);
|
|
assert.match(relayPrimitive, /delete tenant Relay backups/);
|
|
assert.match(relayPrimitive, /symbolic link/);
|
|
db.close();
|
|
});
|
|
|
|
test("accepts an idempotent completed purge only with its full deterministic proof", () => {
|
|
assert.equal(relayPurgeRecordCanReturn("quiescing", false), true);
|
|
assert.equal(relayPurgeRecordCanReturn("failed", false), true);
|
|
assert.equal(relayPurgeRecordCanReturn("completed", false), false);
|
|
assert.equal(relayPurgeRecordCanReturn("completed", true), true);
|
|
const db = new DatabaseSync(":memory:");
|
|
const purgeId = `purge_${"a".repeat(32)}`;
|
|
const evidence = "e".repeat(64);
|
|
const auditId = relayPurgeCompletionAuditId(purgeId);
|
|
db.exec(`
|
|
CREATE TABLE relay_purge_jobs (
|
|
id TEXT PRIMARY KEY, deletion_request_id TEXT NOT NULL, tenant_id TEXT NOT NULL,
|
|
relay_node_id TEXT NOT NULL, placement_generation INTEGER NOT NULL,
|
|
state TEXT NOT NULL, evidence_sha256 TEXT
|
|
);
|
|
CREATE TABLE audit_events (
|
|
id TEXT PRIMARY KEY, actor_id TEXT NOT NULL, action TEXT NOT NULL,
|
|
target_type TEXT NOT NULL, target_id TEXT NOT NULL, after_json TEXT
|
|
);
|
|
CREATE TABLE tenant_authorization_state (tenant_id TEXT PRIMARY KEY, status TEXT NOT NULL);
|
|
CREATE TABLE tenant_placements (
|
|
tenant_id TEXT PRIMARY KEY, relay_node_id TEXT, generation INTEGER NOT NULL, state TEXT NOT NULL
|
|
);
|
|
CREATE TABLE tenant_instances (
|
|
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, lifecycle TEXT NOT NULL,
|
|
desired_state TEXT NOT NULL, observed_state TEXT NOT NULL
|
|
);
|
|
CREATE TABLE account_deletion_requests (
|
|
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, status TEXT NOT NULL
|
|
);
|
|
CREATE TABLE accounts (id TEXT PRIMARY KEY, status TEXT NOT NULL);
|
|
CREATE TABLE hosts (
|
|
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, lifecycle TEXT, slot_state TEXT
|
|
);
|
|
CREATE TABLE device_credentials (id TEXT PRIMARY KEY, host_id TEXT NOT NULL);
|
|
CREATE TABLE phone_route_handles (id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL);
|
|
CREATE TABLE relay_phone_principals (phone_id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL);
|
|
CREATE TABLE phone_handoff_tickets (id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL);
|
|
INSERT INTO relay_purge_jobs VALUES (
|
|
'${purgeId}', 'deletion_a', 'tenant_a', 'node_a', 4, 'completed', '${evidence}'
|
|
);
|
|
INSERT INTO audit_events VALUES (
|
|
'${auditId}', 'node_a', 'relay.purge.completed', 'relay_purge', '${purgeId}',
|
|
json_object('evidence_sha256', '${evidence}')
|
|
);
|
|
INSERT INTO tenant_authorization_state VALUES ('tenant_a', 'deleted');
|
|
INSERT INTO tenant_placements VALUES ('tenant_a', NULL, 5, 'deleted');
|
|
INSERT INTO tenant_instances VALUES ('tenant_a', 'account_a', 'deleted', 'deleted', 'deleted');
|
|
INSERT INTO account_deletion_requests VALUES ('deletion_a', 'account_a', 'relay_purged');
|
|
INSERT INTO accounts VALUES ('account_a', 'relay_purged');
|
|
INSERT INTO hosts VALUES ('host_a', 'account_a', 'deactivated', 'released');
|
|
`);
|
|
const proof = () => Number(
|
|
db.prepare(COMPLETED_RELAY_PURGE_PROOF_SQL).get(purgeId, auditId, evidence).proof_valid,
|
|
);
|
|
assert.equal(proof(), 1);
|
|
db.prepare("INSERT INTO phone_route_handles VALUES ('route_a', 'tenant_a')").run();
|
|
assert.equal(proof(), 0);
|
|
db.prepare("DELETE FROM phone_route_handles").run();
|
|
db.prepare("UPDATE audit_events SET after_json = json_object('evidence_sha256', ?)")
|
|
.run("f".repeat(64));
|
|
assert.equal(proof(), 0);
|
|
db.prepare("DELETE FROM audit_events").run();
|
|
assert.equal(proof(), 0);
|
|
db.close();
|
|
});
|
|
|
|
test("consumes a 60-second origin-bound phone handoff only once", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE phone_handoff_tickets (
|
|
id TEXT PRIMARY KEY, ticket_hash TEXT NOT NULL, expected_origin TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL, consumed_at TEXT, consumed_by_node_id TEXT,
|
|
pending_phone_name TEXT, pending_ed25519_public TEXT,
|
|
pending_x25519_public TEXT, pending_identity_fingerprint TEXT
|
|
);
|
|
INSERT INTO phone_handoff_tickets
|
|
(id, ticket_hash, expected_origin, expires_at, consumed_at) VALUES
|
|
('handoff_a', '${"a".repeat(64)}', 'https://pwa.example.test',
|
|
'2026-08-12T12:01:00.000Z', NULL);
|
|
`);
|
|
const consume = db.prepare(CONSUME_PHONE_HANDOFF_SQL);
|
|
assert.equal(
|
|
consume.run(
|
|
"2026-08-12T12:00:30.000Z",
|
|
"handoff_a",
|
|
"a".repeat(64),
|
|
"https://pwa.example.test",
|
|
"node_a",
|
|
"Phone",
|
|
"e".repeat(43),
|
|
"x".repeat(43),
|
|
"f".repeat(64),
|
|
).changes,
|
|
1,
|
|
);
|
|
assert.equal(
|
|
consume.run(
|
|
"2026-08-12T12:00:31.000Z",
|
|
"handoff_a",
|
|
"a".repeat(64),
|
|
"https://pwa.example.test",
|
|
"node_a",
|
|
"Phone",
|
|
"e".repeat(43),
|
|
"x".repeat(43),
|
|
"f".repeat(64),
|
|
).changes,
|
|
0,
|
|
);
|
|
db.exec(
|
|
`INSERT INTO phone_handoff_tickets
|
|
(id, ticket_hash, expected_origin, expires_at, consumed_at) VALUES
|
|
('handoff_b', '${"b".repeat(64)}', 'https://pwa.example.test',
|
|
'2026-08-12T12:01:00.000Z', NULL)`,
|
|
);
|
|
assert.equal(
|
|
consume.run(
|
|
"2026-08-12T12:00:30.000Z",
|
|
"handoff_b",
|
|
"b".repeat(64),
|
|
"https://evil.example.test",
|
|
"node_a",
|
|
"Phone",
|
|
"e".repeat(43),
|
|
"x".repeat(43),
|
|
"f".repeat(64),
|
|
).changes,
|
|
0,
|
|
);
|
|
assert.equal(
|
|
consume.run(
|
|
"2026-08-12T12:01:00.000Z",
|
|
"handoff_b",
|
|
"b".repeat(64),
|
|
"https://pwa.example.test",
|
|
"node_a",
|
|
"Phone",
|
|
"e".repeat(43),
|
|
"x".repeat(43),
|
|
"f".repeat(64),
|
|
).changes,
|
|
0,
|
|
);
|
|
db.close();
|
|
});
|
|
|
|
test("records an exact idempotency key for ambiguous phone handoff completion", async () => {
|
|
const [migration, activationMigration, control] = await Promise.all([
|
|
readFile(new URL("../drizzle/0015_phone_handoff_idempotency.sql", import.meta.url), "utf8"),
|
|
readFile(new URL("../drizzle/0016_phone_handoff_activation.sql", import.meta.url), "utf8"),
|
|
readFile(new URL("../db/relay-control-plane.ts", import.meta.url), "utf8"),
|
|
]);
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec("CREATE TABLE phone_handoff_tickets (id TEXT PRIMARY KEY)");
|
|
for (const statement of migrationStatements(migration)) db.exec(statement);
|
|
for (const statement of migrationStatements(activationMigration)) db.exec(statement);
|
|
const columns = db.prepare("PRAGMA table_info(phone_handoff_tickets)").all().map((row) => row.name);
|
|
assert.ok(columns.includes("completed_phone_id"));
|
|
assert.ok(columns.includes("completed_phone_token_hash"));
|
|
assert.ok(columns.includes("completed_route_handle_hash"));
|
|
assert.ok(columns.includes("activation_nonce"));
|
|
assert.ok(columns.includes("activated_at"));
|
|
assert.match(control, /completed_phone_id = \?/);
|
|
assert.match(control, /completed_phone_token_hash !== phoneTokenHash/);
|
|
assert.match(control, /completed_route_handle_hash !== routeHandleHash/);
|
|
assert.match(control, /An identical request may have won the one-shot consume update/);
|
|
db.close();
|
|
});
|
|
|
|
test("keeps handoff credentials pending until the first exact route proof activates them once", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
const tokenHash = "a".repeat(64);
|
|
const routeHash = "b".repeat(64);
|
|
db.exec(`
|
|
CREATE TABLE phone_handoff_tickets (
|
|
id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL, consumed_by_node_id TEXT,
|
|
completed_at TEXT, completed_phone_id TEXT, completed_phone_token_hash TEXT,
|
|
completed_route_handle_hash TEXT, activation_nonce TEXT UNIQUE, activated_at TEXT
|
|
);
|
|
CREATE TABLE relay_phone_principals (
|
|
phone_id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL, token_hash TEXT NOT NULL,
|
|
name TEXT NOT NULL, ed25519_public TEXT NOT NULL, x25519_public TEXT NOT NULL,
|
|
identity_fingerprint TEXT NOT NULL, status TEXT NOT NULL,
|
|
revoked_at TEXT, last_used_at TEXT
|
|
);
|
|
CREATE TABLE phone_route_handles (
|
|
id TEXT PRIMARY KEY, handle_hash TEXT NOT NULL, tenant_id TEXT NOT NULL,
|
|
phone_id TEXT NOT NULL, status TEXT NOT NULL, revoked_at TEXT, last_used_at TEXT
|
|
);
|
|
CREATE TABLE relay_regions (id TEXT PRIMARY KEY, code TEXT NOT NULL);
|
|
CREATE TABLE tenant_placements (
|
|
tenant_id TEXT PRIMARY KEY, home_region_id TEXT NOT NULL, relay_node_id TEXT,
|
|
generation INTEGER NOT NULL, state TEXT NOT NULL
|
|
);
|
|
CREATE TABLE tenant_authorization_state (
|
|
tenant_id TEXT PRIMARY KEY, status TEXT NOT NULL, revision INTEGER NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
INSERT INTO relay_regions VALUES ('region_a', 'cn-east-1');
|
|
INSERT INTO tenant_placements VALUES ('tenant_a', 'region_a', 'node_a', 3, 'active');
|
|
INSERT INTO tenant_authorization_state VALUES ('tenant_a', 'active', 7, '2026-08-12T12:00:00.000Z');
|
|
INSERT INTO phone_handoff_tickets VALUES (
|
|
'handoff_a', 'tenant_a', 'node_a', '2026-08-12T12:00:00.000Z',
|
|
'phone_a', '${tokenHash}', '${routeHash}', NULL, NULL
|
|
);
|
|
INSERT INTO relay_phone_principals VALUES (
|
|
'phone_a', 'tenant_a', '${tokenHash}', 'Phone', 'ed', 'x', '${"f".repeat(64)}',
|
|
'pending', NULL, NULL
|
|
);
|
|
INSERT INTO phone_route_handles VALUES (
|
|
'route_a', '${routeHash}', 'tenant_a', 'phone_a', 'pending', NULL, NULL
|
|
);
|
|
`);
|
|
|
|
assert.equal(db.prepare(AUTHORIZE_PHONE_ROUTE_SQL).get(routeHash, tokenHash), undefined);
|
|
const nonce = "activation_" + "c".repeat(32);
|
|
const changes = [
|
|
db.prepare(CLAIM_PHONE_HANDOFF_ACTIVATION_SQL)
|
|
.run(nonce, routeHash, tokenHash, "2026-08-12T11:59:00.000Z", "node_a").changes,
|
|
db.prepare(ACTIVATE_PHONE_PRINCIPAL_SQL).run(nonce, tokenHash).changes,
|
|
db.prepare(ACTIVATE_PHONE_ROUTE_SQL).run(nonce, routeHash, tokenHash).changes,
|
|
db.prepare(ADVANCE_AUTHORIZATION_AFTER_PHONE_ACTIVATION_SQL)
|
|
.run("2026-08-12T12:00:01.000Z", nonce).changes,
|
|
db.prepare(FINALIZE_PHONE_HANDOFF_ACTIVATION_SQL)
|
|
.run("2026-08-12T12:00:01.000Z", nonce).changes,
|
|
].map(Number);
|
|
assert.deepEqual(changes, [1, 1, 1, 1, 1]);
|
|
assert.equal(db.prepare(AUTHORIZE_PHONE_ROUTE_SQL).get(routeHash, tokenHash).phone_id, "phone_a");
|
|
assert.equal(db.prepare("SELECT revision FROM tenant_authorization_state").get().revision, 8);
|
|
|
|
const losingNonce = "activation_" + "d".repeat(32);
|
|
const repeated = [
|
|
db.prepare(CLAIM_PHONE_HANDOFF_ACTIVATION_SQL)
|
|
.run(losingNonce, routeHash, tokenHash, "2026-08-12T11:59:00.000Z", "node_a").changes,
|
|
db.prepare(ACTIVATE_PHONE_PRINCIPAL_SQL).run(losingNonce, tokenHash).changes,
|
|
db.prepare(ACTIVATE_PHONE_ROUTE_SQL).run(losingNonce, routeHash, tokenHash).changes,
|
|
db.prepare(ADVANCE_AUTHORIZATION_AFTER_PHONE_ACTIVATION_SQL)
|
|
.run("2026-08-12T12:00:02.000Z", losingNonce).changes,
|
|
db.prepare(FINALIZE_PHONE_HANDOFF_ACTIVATION_SQL)
|
|
.run("2026-08-12T12:00:02.000Z", losingNonce).changes,
|
|
].map(Number);
|
|
assert.deepEqual(repeated, [0, 0, 0, 0, 0]);
|
|
assert.equal(db.prepare("SELECT revision FROM tenant_authorization_state").get().revision, 8);
|
|
db.close();
|
|
});
|
|
|
|
test("an expired response-lost handoff can be superseded without deleting active phones", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE phone_handoff_tickets (
|
|
id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL, expires_at TEXT NOT NULL,
|
|
pending_identity_fingerprint TEXT, completed_phone_id TEXT,
|
|
completed_phone_token_hash TEXT, completed_route_handle_hash TEXT,
|
|
activation_nonce TEXT
|
|
);
|
|
CREATE TABLE relay_phone_principals (
|
|
phone_id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL, token_hash TEXT NOT NULL,
|
|
status TEXT NOT NULL
|
|
);
|
|
CREATE TABLE phone_route_handles (
|
|
id TEXT PRIMARY KEY, handle_hash TEXT NOT NULL, tenant_id TEXT NOT NULL,
|
|
phone_id TEXT NOT NULL, status TEXT NOT NULL
|
|
);
|
|
INSERT INTO phone_handoff_tickets VALUES
|
|
('handoff_old', 'tenant_a', '2026-08-12T12:00:00.000Z', 'fingerprint_a',
|
|
'phone_old', 'token_old', 'route_old', NULL),
|
|
('handoff_new', 'tenant_a', '2026-08-12T12:01:30.000Z', 'fingerprint_a',
|
|
NULL, NULL, NULL, NULL),
|
|
('handoff_active', 'tenant_a', '2026-08-12T12:00:00.000Z', 'fingerprint_b',
|
|
'phone_active', 'token_active', 'route_active', 'activation_done');
|
|
INSERT INTO relay_phone_principals VALUES
|
|
('phone_old', 'tenant_a', 'token_old', 'pending'),
|
|
('phone_active', 'tenant_a', 'token_active', 'active');
|
|
INSERT INTO phone_route_handles VALUES
|
|
('handle_old', 'route_old', 'tenant_a', 'phone_old', 'pending'),
|
|
('handle_active', 'route_active', 'tenant_a', 'phone_active', 'active');
|
|
`);
|
|
assert.equal(
|
|
db.prepare(DELETE_SUPERSEDED_PENDING_PHONE_ROUTES_SQL)
|
|
.run('handoff_new', '2026-08-12T12:00:01.000Z').changes,
|
|
1,
|
|
);
|
|
assert.equal(
|
|
db.prepare(DELETE_SUPERSEDED_PENDING_PHONE_PRINCIPALS_SQL)
|
|
.run('handoff_new', '2026-08-12T12:00:01.000Z').changes,
|
|
1,
|
|
);
|
|
assert.deepEqual(
|
|
db.prepare("SELECT phone_id, status FROM relay_phone_principals").all().map((row) => ({ ...row })),
|
|
[{ phone_id: "phone_active", status: "active" }],
|
|
);
|
|
assert.deepEqual(
|
|
db.prepare("SELECT id, status FROM phone_route_handles").all().map((row) => ({ ...row })),
|
|
[{ id: "handle_active", status: "active" }],
|
|
);
|
|
db.close();
|
|
});
|
|
|
|
test("requires a short-lived trusted mTLS terminator assertion", async () => {
|
|
const assertionSecret = "m".repeat(32);
|
|
const timestampSeconds = 1_786_536_000;
|
|
const identity = {
|
|
nodeId: "node_cn-east-1",
|
|
spiffeId: "spiffe://nekonest.cloud/relay/cn-east-1",
|
|
certificateFingerprintSha256: "a".repeat(64),
|
|
};
|
|
const pathname = "/api/internal/relay/heartbeat";
|
|
const assertion = await createTrustedRelayMtlsAssertion({
|
|
assertionSecret,
|
|
method: "POST",
|
|
pathname,
|
|
...identity,
|
|
timestampSeconds,
|
|
});
|
|
const request = new Request(`https://control.example.test${pathname}`, {
|
|
method: "POST",
|
|
headers: {
|
|
"x-neko-relay-node-id": identity.nodeId,
|
|
"x-neko-mtls-spiffe-id": identity.spiffeId,
|
|
"x-neko-mtls-cert-sha256": identity.certificateFingerprintSha256,
|
|
"x-neko-mtls-verified": "SUCCESS",
|
|
"x-neko-mtls-timestamp": String(timestampSeconds),
|
|
"x-neko-mtls-assertion": assertion,
|
|
},
|
|
});
|
|
assert.deepEqual(
|
|
await verifyTrustedRelayMtlsIdentity({
|
|
request,
|
|
assertionSecret,
|
|
nowMs: timestampSeconds * 1_000,
|
|
}),
|
|
identity,
|
|
);
|
|
await assert.rejects(
|
|
verifyTrustedRelayMtlsIdentity({ request, assertionSecret: "", nowMs: timestampSeconds * 1_000 }),
|
|
/not configured/,
|
|
);
|
|
const spoofed = new Request(request, {
|
|
headers: { ...Object.fromEntries(request.headers), "x-neko-mtls-spiffe-id": "spiffe://evil/relay" },
|
|
});
|
|
await assert.rejects(
|
|
verifyTrustedRelayMtlsIdentity({ request: spoofed, assertionSecret, nowMs: timestampSeconds * 1_000 }),
|
|
);
|
|
await assert.rejects(
|
|
verifyTrustedRelayMtlsIdentity({
|
|
request,
|
|
assertionSecret,
|
|
nowMs: (timestampSeconds + 31) * 1_000,
|
|
}),
|
|
/expired/,
|
|
);
|
|
});
|
|
|
|
test("binds Relay node identity to active unexpired SPIFFE and certificate records", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE relay_nodes (id TEXT PRIMARY KEY, status TEXT);
|
|
CREATE TABLE relay_node_credentials (
|
|
node_id TEXT, mtls_spiffe_id TEXT, certificate_fingerprint_sha256 TEXT,
|
|
status TEXT, issued_at TEXT, expires_at TEXT, revoked_at TEXT, last_used_at TEXT
|
|
);
|
|
INSERT INTO relay_nodes VALUES ('node_a', 'active');
|
|
INSERT INTO relay_node_credentials VALUES
|
|
('node_a', 'spiffe://nekonest.cloud/relay/a', '${"a".repeat(64)}', 'active',
|
|
'2026-08-12T11:00:00.000Z', '2026-08-12T13:00:00.000Z', NULL, NULL);
|
|
`);
|
|
const authenticate = db.prepare(AUTHENTICATE_RELAY_NODE_IDENTITY_SQL);
|
|
const args = [
|
|
"2026-08-12T12:00:00.000Z", "node_a",
|
|
"spiffe://nekonest.cloud/relay/a", "a".repeat(64),
|
|
];
|
|
assert.equal(authenticate.run(...args).changes, 1);
|
|
assert.equal(authenticate.run(args[0], args[1], "spiffe://evil/relay", args[3]).changes, 0);
|
|
assert.equal(authenticate.run(args[0], args[1], args[2], "b".repeat(64)).changes, 0);
|
|
db.prepare("UPDATE relay_node_credentials SET revoked_at = ?, status = 'revoked'")
|
|
.run("2026-08-12T12:00:01.000Z");
|
|
assert.equal(authenticate.run(...args).changes, 0);
|
|
db.prepare("UPDATE relay_node_credentials SET revoked_at = NULL, status = 'active', expires_at = ?")
|
|
.run("2026-08-12T12:00:00.000Z");
|
|
assert.equal(authenticate.run(...args).changes, 0);
|
|
db.close();
|
|
});
|
|
|
|
test("encrypts one bounded registration response for lost-response replay", async () => {
|
|
const response = {
|
|
device_id: `host_${"a".repeat(32)}`,
|
|
token: "b".repeat(64),
|
|
name: "Workstation",
|
|
transport_mode: "sealed",
|
|
connection_state: "provisioning",
|
|
retry_after_seconds: 5,
|
|
};
|
|
const common = {
|
|
retryKey: "c".repeat(64),
|
|
pairingId: `pair_${"d".repeat(32)}`,
|
|
requestHash: "e".repeat(64),
|
|
};
|
|
const encrypted = await encryptRegistrationReplay({ ...common, response });
|
|
assert.doesNotMatch(encrypted.ciphertext, new RegExp(response.token));
|
|
assert.deepEqual(await decryptRegistrationReplay({ ...common, ...encrypted }), response);
|
|
await assert.rejects(
|
|
decryptRegistrationReplay({ ...common, ...encrypted, retryKey: "f".repeat(64) }),
|
|
);
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec("CREATE TABLE device_registration_replays (pairing_id TEXT PRIMARY KEY, request_hash TEXT)");
|
|
db.prepare("INSERT INTO device_registration_replays VALUES (?, ?)").run(common.pairingId, common.requestHash);
|
|
assert.throws(
|
|
() => db.prepare("INSERT INTO device_registration_replays VALUES (?, ?)")
|
|
.run(common.pairingId, "f".repeat(64)),
|
|
/UNIQUE constraint failed/,
|
|
);
|
|
db.close();
|
|
});
|
|
|
|
test("authorizes phone routing only when handle and active token digest match one tenant", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE phone_route_handles (
|
|
handle_hash TEXT, tenant_id TEXT, phone_id TEXT, status TEXT,
|
|
revoked_at TEXT
|
|
);
|
|
CREATE TABLE relay_phone_principals (
|
|
phone_id TEXT, tenant_id TEXT, token_hash TEXT, name TEXT,
|
|
ed25519_public TEXT, x25519_public TEXT, identity_fingerprint TEXT,
|
|
status TEXT, revoked_at TEXT
|
|
);
|
|
CREATE TABLE tenant_placements (
|
|
tenant_id TEXT, home_region_id TEXT, relay_node_id TEXT,
|
|
generation INTEGER, state TEXT
|
|
);
|
|
CREATE TABLE relay_regions (id TEXT, code TEXT);
|
|
CREATE TABLE tenant_authorization_state (tenant_id TEXT, status TEXT);
|
|
INSERT INTO relay_regions VALUES ('region_a', 'cn-east');
|
|
INSERT INTO tenant_authorization_state VALUES ('tenant_a', 'active'), ('tenant_b', 'active');
|
|
INSERT INTO tenant_placements VALUES
|
|
('tenant_a', 'region_a', 'node_a', 1, 'active'),
|
|
('tenant_b', 'region_a', 'node_a', 1, 'active');
|
|
INSERT INTO relay_phone_principals VALUES
|
|
('phone_a', 'tenant_a', '${"a".repeat(64)}', 'Phone A', 'ed-a', 'x-a', 'fp-a', 'active', NULL),
|
|
('phone_b', 'tenant_b', '${"b".repeat(64)}', 'Phone B', 'ed-b', 'x-b', 'fp-b', 'active', NULL);
|
|
INSERT INTO phone_route_handles VALUES
|
|
('${"1".repeat(64)}', 'tenant_a', 'phone_a', 'active', NULL),
|
|
('${"2".repeat(64)}', 'tenant_b', 'phone_b', 'active', NULL);
|
|
`);
|
|
const authorize = db.prepare(AUTHORIZE_PHONE_ROUTE_SQL);
|
|
assert.equal(authorize.get("1".repeat(64), "a".repeat(64)).phone_id, "phone_a");
|
|
assert.equal(authorize.get("1".repeat(64), "b".repeat(64)), undefined);
|
|
assert.equal(authorize.get("2".repeat(64), "a".repeat(64)), undefined);
|
|
assert.equal(authorize.get("1".repeat(64), ""), undefined);
|
|
db.prepare("UPDATE relay_phone_principals SET status = 'revoked', revoked_at = '2026-08-12T12:00:00.000Z' WHERE phone_id = 'phone_a'").run();
|
|
assert.equal(authorize.get("1".repeat(64), "a".repeat(64)), undefined);
|
|
db.close();
|
|
});
|
|
|
|
test("revokes a phone only through its current tenant placement and rejects it afterward", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE tenant_placements (tenant_id TEXT, relay_node_id TEXT);
|
|
CREATE TABLE relay_phone_principals (
|
|
phone_id TEXT, tenant_id TEXT, token_hash TEXT, name TEXT,
|
|
ed25519_public TEXT, x25519_public TEXT, identity_fingerprint TEXT,
|
|
status TEXT, revoked_at TEXT
|
|
);
|
|
CREATE TABLE phone_route_handles (
|
|
handle_hash TEXT, tenant_id TEXT, phone_id TEXT, status TEXT, revoked_at TEXT
|
|
);
|
|
CREATE TABLE tenant_authorization_state (
|
|
tenant_id TEXT PRIMARY KEY, revision INTEGER, status TEXT, updated_at TEXT
|
|
);
|
|
CREATE TABLE relay_regions (id TEXT, code TEXT);
|
|
INSERT INTO relay_regions VALUES ('region_a', 'cn-east');
|
|
ALTER TABLE tenant_placements ADD home_region_id TEXT;
|
|
ALTER TABLE tenant_placements ADD generation INTEGER;
|
|
ALTER TABLE tenant_placements ADD state TEXT;
|
|
INSERT INTO tenant_placements VALUES ('tenant_a', 'node_a', 'region_a', 1, 'active');
|
|
INSERT INTO relay_phone_principals VALUES
|
|
('phone_a', 'tenant_a', '${"a".repeat(64)}', 'Phone', 'ed', 'x', 'fp', 'active', NULL);
|
|
INSERT INTO phone_route_handles VALUES
|
|
('${"1".repeat(64)}', 'tenant_a', 'phone_a', 'active', NULL);
|
|
INSERT INTO tenant_authorization_state VALUES
|
|
('tenant_a', 1, 'active', '2026-08-12T11:00:00.000Z');
|
|
`);
|
|
const ownership = db.prepare(PHONE_FOR_NODE_REVOCATION_SQL);
|
|
assert.equal(ownership.get('tenant_a', 'phone_a').relay_node_id, 'node_a');
|
|
assert.equal(ownership.get('tenant_b', 'phone_a'), undefined);
|
|
const now = '2026-08-12T12:00:00.000Z';
|
|
assert.equal(db.prepare(REVOKE_PHONE_PRINCIPAL_SQL).run(now, 'phone_a', 'tenant_a').changes, 1);
|
|
assert.equal(db.prepare(REVOKE_PHONE_ROUTES_SQL).run(now, 'phone_a', 'tenant_a').changes, 1);
|
|
assert.equal(db.prepare(ADVANCE_AUTHORIZATION_AFTER_PHONE_REVOKE_SQL)
|
|
.run(now, 'tenant_a', 'phone_a').changes, 1);
|
|
const authorize = db.prepare(AUTHORIZE_PHONE_ROUTE_SQL);
|
|
assert.equal(authorize.get('1'.repeat(64), 'a'.repeat(64)), undefined);
|
|
assert.equal(db.prepare("SELECT revision FROM tenant_authorization_state").get().revision, 2);
|
|
db.close();
|
|
});
|
|
|
|
test("advances authorization revision only after a committed claim or revoke", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE tenant_instances (id TEXT PRIMARY KEY, account_id TEXT NOT NULL);
|
|
CREATE TABLE tenant_authorization_state (
|
|
tenant_id TEXT PRIMARY KEY, revision INTEGER NOT NULL, updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE hosts (
|
|
id TEXT PRIMARY KEY, lifecycle TEXT NOT NULL, deactivated_at TEXT
|
|
);
|
|
CREATE TABLE device_credentials (
|
|
id TEXT PRIMARY KEY, host_id TEXT NOT NULL, token_hash TEXT NOT NULL, status TEXT NOT NULL
|
|
);
|
|
INSERT INTO tenant_instances VALUES
|
|
('tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'acct_a');
|
|
INSERT INTO tenant_authorization_state VALUES
|
|
('tenant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 0, '2026-08-12T11:00:00.000Z');
|
|
INSERT INTO hosts VALUES
|
|
('host_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'active', NULL);
|
|
INSERT INTO device_credentials VALUES
|
|
('credential_a', 'host_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', '${"c".repeat(64)}', 'active');
|
|
`);
|
|
const claim = db.prepare(ADVANCE_AUTHORIZATION_AFTER_CLAIM_SQL);
|
|
assert.equal(
|
|
claim.run(
|
|
"2026-08-12T12:00:00.000Z",
|
|
"acct_a",
|
|
"credential_a",
|
|
"host_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"c".repeat(64),
|
|
).changes,
|
|
1,
|
|
);
|
|
assert.equal(
|
|
claim.run(
|
|
"2026-08-12T12:00:01.000Z",
|
|
"acct_a",
|
|
"credential_missing",
|
|
"host_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"c".repeat(64),
|
|
).changes,
|
|
0,
|
|
);
|
|
db.prepare("UPDATE hosts SET lifecycle = 'deactivated', deactivated_at = ? WHERE id = ?")
|
|
.run("2026-08-12T12:00:02.000Z", "host_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
assert.equal(
|
|
db.prepare(ADVANCE_AUTHORIZATION_AFTER_REVOKE_SQL).run(
|
|
"2026-08-12T12:00:02.000Z",
|
|
"acct_a",
|
|
"host_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"2026-08-12T12:00:02.000Z",
|
|
).changes,
|
|
1,
|
|
);
|
|
assert.equal(
|
|
db.prepare("SELECT revision FROM tenant_authorization_state").get().revision,
|
|
2,
|
|
);
|
|
db.close();
|
|
});
|
|
|
|
test("atomically reserves finite host capacity and does not double-book a pending slot", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE beta_programs (
|
|
id TEXT PRIMARY KEY, capacity_slots INTEGER, state TEXT NOT NULL,
|
|
starts_at TEXT NOT NULL, ends_at TEXT, created_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE launch_gates (
|
|
key TEXT PRIMARY KEY, priority TEXT NOT NULL, status TEXT NOT NULL,
|
|
owner TEXT, notes TEXT, evidence_url TEXT
|
|
);
|
|
CREATE TABLE entitlement_grants (
|
|
account_id TEXT NOT NULL, state TEXT NOT NULL, starts_at TEXT NOT NULL,
|
|
ends_at TEXT, revoked_at TEXT, capacity_slots INTEGER
|
|
);
|
|
CREATE TABLE hosts (
|
|
account_id TEXT NOT NULL, lifecycle TEXT NOT NULL, slot_state TEXT NOT NULL
|
|
);
|
|
CREATE TABLE pairing_requests (
|
|
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, requested_name TEXT NOT NULL,
|
|
os TEXT NOT NULL, code_hash TEXT NOT NULL, status TEXT NOT NULL,
|
|
expires_at TEXT NOT NULL, created_at TEXT NOT NULL
|
|
);
|
|
INSERT INTO beta_programs VALUES
|
|
('beta', 1, 'active', '2026-08-12T00:00:00.000Z', NULL,
|
|
'2026-08-12T00:00:00.000Z');
|
|
`);
|
|
const insertGate = db.prepare(
|
|
`INSERT INTO launch_gates VALUES (?, 'P0', 'passed', 'owner', 'evidence', 'https://example.test/evidence')`,
|
|
);
|
|
for (const key of REQUIRED_PUBLIC_BETA_P0_KEYS) insertGate.run(key);
|
|
const reserve = db.prepare(RESERVE_PAIRING_SQL);
|
|
const params = (id, code) => [
|
|
id,
|
|
"acct_a",
|
|
"Host",
|
|
"linux",
|
|
code,
|
|
"2026-08-12T12:10:00.000Z",
|
|
"2026-08-12T12:00:00.000Z",
|
|
];
|
|
assert.equal(reserve.run(...params("pair_a", "a".repeat(64))).changes, 1);
|
|
assert.equal(reserve.run(...params("pair_b", "b".repeat(64))).changes, 0);
|
|
db.prepare("UPDATE pairing_requests SET status = 'cancelled' WHERE id = 'pair_a'").run();
|
|
assert.equal(reserve.run(...params("pair_b", "b".repeat(64))).changes, 1);
|
|
db.close();
|
|
});
|
|
|
|
test("keeps Relay-provided registration source hashes in isolated rate-limit buckets", () => {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE pairing_claim_rate_limits (
|
|
source_hash TEXT NOT NULL, window_start TEXT NOT NULL,
|
|
attempts INTEGER NOT NULL, updated_at TEXT NOT NULL,
|
|
PRIMARY KEY (source_hash, window_start)
|
|
);
|
|
`);
|
|
const consume = db.prepare(CONSUME_CLAIM_RATE_SQL);
|
|
const window = "2026-08-12T12:00:00.000Z";
|
|
for (let index = 0; index < 20; index += 1) {
|
|
assert.equal(consume.run("a".repeat(64), window, window).changes, 1);
|
|
}
|
|
assert.equal(consume.run("a".repeat(64), window, window).changes, 0);
|
|
assert.equal(consume.run("b".repeat(64), window, window).changes, 1);
|
|
assert.equal(db.prepare("SELECT attempts FROM pairing_claim_rate_limits WHERE source_hash = ?")
|
|
.get("b".repeat(64)).attempts, 1);
|
|
db.close();
|
|
});
|
|
|
|
test("removes activation polling and exposes only stable-endpoint relay APIs", async () => {
|
|
const [registration, dashboardHandoff, consume, complete, relayRepository, docs,
|
|
registerRoute, snapshotRoute, revokePhoneRoute] = await Promise.all([
|
|
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/api/pwa/handoff/route.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/api/internal/relay/consume-phone-handoff/route.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/api/internal/relay/complete-phone-handoff/route.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../db/relay-control-plane.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../docs/daemon-relay-handoff.md", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/api/internal/relay/register-device/route.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/api/internal/relay/authorization-snapshot/route.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/api/internal/relay/revoke-phone/route.ts", import.meta.url), "utf8"),
|
|
]);
|
|
assert.doesNotMatch(registration, /activation_poll_path|relay_url/);
|
|
assert.match(registration, /connection_state/);
|
|
assert.match(dashboardHandoff, /createPhoneHandoffTicket/);
|
|
assert.match(consume, /phone_ed25519_public/);
|
|
assert.match(consume, /phone_x25519_public/);
|
|
assert.match(complete, /phone_token_hash/);
|
|
assert.doesNotMatch(relayRepository, /phone_token:\s*string/);
|
|
assert.match(relayRepository, /routeHandleHash/);
|
|
assert.match(registerRoute, /authenticateRelayNode/);
|
|
assert.match(registerRoute, /trustedSourceHash/);
|
|
assert.match(registerRoute, /registrationRetryKey/);
|
|
assert.doesNotMatch(registerRoute, /x-forwarded-for|cf-connecting-ip|relay_url|redirect/);
|
|
assert.match(snapshotRoute, /placementGeneration/);
|
|
assert.match(relayRepository, /classifySnapshotPlacement/);
|
|
assert.match(relayRepository, /expectedGeneration: input\.placementGeneration/);
|
|
assert.match(relayRepository, /access_suspended/);
|
|
assert.match(revokePhoneRoute, /authenticateRelayNode/);
|
|
assert.match(relayRepository, /phone\.revoked/);
|
|
assert.match(
|
|
relayRepository,
|
|
/placements\.state IN \('active', 'draining'\) AND placements\.relay_node_id = \?/,
|
|
);
|
|
assert.match(registration, /device_registration_replays/);
|
|
const claimBody = registration.slice(registration.indexOf("export async function claimDevice"));
|
|
assert.ok(
|
|
claimBody.indexOf("FROM device_registration_replays WHERE pairing_id")
|
|
< claimBody.indexOf("const pairing = await db"),
|
|
"lost-response replay must resolve before a spent pairing code is rejected",
|
|
);
|
|
assert.match(registration, /device_identity_conflict/);
|
|
assert.match(registration, /device_capacity_exceeded/);
|
|
assert.match(registration, /registration_disabled/);
|
|
assert.match(registration, /registration_rate_limited/);
|
|
assert.match(registration, /protocol_upgrade_required/);
|
|
assert.match(docs, /同一.*服务地址/);
|
|
});
|