fix: validate owner email on platform org creation (#26286)

Remove isPlatform bypass from owner verification to ensure users can only create organizations where they are the designated owner. Add test coverage for create and intentToCreateOrg handlers:

- Regression tests for isPlatform bypass fix
- Happy path for admin creating org for another user
This commit is contained in:
Pedro Castro
2026-01-06 22:46:44 -03:00
committed by GitHub
parent 30ef38804c
commit 01be9f1ef2
4 changed files with 108 additions and 2 deletions
@@ -0,0 +1,87 @@
import prismock from "../../../../../../tests/libs/__mocks__/prisma";
import { describe, expect, it, vi, beforeEach } from "vitest";
import { UserPermissionRole } from "@calcom/prisma/enums";
import { createHandler } from "./create.handler";
vi.mock("@calcom/lib/constants", async (importOriginal) => {
const actual = await importOriginal<typeof import("@calcom/lib/constants")>();
return {
...actual,
RESERVED_SUBDOMAINS: [],
ORG_SELF_SERVE_ENABLED: true,
ORG_MINIMUM_PUBLISHED_TEAMS_SELF_SERVE: 0,
};
});
vi.mock("@calcom/lib/domainManager/organization", () => ({
createDomain: vi.fn().mockResolvedValue(true),
}));
vi.mock("@calcom/lib/server/i18n", () => ({
getTranslation: vi.fn().mockResolvedValue((key: string) => key),
}));
const createTestUser = async (overrides: { email: string; role?: UserPermissionRole }) => {
return prismock.user.create({
data: {
email: overrides.email,
username: overrides.email.split("@")[0],
role: overrides.role ?? UserPermissionRole.USER,
completedOnboarding: true,
emailVerified: new Date(),
},
});
};
const createInput = (overrides: Partial<Parameters<typeof createHandler>[0]["input"]> = {}) => ({
name: "Test Org",
slug: "test-org",
orgOwnerEmail: "owner@example.com",
isPlatform: false,
creationSource: "WEBAPP" as const,
...overrides,
});
describe("createHandler", () => {
beforeEach(async () => {
vi.clearAllMocks();
await prismock.reset();
});
describe("organization ownership authorization", () => {
it.each([
{ orgOwnerEmail: "other@example.com" },
{ orgOwnerEmail: "other@example.com", isPlatform: true },
])("rejects non-admin creating org for another user (%o)", async (inputOverrides) => {
const user = await createTestUser({ email: "user@example.com" });
await expect(
createHandler({
input: createInput(inputOverrides),
ctx: { user },
})
).rejects.toMatchObject({
code: "FORBIDDEN",
message: "You can only create organization where you are the owner",
});
});
it("allows admin to bypass owner email restriction", async () => {
const admin = await createTestUser({ email: "admin@example.com", role: UserPermissionRole.ADMIN });
await createTestUser({ email: "owner@example.com" });
const adminWithProfile = {
...admin,
profile: admin.profile ?? { organizationId: null },
};
const result = await createHandler({
input: createInput({ orgOwnerEmail: "owner@example.com" }),
ctx: { user: adminWithProfile },
});
expect(result.email).toBe("owner@example.com");
});
});
});
@@ -92,7 +92,7 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => {
throw new TRPCError({ code: "FORBIDDEN", message: "Only admins can create organizations" });
}
if (!IS_USER_ADMIN && loggedInUser.email !== orgOwnerEmail && !isPlatform) {
if (!IS_USER_ADMIN && loggedInUser.email !== orgOwnerEmail) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You can only create organization where you are the owner",
@@ -281,6 +281,25 @@ describe("intentToCreateOrgHandler", () => {
);
});
it("should reject non-admin creating org for another user even with isPlatform flag", async () => {
const nonAdminUser = await createTestUser({
email: "nonadmin@example.com",
role: UserPermissionRole.USER,
});
await expect(
intentToCreateOrgHandler({
input: { ...mockInput, isPlatform: true },
ctx: {
user: nonAdminUser,
},
})
).rejects.toMatchObject({
code: "FORBIDDEN",
message: "You can only create organization where you are the owner",
});
});
it("should throw error when target user is not found", async () => {
// Create admin user
const adminUser = await createTestUser({
@@ -52,7 +52,7 @@ export const intentToCreateOrgHandler = async ({ input, ctx }: CreateOptions) =>
const IS_USER_ADMIN = loggedInUser.role === UserPermissionRole.ADMIN;
log.debug("User authorization check", safeStringify({ userId: loggedInUser.id, isAdmin: IS_USER_ADMIN }));
if (!IS_USER_ADMIN && loggedInUser.email !== orgOwnerEmail && !isPlatform) {
if (!IS_USER_ADMIN && loggedInUser.email !== orgOwnerEmail) {
log.warn(
"Unauthorized organization creation attempt",
safeStringify({ loggedInUserEmail: loggedInUser.email, orgOwnerEmail })