* feat: redirect authenticated users from signup page - Add session check in signup getServerSideProps - Redirect to redirect parameter if present, otherwise to root - Follows existing authentication patterns in codebase Co-Authored-By: peer@cal.com <peer@cal.com> * fix test * fix test --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: unknown <adhabal2002@gmail.com> Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
207 lines
6.4 KiB
TypeScript
207 lines
6.4 KiB
TypeScript
import type { GetServerSidePropsContext } from "next";
|
|
import { z } from "zod";
|
|
|
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
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 { 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";
|
|
|
|
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 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,
|
|
isSAMLLoginEnabled,
|
|
prepopulateFormValues: undefined,
|
|
emailVerificationEnabled,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
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,
|
|
},
|
|
};
|
|
};
|