Handle non-org team with same slug as the organizations requestedSlug (#11996)

pull/12010/head^2
Hariom Balhara 2023-10-20 00:05:34 +05:30 committed by GitHub
parent 5e3c0cdea1
commit e2414b174a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 17 deletions

View File

@ -235,7 +235,7 @@ const nextConfig = {
? [
{
...matcherConfigRootPath,
destination: "/team/:orgSlug",
destination: "/team/:orgSlug?isOrgProfile=1",
},
{
...matcherConfigUserRoute,

View File

@ -1,3 +1,9 @@
// This route is reachable by
// 1. /team/[slug]
// 2. / (when on org domain e.g. http://calcom.cal.com/. This is through a rewrite from next.config.js)
// Also the getServerSideProps and default export are reused by
// 1. org/[orgSlug]/team/[slug]
// 2. org/[orgSlug]/[user]/[type]
import classNames from "classnames";
import type { GetServerSidePropsContext } from "next";
import Link from "next/link";
@ -12,6 +18,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import useTheme from "@calcom/lib/hooks/useTheme";
import logger from "@calcom/lib/logger";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import slugify from "@calcom/lib/slugify";
@ -34,7 +41,7 @@ import { ssrInit } from "@server/lib/ssr";
import { getTemporaryOrgRedirect } from "../../lib/getTemporaryOrgRedirect";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
const log = logger.getSubLogger({ prefix: ["team/[slug]"] });
function TeamPage({
team,
isUnpublished,
@ -277,12 +284,23 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
);
const isOrgContext = isValidOrgDomain && currentOrgDomain;
// Provided by Rewrite from next.config.js
const isOrgProfile = context.query?.isOrgProfile === "1";
const flags = await getFeatureFlagMap(prisma);
const isOrganizationFeatureEnabled = flags["organizations"];
log.debug("getServerSideProps", {
isOrgProfile,
isOrganizationFeatureEnabled,
isValidOrgDomain,
currentOrgDomain,
});
const team = await getTeamWithMembers({
slug: slugify(slug ?? ""),
orgSlug: currentOrgDomain,
isTeamView: true,
isOrgView: isValidOrgDomain && context.resolvedUrl === "/",
isOrgView: isValidOrgDomain && isOrgProfile,
});
if (!isOrgContext && slug) {
@ -299,17 +317,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const ssr = await ssrInit(context);
const metadata = teamMetadataSchema.parse(team?.metadata ?? {});
console.info("gSSP, team/[slug] - ", {
isValidOrgDomain,
currentOrgDomain,
ALLOWED_HOSTNAMES: process.env.ALLOWED_HOSTNAMES,
flags: JSON.stringify(flags),
});
// Taking care of sub-teams and orgs
if (
(!isValidOrgDomain && team?.parent) ||
(!isValidOrgDomain && !!metadata?.isOrganization) ||
flags["organizations"] !== true
!isOrganizationFeatureEnabled
) {
return { notFound: true } as const;
}

View File

@ -7,6 +7,7 @@ import { SchedulingType } from "@calcom/prisma/enums";
import { EventTypeMetaDataSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { WEBAPP_URL } from "../../../constants";
import logger from "../../../logger";
export type TeamWithMembers = Awaited<ReturnType<typeof getTeamWithMembers>>;
@ -17,6 +18,9 @@ export async function getTeamWithMembers(args: {
orgSlug?: string | null;
includeTeamLogo?: boolean;
isTeamView?: boolean;
/**
* If true, means that you are fetching an organization and not a team
*/
isOrgView?: boolean;
}) {
const { id, slug, userId, orgSlug, isTeamView, isOrgView, includeTeamLogo } = args;
@ -120,12 +124,30 @@ export async function getTeamWithMembers(args: {
}
if (id) where.id = id;
if (slug) where.slug = slug;
if (isOrgView) {
// We must fetch only the organization here.
// Note that an organization and a team that doesn't belong to an organization, both have parentId null
// If the organization has null slug(but requestedSlug is 'test') and the team also has slug 'test', we can't distinguish them without explicitly checking the metadata.isOrganization
// Note that, this isn't possible now to have same requestedSlug as the slug of a team not part of an organization. This is legacy teams handling mostly. But it is still safer to be sure that you are fetching an Organization only in case of isOrgView
where.metadata = {
path: ["isOrganization"],
equals: true,
};
}
const team = await prisma.team.findFirst({
const teams = await prisma.team.findMany({
where,
select: teamSelect,
});
if (teams.length > 1) {
logger.error("Found more than one team/Org. We should be doing something wrong.", {
where,
teams: teams.map((team) => ({ id: team.id, slug: team.slug })),
});
}
const team = teams[0];
if (!team) return null;
// This should improve performance saving already app data found.

View File

@ -72,17 +72,17 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => {
},
});
const slugCollisions = await prisma.team.findFirst({
// An org doesn't have a parentId. A team that isn't part of an org also doesn't have a parentId.
// So, an org can't have the same slug as a non-org team.
// There is a unique index on [slug, parentId] in Team because we don't add the slug to the team always. We only add metadata.requestedSlug in some cases. So, DB won't prevent creation of such an organization.
const hasANonOrgTeamOrOrgWithSameSlug = await prisma.team.findFirst({
where: {
slug: slug,
metadata: {
path: ["isOrganization"],
equals: true,
},
parentId: null,
},
});
if (slugCollisions || RESERVED_SUBDOMAINS.includes(slug))
if (hasANonOrgTeamOrOrgWithSameSlug || RESERVED_SUBDOMAINS.includes(slug))
throw new TRPCError({ code: "BAD_REQUEST", message: "organization_url_taken" });
if (userCollisions) throw new TRPCError({ code: "BAD_REQUEST", message: "admin_email_taken" });