191 lines
6.0 KiB
TypeScript
191 lines
6.0 KiB
TypeScript
import type { GetServerSidePropsContext } from "next";
|
|
import { z } from "zod";
|
|
|
|
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
|
|
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
|
|
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
|
import { getFeatureFlag } from "@calcom/features/flags/server/utils";
|
|
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
|
import slugify from "@calcom/lib/slugify";
|
|
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
|
|
|
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
|
|
import { ssrInit } from "@server/lib/ssr";
|
|
|
|
const checkValidEmail = (email: string) => z.string().email().safeParse(email).success;
|
|
|
|
const querySchema = z.object({
|
|
username: z
|
|
.string()
|
|
.optional()
|
|
.transform((val) => val || ""),
|
|
email: z.string().email().optional(),
|
|
});
|
|
|
|
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
|
const emailVerificationEnabled = await getFeatureFlag(prisma, "email-verification");
|
|
await ssrInit(ctx);
|
|
const signupDisabled = await getFeatureFlag(prisma, "disable-signup");
|
|
|
|
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 props = {
|
|
redirectUrl,
|
|
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
|
|
isSAMLLoginEnabled,
|
|
prepopulateFormValues: undefined,
|
|
emailVerificationEnabled,
|
|
};
|
|
|
|
// username + email prepopulated from query params
|
|
const { username: preFillusername, email: prefilEmail } = querySchema.parse(ctx.query);
|
|
|
|
if ((process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" && !token) || signupDisabled) {
|
|
return {
|
|
redirect: {
|
|
permanent: false,
|
|
destination: `/auth/error?error=Bitte melde dich bei uns, wenn du interesse an einem BLS cal Account hast`,
|
|
},
|
|
} as const;
|
|
}
|
|
|
|
// no token given, treat as a normal signup without verification token
|
|
if (!token) {
|
|
return {
|
|
props: JSON.parse(
|
|
JSON.stringify({
|
|
...props,
|
|
prepopulateFormValues: {
|
|
username: preFillusername || null,
|
|
email: prefilEmail || 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=Token zur Verifizierung fehlt oder ist abgelaufen`,
|
|
},
|
|
} as const;
|
|
}
|
|
|
|
const existingUser = await prisma.user.findFirst({
|
|
where: {
|
|
AND: [
|
|
{
|
|
email: verificationToken?.identifier,
|
|
},
|
|
{
|
|
emailVerified: {
|
|
not: null,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
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 = guessUsernameFromEmail(verificationToken.identifier);
|
|
|
|
const tokenTeam = {
|
|
...verificationToken?.team,
|
|
metadata: teamMetadataSchema.parse(verificationToken?.team?.metadata),
|
|
};
|
|
|
|
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) {
|
|
// 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, suggestion } = await checkPremiumUsername(username);
|
|
|
|
username = available ? username : suggestion || username;
|
|
}
|
|
|
|
const isValidEmail = checkValidEmail(verificationToken.identifier);
|
|
const isOrgInviteByLink = isOrganizationOrATeamInOrganization && !isValidEmail;
|
|
const parentOrgSettings = tokenTeam?.parent?.organizationSettings ?? null;
|
|
|
|
return {
|
|
props: {
|
|
...props,
|
|
token,
|
|
prepopulateFormValues: !isOrgInviteByLink
|
|
? {
|
|
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,
|
|
},
|
|
};
|
|
}; |