mirror of
https://github.com/baptisteArno/typebot.io.git
synced 2026-07-21 21:13:45 +08:00
🐛 Fix custom domain deletion ownership check (#2541)
- Added a scoped custom-domain lookup before provider deletion. - Used the verified stored domain name for provider and database deletion. - Added regression tests for denied cross-workspace deletion attempts and valid deletion.
This commit is contained in:
parent
c7ae1c162e
commit
06575dfcd4
@ -0,0 +1,107 @@
|
||||
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
||||
|
||||
const workspaceFindFirst = mock();
|
||||
const customDomainFindFirst = mock();
|
||||
const customDomainDeleteMany = mock();
|
||||
const kyDelete = mock();
|
||||
|
||||
process.env.SKIP_ENV_CHECK = "true";
|
||||
process.env.ENCRYPTION_SECRET = "12345678901234567890123456789012";
|
||||
process.env.NEXTAUTH_URL = "https://app.typebot.io";
|
||||
process.env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME = "viewer-project";
|
||||
process.env.VERCEL_TEAM_ID = "team-id";
|
||||
process.env.VERCEL_TOKEN = "vercel-token";
|
||||
|
||||
mock.module("@typebot.io/lib/ky", () => ({
|
||||
ky: {
|
||||
delete: kyDelete,
|
||||
},
|
||||
}));
|
||||
|
||||
mock.module("@typebot.io/prisma", () => ({
|
||||
default: {
|
||||
workspace: {
|
||||
findFirst: workspaceFindFirst,
|
||||
},
|
||||
customDomain: {
|
||||
findFirst: customDomainFindFirst,
|
||||
deleteMany: customDomainDeleteMany,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const { handleDeleteCustomDomain } = await import("./handleDeleteCustomDomain");
|
||||
|
||||
describe("handleDeleteCustomDomain", () => {
|
||||
beforeEach(() => {
|
||||
workspaceFindFirst.mockReset();
|
||||
customDomainFindFirst.mockReset();
|
||||
customDomainDeleteMany.mockReset();
|
||||
kyDelete.mockReset();
|
||||
|
||||
workspaceFindFirst.mockResolvedValue({
|
||||
members: [{ userId: "user-id", role: "ADMIN" }],
|
||||
});
|
||||
kyDelete.mockResolvedValue({});
|
||||
customDomainDeleteMany.mockResolvedValue({ count: 1 });
|
||||
});
|
||||
|
||||
it("does not delete an external domain when it is not owned by the workspace", async () => {
|
||||
customDomainFindFirst.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
handleDeleteCustomDomain({
|
||||
input: {
|
||||
workspaceId: "workspace-id",
|
||||
name: "other-workspace.example.com",
|
||||
},
|
||||
context: {
|
||||
user: { id: "user-id" },
|
||||
},
|
||||
}),
|
||||
).rejects.toBeDefined();
|
||||
|
||||
expect(customDomainFindFirst).toHaveBeenCalledWith({
|
||||
where: {
|
||||
name: "other-workspace.example.com",
|
||||
workspaceId: "workspace-id",
|
||||
},
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
expect(kyDelete).not.toHaveBeenCalled();
|
||||
expect(customDomainDeleteMany).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("deletes an owned external domain and the matching database record", async () => {
|
||||
customDomainFindFirst.mockResolvedValue({
|
||||
name: "owned.example.com",
|
||||
});
|
||||
|
||||
await expect(
|
||||
handleDeleteCustomDomain({
|
||||
input: {
|
||||
workspaceId: "workspace-id",
|
||||
name: "owned.example.com",
|
||||
},
|
||||
context: {
|
||||
user: { id: "user-id" },
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual({ message: "success" });
|
||||
|
||||
expect(kyDelete).toHaveBeenCalledWith(
|
||||
"https://api.vercel.com/v9/projects/viewer-project/domains/owned.example.com?teamId=team-id",
|
||||
{
|
||||
headers: { Authorization: "Bearer vercel-token" },
|
||||
},
|
||||
);
|
||||
expect(customDomainDeleteMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
name: "owned.example.com",
|
||||
workspaceId: "workspace-id",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -34,8 +34,21 @@ export const handleDeleteCustomDomain = async ({
|
||||
if (!workspace || isWriteWorkspaceForbidden(workspace, user))
|
||||
throw new ORPCError("NOT_FOUND", { message: "Workspace not found" });
|
||||
|
||||
const customDomain = await prisma.customDomain.findFirst({
|
||||
where: {
|
||||
name,
|
||||
workspaceId,
|
||||
},
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!customDomain)
|
||||
throw new ORPCError("NOT_FOUND", { message: "Custom domain not found" });
|
||||
|
||||
try {
|
||||
await deleteDomainOnVercel(name);
|
||||
await deleteDomainOnVercel(customDomain.name);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (error instanceof HTTPError)
|
||||
@ -50,7 +63,7 @@ export const handleDeleteCustomDomain = async ({
|
||||
}
|
||||
await prisma.customDomain.deleteMany({
|
||||
where: {
|
||||
name,
|
||||
name: customDomain.name,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user