Files
calendar/apps/web/lib/signup/getServerSideProps.tsx
T
Benny JooGitHubbenny@cal.com <sldisek783@gmail.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ab21c7f805 refactor: Cal.diy (#28903)
* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com

This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main:

- Rebrand Cal.com to Cal.diy across the entire codebase
- Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions
- Switch license from AGPL-3.0 to MIT
- Remove docs/ directory (migrated to Nextra at cal.diy)
- Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc.
- Clean up .env.example for self-hosted Cal.diy
- Update Docker image references to calcom/cal.diy
- Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork
- Add PR welcome bot for Cal.diy contributors
- Fix API v2 breaking changes oasdiff ignore entries
- Replace Blacksmith CI runners with default GitHub Actions

3893 files changed, 20789 insertions(+), 411020 deletions(-)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* rip out org related comments in api v2

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-15 09:52:36 -03:00

223 lines
6.8 KiB
TypeScript

import process from "node:process";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
import { emailSchema } from "@calcom/lib/emailSchema";
import slugify from "@calcom/lib/slugify";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
const checkValidEmail = (email: string) => emailSchema.safeParse(email).success;
const querySchema = z.object({
username: z
.string()
.optional()
.transform((val) => val || ""),
email: emailSchema.optional(),
});
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const featuresRepository = new FeaturesRepository(prisma);
const emailVerificationEnabled =
await featuresRepository.checkIfFeatureIsEnabledGlobally("email-verification");
const signupDisabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("disable-signup");
const onboardingV3Enabled = await featuresRepository.checkIfFeatureIsEnabledGlobally("onboarding-v3");
const token = z.string().optional().parse(ctx.query.token);
const redirectUrlData = z
.string()
.refine((value) => value.startsWith(WEBAPP_URL), {
params: (value: string) => ({ value }),
message: "Redirect URL must start with 'cal.com'",
})
.optional()
.safeParse(ctx.query.redirect);
const redirectUrl = redirectUrlData.success && redirectUrlData.data ? redirectUrlData.data : null;
const session = await getServerSession({
req: ctx.req,
});
if (session?.user?.id) {
return {
redirect: {
permanent: false,
destination: redirectUrl || "/",
},
} as const;
}
const props = {
redirectUrl,
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
prepopulateFormValues: undefined,
emailVerificationEnabled,
onboardingV3Enabled,
};
if ((process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" && !token) || signupDisabled) {
return {
redirect: {
permanent: false,
destination: `/auth/error?error=Signup is disabled in this instance`,
},
} as const;
}
// no token given, treat as a normal signup without verification token
if (!token) {
// username + email prepopulated from query params
const queryData = querySchema.safeParse(ctx.query);
return {
props: JSON.parse(
JSON.stringify({
...props,
prepopulateFormValues: {
username: queryData.success ? queryData.data.username : null,
email: queryData.success ? queryData.data.email : null,
},
})
),
};
}
const verificationToken = await prisma.verificationToken.findUnique({
where: {
token,
},
include: {
team: {
select: {
metadata: true,
isOrganization: true,
parentId: true,
parent: {
select: {
slug: true,
isOrganization: true,
organizationSettings: true,
},
},
slug: true,
organizationSettings: true,
},
},
},
});
if (!verificationToken || verificationToken.expires < new Date()) {
return {
redirect: {
permanent: false,
destination: `/auth/error?error=Verification Token is missing or has expired`,
},
} as const;
}
if (!verificationToken?.team) {
return {
redirect: {
permanent: false,
destination: `/auth/error?error=Verification Token is not associated with any team`,
},
} as const;
}
const isValidEmail = checkValidEmail(verificationToken.identifier);
if (isValidEmail) {
const existingUser = await prisma.user.findFirst({
where: {
AND: [
{
email: verificationToken.identifier,
},
{
emailVerified: {
not: null,
},
},
],
},
select: {
id: true,
},
});
if (existingUser) {
return {
redirect: {
permanent: false,
destination: `/auth/login?callbackUrl=${WEBAPP_URL}/${ctx.query.callbackUrl}`,
},
};
}
}
const guessUsernameFromEmail = (email: string) => {
const [username] = email.split("@");
return username;
};
let username = isValidEmail ? guessUsernameFromEmail(verificationToken.identifier) : "";
const tokenTeam = {
...verificationToken.team,
metadata: teamMetadataSchema.parse(verificationToken.team.metadata ?? null),
};
const isATeamInOrganization = tokenTeam?.parentId !== null;
// Detect if the team is an org by either the metadata flag or if it has a parent team
const isOrganization = tokenTeam.isOrganization;
const isOrganizationOrATeamInOrganization = isOrganization || isATeamInOrganization;
// If we are dealing with an org, the slug may come from the team itself or its parent
const orgSlug = isOrganizationOrATeamInOrganization
? tokenTeam.metadata?.requestedSlug || tokenTeam.parent?.slug || tokenTeam.slug
: null;
// Org context shouldn't check if a username is premium
if (!IS_SELF_HOSTED && !isOrganizationOrATeamInOrganization && username) {
// Im not sure we actually hit this because of next redirects signup to website repo - but just in case this is pretty cool :)
const available = true;
const suggestion: string | undefined = undefined;
username = available ? username : suggestion || username;
}
const isOrgInviteByLink = isOrganizationOrATeamInOrganization && !isValidEmail;
const parentOrgSettings = tokenTeam?.parent?.organizationSettings ?? null;
return {
props: {
...props,
token,
prepopulateFormValues:
!isOrgInviteByLink && isValidEmail
? {
email: verificationToken.identifier,
username: isOrganizationOrATeamInOrganization
? getOrgUsernameFromEmail(
verificationToken.identifier,
(isOrganization
? tokenTeam.organizationSettings?.orgAutoAcceptEmail
: parentOrgSettings?.orgAutoAcceptEmail) || ""
)
: slugify(username),
}
: null,
orgSlug,
orgAutoAcceptEmail: isOrgInviteByLink
? (tokenTeam?.organizationSettings?.orgAutoAcceptEmail ??
parentOrgSettings?.orgAutoAcceptEmail ??
null)
: null,
},
};
};