fix: Organization Migration - Prevent the redirected username from being claimed by others. (#13675)

* Check for migrated username

* Add e2e for reserved username due to migration

* Add username change test

* Update username.ts

* fixed type checks

* Refactor to use isOrganization

* Typing

* Typing

* Removed the isOrganization function

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
Hariom Balhara
2024-03-05 17:08:44 +00:00
committed by GitHub
co-authored by Joe Au-Yeung Keith Williams
parent d8cee20d0c
commit a677d0d2c4
18 changed files with 216 additions and 45 deletions
+4 -5
View File
@@ -1,6 +1,5 @@
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
@@ -48,7 +47,7 @@ export async function moveUserToOrg({
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
if (!isOrganization({ team })) {
if (!team.isOrganization) {
throw new Error(`Team with ID:${targetOrgId} is not an Org`);
}
@@ -197,7 +196,7 @@ export async function moveTeamToOrg({
const teamMetadata = teamMetadataSchema.parse(possibleOrg?.metadata);
if (!isOrganization({ team: possibleOrg })) {
if (!possibleOrg.isOrganization) {
throw new Error(`${targetOrg.id} is not an Org`);
}
@@ -667,7 +666,7 @@ async function moveTeamsWithoutMembersToOrg({
};
})
// Remove Orgs from the list
.filter((team) => !isOrganization({ team }));
.filter((team) => !team.isOrganization);
const teamIdsToBeMovedToOrg = teamsToBeMovedToOrg.map((t) => t.id);
@@ -832,7 +831,7 @@ async function removeTeamsWithoutItsMemberFromOrg({ userToRemoveFromOrg }: { use
};
})
// Remove Orgs from the list
.filter((team) => !isOrganization({ team }));
.filter((team) => !team.isOrganization);
const teamIdsToBeRemovedFromOrg = teamsToBeRemovedFromOrg.map((t) => t.id);
@@ -2,6 +2,9 @@ import { expect } from "@playwright/test";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { MembershipRole } from "@calcom/prisma/enums";
import { moveUserToOrg } from "@lib/orgMigration";
import { test } from "./lib/fixtures";
@@ -110,4 +113,54 @@ test.describe("Change username on settings", () => {
await expect(page).toHaveURL(/.*checkout.stripe.com/);
});
test("User can't take a username that has been migrated to a different username in an organization", async ({
users,
orgs,
page,
}) => {
const existingUser =
await test.step("Migrate user to a different username in an organization", async () => {
const org = await orgs.create({
name: "TestOrg",
});
const existingUser = await users.create({
username: "john",
emailDomain: org.organizationSettings?.orgAutoAcceptEmail ?? "",
name: "John Outside Organization",
});
await moveUserToOrg({
user: existingUser,
targetOrg: {
// Changed username. After this there is no user with username equal to {existingUser.username}
username: `${existingUser.username}-org`,
id: org.id,
membership: {
role: MembershipRole.MEMBER,
accepted: true,
},
},
shouldMoveTeams: false,
});
return existingUser;
});
await test.step("Changing username for another user to the previous username of migrated user - shouldn't be allowed", async () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const previousUsername = existingUser.username!;
const user = await users.create();
await user.apiLogin();
await page.goto("/settings/my-account/profile");
const usernameInput = page.locator("[data-testid=username-input]");
await usernameInput.fill(previousUsername);
await page.waitForLoadState("networkidle");
await expect(page.locator("[data-testid=update-username-btn]").nth(0)).toBeHidden();
await expect(page.locator("[data-testid=update-username-btn]").nth(1)).toBeHidden();
});
});
});
+23 -3
View File
@@ -604,15 +604,33 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
return membership;
},
getOrgMembership: async () => {
return prisma.membership.findFirstOrThrow({
const membership = await prisma.membership.findFirstOrThrow({
where: {
userId: user.id,
team: {
isOrganization: true,
},
},
include: { team: { include: { children: true } } },
include: {
team: {
include: {
children: true,
organizationSettings: true,
},
},
},
});
if (!membership) {
return membership;
}
return {
...membership,
team: {
...membership.team,
metadata: teamMetadataSchema.parse(membership.team.metadata),
},
};
},
getFirstEventAsOwner: async () =>
prisma.eventType.findFirstOrThrow({
@@ -670,6 +688,7 @@ type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & {
roleInOrganization?: MembershipRole;
schedule?: Schedule;
password?: string | null;
emailDomain?: string;
};
// creates the actual user in the db.
@@ -687,10 +706,11 @@ const createUser = (
? opts.username
: `${opts?.username || "user"}-${workerInfo.workerIndex}-${Date.now()}`;
const emailDomain = opts?.emailDomain || "example.com";
return {
username: uname,
name: opts?.name,
email: opts?.email ?? `${uname}@example.com`,
email: opts?.email ?? `${uname}@${emailDomain}`,
password: {
create: {
hash: hashPassword(uname),
@@ -2,6 +2,9 @@ import type { Browser, Page } from "@playwright/test";
import { expect } from "@playwright/test";
import prisma from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/client";
import { moveUserToOrg } from "@lib/orgMigration";
import { test } from "../lib/fixtures";
import { getInviteLink } from "../lib/testUtils";
@@ -16,7 +19,7 @@ test.afterEach(async ({ users, orgs }) => {
test.describe("Organization", () => {
test.describe("Email not matching orgAutoAcceptEmail", () => {
test("Org Invitation", async ({ browser, page, users, emails }) => {
test("nonexisting user invited to an organization", async ({ browser, page, users, emails }) => {
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true });
const { team: org } = await orgOwner.getOrgMembership();
await orgOwner.apiLogin();
@@ -83,7 +86,17 @@ test.describe("Organization", () => {
});
});
test("Team invitation", async ({ browser, page, users, emails }) => {
// This test is already covered by booking.e2e.ts where existing user is invited and his booking links are tested.
// We can re-test here when we want to test some more scenarios.
// eslint-disable-next-line @typescript-eslint/no-empty-function
test("existing user invited to an organization", () => {});
test("nonexisting user invited to a Team inside organization", async ({
browser,
page,
users,
emails,
}) => {
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, hasSubteam: true });
await orgOwner.apiLogin();
const { team } = await orgOwner.getFirstTeamMembership();
@@ -183,7 +196,7 @@ test.describe("Organization", () => {
});
test.describe("Email matching orgAutoAcceptEmail and a Verified Organization", () => {
test("Org Invitation", async ({ browser, page, users, emails }) => {
test("nonexisting user is invited to Org", async ({ browser, page, users, emails }) => {
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true });
const { team: org } = await orgOwner.getOrgMembership();
await orgOwner.apiLogin();
@@ -248,7 +261,52 @@ test.describe("Organization", () => {
});
});
test("Team Invitation", async ({ browser, page, users, emails }) => {
// Such a user has user.username changed directly in addition to having the new username in the profile.username
test("existing user migrated to an organization", async ({ users, page, emails }) => {
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true });
const { team: org } = await orgOwner.getOrgMembership();
await orgOwner.apiLogin();
const { existingUser } = await test.step("Invite an existing user to an organization", async () => {
const existingUser = await users.create({
username: "john",
emailDomain: org.organizationSettings?.orgAutoAcceptEmail ?? "",
name: "John Outside Organization",
});
await moveUserToOrg({
user: existingUser,
targetOrg: {
username: `${existingUser.username}-org`,
id: org.id,
membership: {
role: MembershipRole.MEMBER,
accepted: true,
},
},
shouldMoveTeams: false,
});
return { existingUser };
});
await test.step("Signing up with the previous username of the migrated user - shouldn't be allowed", async () => {
await page.goto("/signup");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await page.locator('input[name="username"]').fill(existingUser.username!);
await page
.locator('input[name="email"]')
.fill(`${existingUser.username}-differnet-email@example.com`);
await page.locator('input[name="password"]').fill("Password99!");
await page.waitForLoadState("networkidle");
await expect(page.locator('button[type="submit"]')).toBeDisabled();
});
});
test("nonexisting user is invited to a team inside organization", async ({
browser,
page,
users,
emails,
}) => {
const orgOwner = await users.create(undefined, {
hasTeam: true,
isOrg: true,
@@ -8,6 +8,7 @@ import { createOrUpdateMemberships } from "@calcom/features/auth/signup/utils/cr
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getLocaleFromRequest } from "@calcom/lib/getLocaleFromRequest";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import { usernameHandler, type RequestWithUsernameStatus } from "@calcom/lib/server/username";
import { createWebUser as syncServicesCreateWebUser } from "@calcom/lib/sync/SyncServiceManager";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
@@ -23,6 +24,8 @@ import {
validateAndGetCorrectedUsernameForTeam,
} from "../utils/token";
const log = logger.getSubLogger({ prefix: ["signupCalcomHandler"] });
async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) {
const {
email: _email,
@@ -35,6 +38,9 @@ async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) {
token: true,
})
.parse(req.body);
log.debug("handler", { email: _email });
let username: string | null = req.usernameStatus.requestedUserName;
let checkoutSessionId: string | null = null;
@@ -6,6 +6,7 @@ import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
import { createOrUpdateMemberships } from "@calcom/features/auth/signup/utils/createOrUpdateMemberships";
import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger";
import { isUsernameReservedDueToMigration } from "@calcom/lib/server/username";
import slugify from "@calcom/lib/slugify";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import { validateAndGetCorrectedUsernameAndEmail } from "@calcom/lib/validateUsername";
@@ -78,7 +79,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
organizationSettings: true,
},
});
if (team) {
const isInviteForATeamInOrganization = !!team.parent;
const isCheckingUsernameInGlobalNamespace = !team.isOrganization && !isInviteForATeamInOrganization;
if (isCheckingUsernameInGlobalNamespace) {
const isUsernameAvailable = !(await isUsernameReservedDueToMigration(correctedUsername));
if (!isUsernameAvailable) {
res.status(409).json({ message: "A user exists with that username" });
return;
}
}
const user = await prisma.user.upsert({
where: { email: userEmail },
update: {
@@ -123,6 +136,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
} else {
const isUsernameAvailable = !(await isUsernameReservedDueToMigration(correctedUsername));
if (!isUsernameAvailable) {
res.status(409).json({ message: "A user exists with that username" });
return;
}
if (IS_PREMIUM_USERNAME_ENABLED) {
const checkUsername = await checkPremiumUsername(correctedUsername);
if (checkUsername.premium) {
@@ -6,7 +6,6 @@ import InviteLinkSettingsModal from "@calcom/ee/teams/components/InviteLinkSetti
import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal";
import classNames from "@calcom/lib/classNames";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
import { getTeamUrlSync } from "@calcom/lib/getBookerUrl/client";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -77,7 +76,7 @@ export default function TeamListItem(props: Props) {
utils.viewer.teams.listInvites.invalidate();
const userOrganizationId = user?.profile?.organization?.id;
const isSubTeamOfDifferentOrg = team.parentId ? team.parentId != userOrganizationId : false;
const isDifferentOrg = isOrganization({ team }) && team.id !== userOrganizationId;
const isDifferentOrg = team.isOrganization && team.id !== userOrganizationId;
// If the user team being accepted is a sub-team of different organization or the different organization itself then page must be reloaded to let the session change reflect reliably everywhere.
if (variables.accept && (isSubTeamOfDifferentOrg || isDifferentOrg)) {
window.location.reload();
@@ -101,7 +100,7 @@ export default function TeamListItem(props: Props) {
const { hideDropdown, setHideDropdown } = props;
if (!team) return <></>;
const teamUrl = isOrganization({ team })
const teamUrl = team.isOrganization
? getTeamUrlSync({ orgSlug: team.slug, teamSlug: null })
: getTeamUrlSync({ orgSlug: team.parent ? team.parent.slug : null, teamSlug: team.slug });
const teamInfo = (
@@ -2,7 +2,6 @@ import { useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -46,14 +45,11 @@ export function TeamsListing() {
},
});
const teams = useMemo(() => data?.filter((m) => m.accepted && !isOrganization({ team: m })) || [], [data]);
const teams = useMemo(() => data?.filter((m) => m.accepted && !m.isOrganization) || [], [data]);
const teamInvites = useMemo(
() => data?.filter((m) => !m.accepted && !isOrganization({ team: m })) || [],
[data]
);
const teamInvites = useMemo(() => data?.filter((m) => !m.accepted && !m.isOrganization) || [], [data]);
const organizationInvites = (data?.filter((m) => !m.accepted && isOrganization({ team: m })) || []).filter(
const organizationInvites = (data?.filter((m) => !m.accepted && m.isOrganization) || []).filter(
(orgInvite) => {
const isThereASubTeamOfTheOrganizationInInvites = teamInvites.find(
(teamInvite) => teamInvite.parentId === orgInvite.id
+1 -2
View File
@@ -4,7 +4,6 @@ import { getStripeCustomerIdFromUserId } from "@calcom/app-store/stripepayment/l
import stripe from "@calcom/app-store/stripepayment/lib/server";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { ORGANIZATION_MIN_SEATS } from "@calcom/lib/constants";
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
import prisma from "@calcom/prisma";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
@@ -157,7 +156,7 @@ export const updateQuantitySubscriptionFromStripe = async (teamId: number) => {
)?.quantity;
if (!subscriptionQuantity) throw new Error("Subscription not found");
if (!!isOrganization({ team }) && membershipCount < ORGANIZATION_MIN_SEATS) {
if (team.isOrganization && membershipCount < ORGANIZATION_MIN_SEATS) {
console.info(
`Org ${teamId} has less members than the min ${ORGANIZATION_MIN_SEATS}, skipping updating subscription.`
);
+1 -5
View File
@@ -1,4 +1,4 @@
import type { Membership, Team } from "@calcom/prisma/client";
import type { Membership } from "@calcom/prisma/client";
import { MembershipRole } from "@calcom/prisma/enums";
export const enum ENTITY_PERMISSION_LEVEL {
@@ -19,10 +19,6 @@ export function canEditEntity(
);
}
export function isOrganization({ team }: { team: Pick<Team, "isOrganization"> }) {
return team.isOrganization;
}
export function getEntityPermissionLevel(
entity: {
userId: number | null;
+8 -1
View File
@@ -1,8 +1,10 @@
import slugify from "@calcom/lib/slugify";
import { ProfileRepository } from "./repository/profile";
import { isUsernameReservedDueToMigration } from "./username";
export async function checkRegularUsername(_username: string, currentOrgDomain?: string | null) {
const isCheckingUsernameInGlobalNamespace = !currentOrgDomain;
const username = slugify(_username);
const premium = !!process.env.NEXT_PUBLIC_IS_E2E && username.length < 5;
@@ -23,8 +25,13 @@ export async function checkRegularUsername(_username: string, currentOrgDomain?:
message: "A user exists with that username",
};
}
const isUsernameAvailable = isCheckingUsernameInGlobalNamespace
? !(await isUsernameReservedDueToMigration(username))
: true;
return {
available: true as const,
available: isUsernameAvailable,
premium,
};
}
+2
View File
@@ -3,4 +3,6 @@ import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
import { checkRegularUsername } from "./checkRegularUsername";
import { usernameCheck as checkPremiumUsername } from "./username";
// TODO: Replace `lib/checkPremiumUsername` with `usernameCheck` and then import checkPremiumUsername directly here.
// We want to remove dependency on website for signup stuff as signup is now part of app.
export const checkUsername = !IS_PREMIUM_USERNAME_ENABLED ? checkRegularUsername : checkPremiumUsername;
+2 -3
View File
@@ -4,7 +4,6 @@ import { Prisma } from "@calcom/prisma/client";
import type { User as UserType } from "@calcom/prisma/client";
import type { UpId, UserProfile } from "@calcom/types/UserProfile";
import { isOrganization } from "../../entityPermissionUtils";
import logger from "../../logger";
import { safeStringify } from "../../safeStringify";
import { ProfileRepository } from "./profile";
@@ -92,8 +91,8 @@ export class UserRepository {
userId,
});
const acceptedOrgMemberships = acceptedTeamMemberships.filter((membership) =>
isOrganization({ team: membership.team })
const acceptedOrgMemberships = acceptedTeamMemberships.filter(
(membership) => membership.team.isOrganization
);
const organizations = acceptedOrgMemberships.map((membership) => membership.team);
+27 -3
View File
@@ -2,10 +2,13 @@ import type { NextApiRequest, NextApiResponse } from "next";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
import { RedirectType } from "@calcom/prisma/enums";
import { IS_PREMIUM_USERNAME_ENABLED } from "../constants";
import logger from "../logger";
import notEmpty from "../notEmpty";
const log = logger.getSubLogger({ prefix: ["server/username"] });
const cachedData: Set<string> = new Set();
export type RequestWithUsernameStatus = NextApiRequest & {
@@ -106,7 +109,9 @@ const usernameHandler =
return handler(req, res);
};
const usernameCheck = async (usernameRaw: string) => {
const usernameCheck = async (usernameRaw: string, currentOrgDomain?: string | null) => {
log.debug("usernameCheck", { usernameRaw, currentOrgDomain });
const isCheckingUsernameInGlobalNamespace = !currentOrgDomain;
const response = {
available: true,
premium: false,
@@ -129,6 +134,10 @@ const usernameCheck = async (usernameRaw: string) => {
if (user) {
response.available = false;
} else {
response.available = isCheckingUsernameInGlobalNamespace
? !(await isUsernameReservedDueToMigration(username))
: true;
}
if (await isPremiumUserName(username)) {
@@ -158,6 +167,20 @@ const usernameCheck = async (usernameRaw: string) => {
return response;
};
/**
* Should be used when in global namespace(i.e. outside of an organization)
*/
export const isUsernameReservedDueToMigration = async (username: string) =>
!!(await prisma.tempOrgRedirect.findUnique({
where: {
from_type_fromOrgId: {
type: RedirectType.User,
from: username,
fromOrgId: 0,
},
},
}));
/**
* It is a bit different from usernameCheck because it also check if the user signing up is the same user that has a pending invitation to organization
* So, it uses email to uniquely identify the user and then also checks if the username requested by that user is available for taking or not.
@@ -207,12 +230,13 @@ const usernameCheckForSignup = async ({
const isClaimingAlreadySetUsername = user.username === username;
const isClaimingUnsetUsername = !user.username;
response.available = isClaimingUnsetUsername || isClaimingAlreadySetUsername;
// There are no premium users outside an organization only
// There are premium users outside an organization only
response.premium = await isPremiumUserName(username);
}
// If user isn't found, it's a direct signup and that can't be of an organization
} else {
// If user isn't found, it's a new signup and that can't be of an organization
response.premium = await isPremiumUserName(username);
response.available = !(await isUsernameReservedDueToMigration(username));
}
// get list of similar usernames in the db
@@ -3,7 +3,6 @@ import { orderBy } from "lodash";
import { hasFilter } from "@calcom/features/filters/lib/hasFilter";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
import { getOrgAvatarUrl, getTeamAvatarUrl, getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server";
@@ -214,7 +213,7 @@ export const getByViewerHandler = async ({ ctx, input }: GetByViewerOptions) =>
await Promise.all(
memberships
.filter((mmship) => {
if (isOrganization({ team: mmship.team })) {
if (mmship.team.isOrganization) {
return false;
} else {
if (!input?.filters || !hasFilter(input?.filters)) {
@@ -1,5 +1,4 @@
import { createAProfileForAnExistingUser } from "@calcom/lib/createAProfileForAnExistingUser";
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import { prisma } from "@calcom/prisma";
@@ -45,9 +44,8 @@ export const acceptOrLeaveHandler = async ({ ctx, input }: AcceptOrLeaveOptions)
});
}
const isAnOrganization = isOrganization({ team });
const isASubteam = team.parentId !== null;
const idOfOrganizationInContext = isAnOrganization ? team.id : isASubteam ? team.parentId : null;
const idOfOrganizationInContext = team.isOrganization ? team.id : isASubteam ? team.parentId : null;
const needProfileUpdate = !!idOfOrganizationInContext;
if (needProfileUpdate) {
await createAProfileForAnExistingUser({
@@ -2,7 +2,6 @@ import { updateQuantitySubscriptionFromStripe } from "@calcom/features/ee/teams/
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
import { createAProfileForAnExistingUser } from "@calcom/lib/createAProfileForAnExistingUser";
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { getTranslation } from "@calcom/lib/server/i18n";
@@ -174,7 +173,7 @@ async function handleExistingUsersInvites({
}
const translation = await getTranslation(input.language ?? "en", "common");
if (!isOrganization({ team })) {
if (!team.isOrganization) {
const [autoJoinUsers, regularUsers] = groupUsersByJoinability({
existingUsersWithMembersips,
team,
@@ -3,7 +3,6 @@ import type { TFunction } from "next-i18next";
import { sendTeamInviteEmail } from "@calcom/emails";
import { ENABLE_PROFILE_SWITCHER, WEBAPP_URL } from "@calcom/lib/constants";
import { isOrganization } from "@calcom/lib/entityPermissionUtils";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { isTeamAdmin } from "@calcom/lib/server/queries";
@@ -452,7 +451,7 @@ export function getAutoJoinStatus({
invitee: UserWithMembership;
connectionInfoMap: Record<string, ReturnType<typeof getOrgConnectionInfo>>;
}) {
const isRegularTeam = !isOrganization({ team }) && !team.parentId;
const isRegularTeam = !team.isOrganization && !team.parentId;
if (isRegularTeam) {
// There are no-auto join in regular teams ever