From da0b5993c44eccb60b17b4572473be52be9729af Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Thu, 29 Oct 2026 20:37:45 +0000 Subject: [PATCH 1/2] fix(web): preserve domain verification when status checks fail --- .../unit/check-organization-domain.test.ts | 116 ++++++++++++++++++ apps/web/actions/organization/check-domain.ts | 2 + 2 files changed, 118 insertions(+) create mode 100644 apps/web/__tests__/unit/check-organization-domain.test.ts diff --git a/apps/web/__tests__/unit/check-organization-domain.test.ts b/apps/web/__tests__/unit/check-organization-domain.test.ts new file mode 100644 index 00000000000..e19a2ae6b6e --- /dev/null +++ b/apps/web/__tests__/unit/check-organization-domain.test.ts @@ -0,0 +1,116 @@ +import { Organisation } from "@cap/web-domain"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { checkOrganizationDomain } from "@/actions/organization/check-domain"; + +const mocks = vi.hoisted(() => ({ + verifiedAt: new Date("2026-01-01T00:00:00Z") as Date | null, + update: vi.fn(), +})); + +vi.mock("@cap/database/auth/session", () => ({ + getCurrentUser: async () => ({ id: "owner" }), +})); + +vi.mock("@cap/database", () => ({ + db: () => ({ + select: () => ({ + from: () => ({ + where: async () => [ + { + id: "org", + customDomain: "videos.example.com", + domainVerified: mocks.verifiedAt, + }, + ], + }), + }), + update: () => ({ + set: (values: unknown) => ({ where: () => mocks.update(values) }), + }), + }), +})); + +vi.mock("@/actions/organization/authorization", () => ({ + requireOrganizationSettingsManager: vi.fn(), +})); + +const organizationId = Organisation.OrganisationId.make("org"); + +function providerResponses(misconfigured: boolean) { + vi.stubGlobal( + "fetch", + vi.fn(async (url: string) => { + if (url.includes("/config?")) { + return Response.json({ misconfigured }); + } + if (url.includes("/records?")) return Response.json({ records: [] }); + return Response.json({ verified: true }); + }), + ); +} + +describe("checkOrganizationDomain", () => { + beforeEach(() => { + mocks.verifiedAt = new Date("2026-01-01T00:00:00Z"); + mocks.update.mockReset(); + vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it.each(["network", "invalid JSON"])( + "keeps verification on a %s failure and reports the lookup error", + async (failure) => { + vi.stubGlobal( + "fetch", + failure === "network" + ? vi.fn().mockRejectedValue(new Error("Network unavailable")) + : vi + .fn() + .mockResolvedValue( + new Response("Service unavailable", { status: 503 }), + ), + ); + + await expect(checkOrganizationDomain(organizationId)).rejects.toThrow( + "Failed to check domain status", + ); + expect(mocks.update).not.toHaveBeenCalled(); + }, + ); + + it("revokes verification when the provider confirms misconfiguration", async () => { + providerResponses(true); + + await expect( + checkOrganizationDomain(organizationId), + ).resolves.toMatchObject({ verified: false }); + expect(mocks.update).toHaveBeenCalledExactlyOnceWith({ + domainVerified: null, + }); + }); + + it("records verification when the provider confirms a new domain", async () => { + mocks.verifiedAt = null; + providerResponses(false); + + await expect( + checkOrganizationDomain(organizationId), + ).resolves.toMatchObject({ verified: true }); + expect(mocks.update).toHaveBeenCalledExactlyOnceWith({ + domainVerified: expect.any(Date), + }); + }); + + it("keeps the existing timestamp for a domain that is still verified", async () => { + providerResponses(false); + + await expect( + checkOrganizationDomain(organizationId), + ).resolves.toMatchObject({ verified: true }); + expect(mocks.update).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/web/actions/organization/check-domain.ts b/apps/web/actions/organization/check-domain.ts index 81b7087c59f..48e7a7189e9 100644 --- a/apps/web/actions/organization/check-domain.ts +++ b/apps/web/actions/organization/check-domain.ts @@ -33,6 +33,8 @@ export async function checkOrganizationDomain( try { const status = await checkDomainStatus(organization.customDomain); + if (status.error) throw new Error(status.error); + if (status.verified && !organization.domainVerified) { await db() .update(organizations) From ec54195e68404affde6b447eae3cdc438d774a8c Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Thu, 29 Oct 2026 20:47:23 +0000 Subject: [PATCH 2/2] fix(web): treat Vercel error responses as failed domain checks --- .../unit/check-organization-domain.test.ts | 40 +++++++++++++++++++ apps/web/actions/organization/domain-utils.ts | 12 +++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/apps/web/__tests__/unit/check-organization-domain.test.ts b/apps/web/__tests__/unit/check-organization-domain.test.ts index e19a2ae6b6e..4621d84302a 100644 --- a/apps/web/__tests__/unit/check-organization-domain.test.ts +++ b/apps/web/__tests__/unit/check-organization-domain.test.ts @@ -82,6 +82,46 @@ describe("checkOrganizationDomain", () => { }, ); + it("keeps verification when the provider returns a JSON error", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => + Response.json( + { error: { code: "internal_server_error", message: "Unavailable" } }, + { status: 500 }, + ), + ), + ); + + await expect(checkOrganizationDomain(organizationId)).rejects.toThrow( + "Failed to check domain status", + ); + expect(mocks.update).not.toHaveBeenCalled(); + }); + + it("revokes verification when the provider no longer has the domain", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async (url: string) => { + if (url.includes("/config?")) { + return Response.json({ misconfigured: false }); + } + if (url.includes("/records?")) return Response.json({ records: [] }); + return Response.json( + { error: { code: "not_found", message: "Domain not found" } }, + { status: 404 }, + ); + }), + ); + + await expect( + checkOrganizationDomain(organizationId), + ).resolves.toMatchObject({ verified: false }); + expect(mocks.update).toHaveBeenCalledExactlyOnceWith({ + domainVerified: null, + }); + }); + it("revokes verification when the provider confirms misconfiguration", async () => { providerResponses(true); diff --git a/apps/web/actions/organization/domain-utils.ts b/apps/web/actions/organization/domain-utils.ts index ea3a5ed5332..e4c4dded339 100644 --- a/apps/web/actions/organization/domain-utils.ts +++ b/apps/web/actions/organization/domain-utils.ts @@ -168,15 +168,23 @@ export const checkDomainStatus = async (domain: string) => { getRequiredConfig(domain), ]); + const domainNotFound = domainJson?.error?.code === "not_found"; + if (configJson?.error || (domainJson?.error && !domainNotFound)) { + throw new Error("Vercel domain lookup failed"); + } + let verified = false; - if (configJson.misconfigured || domainJson?.error?.code === "not_found") { + if (configJson.misconfigured || domainNotFound) { verified = false; } else if (domainJson.verified) { verified = true; } else { const verificationJson = await verifyDomain(domain); - verified = verificationJson?.verified; + if (typeof verificationJson?.verified !== "boolean") { + throw new Error("Vercel domain verification failed"); + } + verified = verificationJson.verified; } const currentAValues = configJson.aValues || [];