Skip to content
Closed
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: 7 additions & 1 deletion src/orb/relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,14 @@ export async function forwardOrbEvent(
fetchImpl: typeof fetch = fetch,
): Promise<"forwarded" | "queued" | "skipped" | "failed"> {
if (!args.installationId || !RELAY_FORWARD_EVENTS.has(args.eventName)) return "skipped";
// issueOrbEnrollment inserts a new `enrolled` row per enrollment without revoking the prior one, so one
// installation can have several enrolled rows. Without ORDER BY, D1/SQLite returns an arbitrary match, so a
// stale row (no relay URL, or an old push URL) can win over the current container. Pick the most recently
// registered enrollment — by relay_registered_at, then enrolled_at — so re-enrollment targets the new host (#1783).
const row = await env.DB
.prepare("SELECT relay_mode, relay_url, relay_secret_enc, relay_secret_iv, relay_secret_salt FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL")
.prepare(
"SELECT relay_mode, relay_url, relay_secret_enc, relay_secret_iv, relay_secret_salt FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL ORDER BY relay_registered_at DESC, enrolled_at DESC",
)
.bind(args.installationId)
.first<{ relay_mode: string; relay_url: string | null; relay_secret_enc: string | null; relay_secret_iv: string | null; relay_secret_salt: string | null }>();
if (!row) return "skipped"; // not a brokered self-host (or revoked) — nothing to relay to
Expand Down
13 changes: 13 additions & 0 deletions test/integration/orb-relay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@ describe("forwardOrbEvent", () => {
expect(calls[0]?.init?.body).toBe(body);
});

it("forwards to the most recently registered enrollment when an installation has multiple enrolled rows (#1783)", async () => {
const e = brokeredEnv();
// Re-enrollment inserts a second `enrolled` row for the same installation (the install is already seeded,
// so only issueOrbEnrollment runs again); row A stays enrolled with no relay.
await enroll(e, 815); // row A — enrolled, never registers a relay
const secretB = ((await issueOrbEnrollment(e, 815)) as { secret: string }).secret; // row B — the current container
await registerOrbRelay(e, secretB, "https://new-host.example/v1/orb/relay"); // register the relay on row B only
const { fetchImpl, calls } = capture(new Response("ok"));
// Without ordering, the arbitrary first row could be A (no relay) → "skipped"; the fix must pick B.
expect(await forwardOrbEvent(e, { eventName: "pull_request", installationId: 815, deliveryId: "d-1783", rawBody: "{}" }, fetchImpl)).toBe("forwarded");
expect(calls[0]?.url).toBe("https://new-host.example/v1/orb/relay");
});

it("returns FAILED (never throws) on a non-ok response or a thrown fetch — the Orb 202 always stands", async () => {
const e = brokeredEnv();
const secret = await enroll(e, 802);
Expand Down
Loading